mirror of
https://github.com/SabreTools/MPF.git
synced 2026-02-04 13:45:29 +00:00
Compare commits
2146 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8a048c8a57 | ||
|
|
d51db072fc | ||
|
|
468c9937da | ||
|
|
206ac76633 | ||
|
|
339b0d93d1 | ||
|
|
fd5da5de74 | ||
|
|
42146f991d | ||
|
|
f3f05eee48 | ||
|
|
61bf2f69aa | ||
|
|
546aa70b69 | ||
|
|
65cd1cede3 | ||
|
|
dffa16139a | ||
|
|
98bacf11fc | ||
|
|
ee0ccecfb2 | ||
|
|
a0825f276b | ||
|
|
66d61e20de | ||
|
|
6455ebd434 | ||
|
|
f4436fddfa | ||
|
|
317777cf93 | ||
|
|
da6dbe136e | ||
|
|
13d7cf8124 | ||
|
|
bb8fea625b | ||
|
|
15a0659867 | ||
|
|
988a5f6d2b | ||
|
|
1941639473 | ||
|
|
d1772f743e | ||
|
|
870c2d1cec | ||
|
|
f7f6ae1eee | ||
|
|
dd9e527592 | ||
|
|
e1122fa976 | ||
|
|
8a44fa3355 | ||
|
|
51a9e3005f | ||
|
|
413b6da24b | ||
|
|
172a0fb5dc | ||
|
|
83a189a5d3 | ||
|
|
60c27ec89b | ||
|
|
51733557cd | ||
|
|
93d964c603 | ||
|
|
2925f2262b | ||
|
|
5b211a7345 | ||
|
|
ed4bd24fcb | ||
|
|
8a7761753b | ||
|
|
1b8cca9999 | ||
|
|
b75391b1c6 | ||
|
|
e9c2fd9245 | ||
|
|
fb24bbd8a5 | ||
|
|
4e3083c8e6 | ||
|
|
05738b7c11 | ||
|
|
f963db67b1 | ||
|
|
de64631c00 | ||
|
|
c8adef78c2 | ||
|
|
7b116e7a04 | ||
|
|
fb7b6ff1be | ||
|
|
7fb8e44c31 | ||
|
|
239ad4c4bc | ||
|
|
9834d0ea3e | ||
|
|
a35c13bd10 | ||
|
|
5e1777a7c7 | ||
|
|
66570300df | ||
|
|
4ac1fb201e | ||
|
|
cba8daa010 | ||
|
|
ba24a4b21a | ||
|
|
91c6fdac82 | ||
|
|
416656c457 | ||
|
|
fdd75818c4 | ||
|
|
ac302626c2 | ||
|
|
428f3cc547 | ||
|
|
66fc36fe3c | ||
|
|
9dddf1c9b6 | ||
|
|
5dbb955d26 | ||
|
|
2f2958bdea | ||
|
|
c91f6ebbce | ||
|
|
22fdd036eb | ||
|
|
3f12c6acb9 | ||
|
|
1dbae18da6 | ||
|
|
6370e2dd6a | ||
|
|
0c8879bc66 | ||
|
|
6be34414fe | ||
|
|
f15fc989c8 | ||
|
|
0fc53cb534 | ||
|
|
dc0909808a | ||
|
|
00401d1282 | ||
|
|
b9d0d5d8f6 | ||
|
|
22a6b77d27 | ||
|
|
bc4fe17fab | ||
|
|
4b4027f285 | ||
|
|
d28257b2b7 | ||
|
|
669ef47f32 | ||
|
|
be224800bc | ||
|
|
8dbb589d42 | ||
|
|
7b2fd5bf35 | ||
|
|
95fa651074 | ||
|
|
a0a155eb9b | ||
|
|
72339b18df | ||
|
|
95c9c7706d | ||
|
|
135bb43cdf | ||
|
|
cfc75ca84d | ||
|
|
33c35b63d7 | ||
|
|
851a43d46f | ||
|
|
a88bef481d | ||
|
|
781fec2b57 | ||
|
|
ee96367a45 | ||
|
|
9f9bfc0888 | ||
|
|
c6cc697320 | ||
|
|
5e3f7f740b | ||
|
|
e17a8f4708 | ||
|
|
ff4771a74a | ||
|
|
426717102d | ||
|
|
126bae33a4 | ||
|
|
11b8dd44bb | ||
|
|
cbbb8aaa8c | ||
|
|
9ee7cd7fd7 | ||
|
|
324c1fcee3 | ||
|
|
06776a6093 | ||
|
|
43a079bb28 | ||
|
|
d45345d338 | ||
|
|
1ff0340cae | ||
|
|
278c86f9f4 | ||
|
|
00089b799c | ||
|
|
0f98f03999 | ||
|
|
22f6f39a91 | ||
|
|
e9a9011dbd | ||
|
|
40bbb3d1c8 | ||
|
|
a48dad817b | ||
|
|
9f76dcc5fd | ||
|
|
4356561a8a | ||
|
|
347c522d62 | ||
|
|
998ecec261 | ||
|
|
ab8e775df0 | ||
|
|
246e6b8bfd | ||
|
|
c7f69de18f | ||
|
|
d4a98d7712 | ||
|
|
2983266e8a | ||
|
|
1baef4440a | ||
|
|
7cd25dae1c | ||
|
|
38f9b7234b | ||
|
|
2d7ea1bed9 | ||
|
|
806a69c280 | ||
|
|
a92159b8cb | ||
|
|
9451629461 | ||
|
|
51a1f0cc8e | ||
|
|
2830641b8a | ||
|
|
7c87a22dcc | ||
|
|
dd5b5d4c7d | ||
|
|
bc5e73d371 | ||
|
|
f47a55b723 | ||
|
|
c4d014e480 | ||
|
|
69b1d2f7ad | ||
|
|
7c295ca2f4 | ||
|
|
5af3aad68a | ||
|
|
f150483e84 | ||
|
|
92ef962f42 | ||
|
|
859e53f843 | ||
|
|
e70d70ca22 | ||
|
|
c2cf8147d3 | ||
|
|
cf32f38c0e | ||
|
|
1f92ff08d6 | ||
|
|
6aaf076434 | ||
|
|
6865b23aa7 | ||
|
|
e1c13982bd | ||
|
|
9cddcc2eae | ||
|
|
33932fad47 | ||
|
|
470e5c69fe | ||
|
|
3aae2990a3 | ||
|
|
503933e67f | ||
|
|
1a99fd9e71 | ||
|
|
affc175bda | ||
|
|
fe4c88d3ad | ||
|
|
9c830c9755 | ||
|
|
fc300465f8 | ||
|
|
de1032a099 | ||
|
|
aa22b9fbff | ||
|
|
a41f0d6237 | ||
|
|
9d5dfaaa68 | ||
|
|
7f08684e9a | ||
|
|
8c2ad6eca5 | ||
|
|
dad108de52 | ||
|
|
df3bf1f7c5 | ||
|
|
0e355b906c | ||
|
|
1d472bf777 | ||
|
|
a7e0ac0806 | ||
|
|
5a208926a5 | ||
|
|
d812ea7e2b | ||
|
|
f19111a1b0 | ||
|
|
a36f7d7df4 | ||
|
|
c5e8de6c1a | ||
|
|
6ebcca104f | ||
|
|
3f048c5243 | ||
|
|
dffebc5d43 | ||
|
|
37f2cf5bab | ||
|
|
9865f88a6f | ||
|
|
90d4d0d029 | ||
|
|
68c3d7c4fa | ||
|
|
503a6a8cdc | ||
|
|
c10b3d28bd | ||
|
|
d349ef8a9d | ||
|
|
3137a543a7 | ||
|
|
7b832049e8 | ||
|
|
6566db5913 | ||
|
|
8c70f19959 | ||
|
|
898069c799 | ||
|
|
01e991c5fd | ||
|
|
3b21fa62a0 | ||
|
|
12a13a2ffa | ||
|
|
074d2c031c | ||
|
|
e33588451d | ||
|
|
f2ba433859 | ||
|
|
b266467c33 | ||
|
|
8eece24d9a | ||
|
|
bb644e9a8b | ||
|
|
c07ca9f39c | ||
|
|
bb92c43b35 | ||
|
|
b9d6a13e20 | ||
|
|
891499710f | ||
|
|
1328a373ea | ||
|
|
4a59ce1d90 | ||
|
|
7a74042aef | ||
|
|
2f7abee51b | ||
|
|
a63c844ed1 | ||
|
|
91a0e85e24 | ||
|
|
c9a67b1b51 | ||
|
|
3d932705bc | ||
|
|
80cde96614 | ||
|
|
aae81035c1 | ||
|
|
d08716045a | ||
|
|
f34999e308 | ||
|
|
028f7d5788 | ||
|
|
c34aeb6e45 | ||
|
|
bdb367c2c9 | ||
|
|
63e6ce121a | ||
|
|
ac072618c4 | ||
|
|
7a640c58ee | ||
|
|
5ad75c80d1 | ||
|
|
78d648d90b | ||
|
|
d415a8f161 | ||
|
|
87ba8d573d | ||
|
|
55a84fc911 | ||
|
|
f9351ff058 | ||
|
|
e0482aad78 | ||
|
|
9243020cd6 | ||
|
|
616f3624b7 | ||
|
|
aff981171a | ||
|
|
4816c5ab6a | ||
|
|
77f9b048fb | ||
|
|
846db2f602 | ||
|
|
6a21ca9f86 | ||
|
|
9613cae204 | ||
|
|
59102a8330 | ||
|
|
52f51cf1ab | ||
|
|
98ae16f7ae | ||
|
|
c0d8a87c44 | ||
|
|
7a120d155a | ||
|
|
d99da089ef | ||
|
|
d76cd346d4 | ||
|
|
5082ca57c4 | ||
|
|
c31eeb001a | ||
|
|
bef4bf175c | ||
|
|
ac744a1e6d | ||
|
|
13d7d83dbb | ||
|
|
7608c08e7c | ||
|
|
c5c180a9c6 | ||
|
|
9bce6aea1a | ||
|
|
7cd84e2e9a | ||
|
|
b8d7bbc72e | ||
|
|
a60f11135e | ||
|
|
c2664a1d2d | ||
|
|
e5632634d0 | ||
|
|
daa3261c16 | ||
|
|
dbf7150a31 | ||
|
|
893fd34d36 | ||
|
|
e1961612c0 | ||
|
|
0f1b23056c | ||
|
|
26254e6b32 | ||
|
|
505fbf2567 | ||
|
|
1bb38ea987 | ||
|
|
a3144b1537 | ||
|
|
ad90e2b6f9 | ||
|
|
705060fa70 | ||
|
|
f8e8c02fcf | ||
|
|
eacee24d45 | ||
|
|
d980fffa09 | ||
|
|
853b8689b4 | ||
|
|
7e4089f79c | ||
|
|
54ee2829f1 | ||
|
|
f89cac5400 | ||
|
|
b003203aef | ||
|
|
4e5c9a242e | ||
|
|
5edb70745a | ||
|
|
f474b339ac | ||
|
|
bb9a344938 | ||
|
|
75ad9eae28 | ||
|
|
2b3b029545 | ||
|
|
9843644dfc | ||
|
|
54103a1d7e | ||
|
|
5da277ae64 | ||
|
|
51461a958d | ||
|
|
6d1fd9d47d | ||
|
|
6b6f888dc3 | ||
|
|
1c6a9da9c8 | ||
|
|
c335cd2869 | ||
|
|
dbe521b719 | ||
|
|
9486cdeedb | ||
|
|
2b9b186be0 | ||
|
|
73a78c786f | ||
|
|
786f2177bd | ||
|
|
ddaf5e35f3 | ||
|
|
b39542b651 | ||
|
|
4479733421 | ||
|
|
6907e5b6ac | ||
|
|
81f672ca42 | ||
|
|
611c33f302 | ||
|
|
9cffc80982 | ||
|
|
3ba4db8f0a | ||
|
|
26daa46486 | ||
|
|
51b14874c7 | ||
|
|
a6014e1b58 | ||
|
|
e4237fedef | ||
|
|
8d334b7228 | ||
|
|
bb95112559 | ||
|
|
6e798aa565 | ||
|
|
94f8d9709a | ||
|
|
37a2e5c957 | ||
|
|
e163b174ac | ||
|
|
db92acfdcc | ||
|
|
26e65b428b | ||
|
|
03c55216ca | ||
|
|
70ae5dd787 | ||
|
|
e2a5cf968d | ||
|
|
60f43de605 | ||
|
|
4205a0baef | ||
|
|
a52ac9f7b5 | ||
|
|
346dab0899 | ||
|
|
c3bfd02310 | ||
|
|
d688fc6975 | ||
|
|
521b8d656b | ||
|
|
9456301168 | ||
|
|
fad425da29 | ||
|
|
6b177c618d | ||
|
|
ca26307dbf | ||
|
|
8a7079a159 | ||
|
|
c4814fc950 | ||
|
|
7deaa9e7af | ||
|
|
1ad4738b60 | ||
|
|
4a82baa5d1 | ||
|
|
1c1740010d | ||
|
|
955fc4b8a0 | ||
|
|
fad9fa5f72 | ||
|
|
9dc976e423 | ||
|
|
c2c92b54d9 | ||
|
|
77ccdb0032 | ||
|
|
4e3046fadd | ||
|
|
70114ee59e | ||
|
|
4bb02b88fc | ||
|
|
f97e293ad2 | ||
|
|
2a040effde | ||
|
|
862e676590 | ||
|
|
bffa70bcc9 | ||
|
|
bb596c49f4 | ||
|
|
917986530b | ||
|
|
14bc7609c5 | ||
|
|
a2361c34bc | ||
|
|
3d29eeb3c3 | ||
|
|
c908a55ce6 | ||
|
|
c2b3932363 | ||
|
|
b4d47aea37 | ||
|
|
f8d3ae7bc7 | ||
|
|
9f50277888 | ||
|
|
96f826994a | ||
|
|
eda3c97465 | ||
|
|
ff380451db | ||
|
|
a9ee6667d0 | ||
|
|
54415241d2 | ||
|
|
79d2957ede | ||
|
|
0b5d52da7d | ||
|
|
274ad9fc9a | ||
|
|
a2217b536b | ||
|
|
43e7883ac9 | ||
|
|
c37d098eee | ||
|
|
17c2ca6fa8 | ||
|
|
4b2d30bc01 | ||
|
|
ec8b65a7fa | ||
|
|
ec5611f5ff | ||
|
|
3e350b666b | ||
|
|
e83f69fc3e | ||
|
|
6ecbbb6978 | ||
|
|
771483ac14 | ||
|
|
ccde878286 | ||
|
|
e0ab3e048b | ||
|
|
cf2ae163c4 | ||
|
|
5025a3e91a | ||
|
|
dab774dab3 | ||
|
|
04c6131d28 | ||
|
|
47561baee8 | ||
|
|
a8b1a8342d | ||
|
|
7b8ef00d59 | ||
|
|
65cc502a94 | ||
|
|
d38b465b08 | ||
|
|
783c323fd0 | ||
|
|
04af8807e5 | ||
|
|
1260dfdff2 | ||
|
|
e5b883fb73 | ||
|
|
1c08451487 | ||
|
|
29b483f805 | ||
|
|
2eff4a7488 | ||
|
|
5e94d02503 | ||
|
|
ccf2166b72 | ||
|
|
024394bbec | ||
|
|
301a0cb188 | ||
|
|
64231da666 | ||
|
|
5f56977021 | ||
|
|
436ccf7a34 | ||
|
|
ef7510804e | ||
|
|
8c61b87954 | ||
|
|
17ba117949 | ||
|
|
0737ba7641 | ||
|
|
e9dba0767e | ||
|
|
2d142e9e9d | ||
|
|
7a928decff | ||
|
|
eb5409bdee | ||
|
|
1578193068 | ||
|
|
131c95e6ef | ||
|
|
a7790a271f | ||
|
|
1b342d56ef | ||
|
|
a500211129 | ||
|
|
4d798fa547 | ||
|
|
597ebdc973 | ||
|
|
c6a8a9265f | ||
|
|
393c53769d | ||
|
|
fa21999d3f | ||
|
|
dafbb05b16 | ||
|
|
1c1b23a84b | ||
|
|
fd0fe4912d | ||
|
|
b5b54d13a2 | ||
|
|
da77987db3 | ||
|
|
774f44c8ce | ||
|
|
251b3754e4 | ||
|
|
963acc3336 | ||
|
|
90588a0f8b | ||
|
|
a56c212488 | ||
|
|
6484ab5fe0 | ||
|
|
1ff48258b8 | ||
|
|
81019f9d56 | ||
|
|
d47c435236 | ||
|
|
d59b114cba | ||
|
|
7f26dcba4e | ||
|
|
5e2766f982 | ||
|
|
c883f899bb | ||
|
|
8c9950d5fa | ||
|
|
3e842af273 | ||
|
|
b837623da2 | ||
|
|
6742901243 | ||
|
|
d6460a2b68 | ||
|
|
7af59dacc6 | ||
|
|
fc3ef36fef | ||
|
|
6298487346 | ||
|
|
727d9844d5 | ||
|
|
72e7619e2d | ||
|
|
24b4647037 | ||
|
|
713b3f0557 | ||
|
|
f796a9b131 | ||
|
|
2cdf92bf92 | ||
|
|
ccc1687f1a | ||
|
|
6057ec3a59 | ||
|
|
2a5e736285 | ||
|
|
010ef9016b | ||
|
|
02606318b0 | ||
|
|
d4f641b122 | ||
|
|
a1dd6e2d21 | ||
|
|
d35679d688 | ||
|
|
83f5083ce7 | ||
|
|
5b6457f4b7 | ||
|
|
c6517d526b | ||
|
|
e35f1fc2ec | ||
|
|
14f4128d4a | ||
|
|
5465252dc7 | ||
|
|
2573b47a79 | ||
|
|
fe20905524 | ||
|
|
88f19180a4 | ||
|
|
de89968a1d | ||
|
|
8fc53c91b0 | ||
|
|
1a1fbd4b40 | ||
|
|
cac6c3049b | ||
|
|
6a6871e922 | ||
|
|
4a02a3efac | ||
|
|
f6eb961af4 | ||
|
|
faeaaef02a | ||
|
|
ebf393e634 | ||
|
|
3fbd4ea719 | ||
|
|
d09ff6cf1c | ||
|
|
1dc0d57d47 | ||
|
|
a748bd4d3a | ||
|
|
35dec7fe57 | ||
|
|
c22d16349a | ||
|
|
0d77a8950c | ||
|
|
285e94ca69 | ||
|
|
747ac4ea3b | ||
|
|
405ae7c7e4 | ||
|
|
f5ebe968c0 | ||
|
|
06a61b17cb | ||
|
|
9e8e4f6e36 | ||
|
|
fa72211b57 | ||
|
|
d5f66000a9 | ||
|
|
a52ba0aa7a | ||
|
|
eb045928f9 | ||
|
|
440302495b | ||
|
|
0732e9db78 | ||
|
|
a167652b2b | ||
|
|
cfa07c1918 | ||
|
|
53b31f91cf | ||
|
|
01cbd2cff5 | ||
|
|
65ad629ee0 | ||
|
|
06adbde715 | ||
|
|
1e5000bd8a | ||
|
|
8cb0b37e80 | ||
|
|
32c12e1332 | ||
|
|
09b307aa24 | ||
|
|
a5a8fbbf51 | ||
|
|
b366d236c8 | ||
|
|
a833e926f3 | ||
|
|
950be07bf0 | ||
|
|
4c5c1417e9 | ||
|
|
6fdc3412e0 | ||
|
|
807b0c5f9e | ||
|
|
9e0b64a1d1 | ||
|
|
8cfbf2d9f1 | ||
|
|
0064737130 | ||
|
|
292e3999c5 | ||
|
|
5ed1e94d84 | ||
|
|
5b094f57cb | ||
|
|
8066b5541e | ||
|
|
921d0207c2 | ||
|
|
4374ff7f74 | ||
|
|
0be5825b5e | ||
|
|
14c630bea7 | ||
|
|
9a66c685fd | ||
|
|
5e0fa1ad47 | ||
|
|
79065dcc69 | ||
|
|
3d7355aee1 | ||
|
|
6e9a6724c3 | ||
|
|
be35acfb48 | ||
|
|
f1a46c2e82 | ||
|
|
872959c889 | ||
|
|
b848a401f8 | ||
|
|
ee4762f8b3 | ||
|
|
d68bcfb96a | ||
|
|
d2433e4749 | ||
|
|
56ec0e7057 | ||
|
|
26e5d33d17 | ||
|
|
8b8b51ace4 | ||
|
|
f350904441 | ||
|
|
8e8e3368d0 | ||
|
|
4d8153dba1 | ||
|
|
e4e4b5ecde | ||
|
|
8373a6b8f5 | ||
|
|
45c51ebc80 | ||
|
|
af27085cc1 | ||
|
|
82e3707dce | ||
|
|
85192e8d3e | ||
|
|
6f784a352e | ||
|
|
ee707cf1af | ||
|
|
a14c998b3b | ||
|
|
004208df6a | ||
|
|
2f765146d1 | ||
|
|
a7d548f7ce | ||
|
|
fbdb9875f3 | ||
|
|
39a524e3cc | ||
|
|
9740ca3a7a | ||
|
|
f8d81972bf | ||
|
|
fe9302a553 | ||
|
|
c0ed7a7a0e | ||
|
|
b0b48743ac | ||
|
|
47e79dab31 | ||
|
|
90edc42fdf | ||
|
|
45db365705 | ||
|
|
952828dddd | ||
|
|
4a1e953ffd | ||
|
|
25740c2936 | ||
|
|
3696257940 | ||
|
|
4d46b7db5c | ||
|
|
21f5f29ac0 | ||
|
|
436e198826 | ||
|
|
91eb8c96c3 | ||
|
|
edf0bcb4fe | ||
|
|
1526714ab9 | ||
|
|
767e0bb05b | ||
|
|
52e781329c | ||
|
|
0c1395d3ec | ||
|
|
791d9cdb7b | ||
|
|
7d7dc4ee4e | ||
|
|
97fd04b13a | ||
|
|
899db4c8c0 | ||
|
|
bba204cbfe | ||
|
|
145660e9f9 | ||
|
|
24de14aea5 | ||
|
|
d57161e4d1 | ||
|
|
a066a5234a | ||
|
|
229db5dda2 | ||
|
|
4f2a8d354a | ||
|
|
b886471d72 | ||
|
|
2bab266ae2 | ||
|
|
6c5fd9bac8 | ||
|
|
08e93d7f13 | ||
|
|
7a510e084b | ||
|
|
da46d20ffc | ||
|
|
234c0bfbab | ||
|
|
82d60dbf4a | ||
|
|
6dffb80609 | ||
|
|
267c0e3184 | ||
|
|
033ccbbe67 | ||
|
|
c31b3f5894 | ||
|
|
9b1a303fea | ||
|
|
80a0f6da35 | ||
|
|
0c30eb7a60 | ||
|
|
7a8125bb71 | ||
|
|
c4beffd845 | ||
|
|
f97c112a53 | ||
|
|
5ef43ab6be | ||
|
|
2c399f99bf | ||
|
|
42e9eb0b96 | ||
|
|
e67d65f908 | ||
|
|
4ec8954b14 | ||
|
|
1a6abb039c | ||
|
|
bb5d1e5ac8 | ||
|
|
03c4c475eb | ||
|
|
04d7817d28 | ||
|
|
7cd100bc53 | ||
|
|
019924232a | ||
|
|
b5fc9f0275 | ||
|
|
44509b72ed | ||
|
|
d532a63dbd | ||
|
|
227785b079 | ||
|
|
0e364be998 | ||
|
|
7ae1f64ee3 | ||
|
|
92463a103d | ||
|
|
101cdb7b34 | ||
|
|
e924299f85 | ||
|
|
b983f7eb4a | ||
|
|
33b4be8b24 | ||
|
|
71a4edc8ba | ||
|
|
ceb305eb54 | ||
|
|
0b0d13dcf3 | ||
|
|
9f02368622 | ||
|
|
152010ee14 | ||
|
|
c6d5f0aea5 | ||
|
|
8c2ad64bb8 | ||
|
|
fa54d694b6 | ||
|
|
dc35b08624 | ||
|
|
4429515ba2 | ||
|
|
fdbc7b34e5 | ||
|
|
a1ab442cf0 | ||
|
|
9ed5c297f6 | ||
|
|
4ce9b214b0 | ||
|
|
7dcdadda75 | ||
|
|
f87a4d9fe2 | ||
|
|
e4e5d173f0 | ||
|
|
115b242d59 | ||
|
|
706bf8a431 | ||
|
|
87ecf1aa99 | ||
|
|
b5cf274333 | ||
|
|
4f4b6879b6 | ||
|
|
3b19463913 | ||
|
|
37386cd182 | ||
|
|
e04bdad16c | ||
|
|
e37f12705d | ||
|
|
5c8dc2c23a | ||
|
|
e9121f3b03 | ||
|
|
d68175db4e | ||
|
|
9d8181b0e2 | ||
|
|
6d657f268a | ||
|
|
3b3c5f823d | ||
|
|
09fc313492 | ||
|
|
316d0f6e54 | ||
|
|
a0033238bd | ||
|
|
5b1c6a7f46 | ||
|
|
8c0dff6552 | ||
|
|
43b230c84a | ||
|
|
f1b657011d | ||
|
|
e4d8ac8e1c | ||
|
|
08f44173dd | ||
|
|
54765c71fd | ||
|
|
01f8b49214 | ||
|
|
e8b0b3efaa | ||
|
|
f637938858 | ||
|
|
ae326c1d2f | ||
|
|
a4a1e6bf0a | ||
|
|
ecee44966e | ||
|
|
83437977ba | ||
|
|
8fcac1d425 | ||
|
|
705b5f1049 | ||
|
|
367d0c104b | ||
|
|
5d4bed2d9e | ||
|
|
63756192d8 | ||
|
|
b68ec78184 | ||
|
|
c55d5183fb | ||
|
|
f82e925944 | ||
|
|
c19f9ea173 | ||
|
|
76b2dd79ab | ||
|
|
c96ff23ad1 | ||
|
|
cd68b55b93 | ||
|
|
cad14d96f7 | ||
|
|
daaf9f1932 | ||
|
|
cb7502b450 | ||
|
|
ece142bbf1 | ||
|
|
611fee4605 | ||
|
|
791e2d0272 | ||
|
|
81742a4676 | ||
|
|
1ff3f2210c | ||
|
|
be9e4b91d5 | ||
|
|
854dcc5f95 | ||
|
|
29b71db33a | ||
|
|
2ee64b222a | ||
|
|
afda54f97b | ||
|
|
ad37c573b6 | ||
|
|
b6cb3104ae | ||
|
|
af9b0dc214 | ||
|
|
b5440032de | ||
|
|
a8e783235c | ||
|
|
fc97fe99e3 | ||
|
|
1c73b1133f | ||
|
|
908eccaafa | ||
|
|
e114d126c5 | ||
|
|
c07b4e4a28 | ||
|
|
06491a6611 | ||
|
|
b9a35850ad | ||
|
|
17a0c5d083 | ||
|
|
1b9523c799 | ||
|
|
ae80ecefc8 | ||
|
|
50ae32e3db | ||
|
|
2d43873398 | ||
|
|
a3df433fef | ||
|
|
35e71d8527 | ||
|
|
c5d07e4be1 | ||
|
|
0df5093b45 | ||
|
|
05d920d095 | ||
|
|
d2f7ac9843 | ||
|
|
857a302aad | ||
|
|
46de589791 | ||
|
|
f14961061c | ||
|
|
453fcf5cb1 | ||
|
|
24cdb27cdb | ||
|
|
24235c5896 | ||
|
|
c226f8cf58 | ||
|
|
b8f6c9f65f | ||
|
|
bb42e2db10 | ||
|
|
db544fd4b7 | ||
|
|
370c6bde5a | ||
|
|
d7965ee37f | ||
|
|
b2c6e07ed1 | ||
|
|
19cef20ceb | ||
|
|
058a1aeeaa | ||
|
|
d185077925 | ||
|
|
e3948ba91b | ||
|
|
4a30f94007 | ||
|
|
1ae27bf9e3 | ||
|
|
bc88103471 | ||
|
|
9ec6fbfe52 | ||
|
|
ebdb8de6b3 | ||
|
|
b8b56f6308 | ||
|
|
0726f5a402 | ||
|
|
90da5d1a7e | ||
|
|
bd3c518fa0 | ||
|
|
44272f60bf | ||
|
|
640ce8d854 | ||
|
|
74732058f2 | ||
|
|
22e480ccd1 | ||
|
|
32a0cb0394 | ||
|
|
88551bc2ed | ||
|
|
039af56f6a | ||
|
|
7a428e2add | ||
|
|
c8dd85eb72 | ||
|
|
7c7a19c5a0 | ||
|
|
9bedd26b24 | ||
|
|
09afdf52fb | ||
|
|
c69afe69dd | ||
|
|
ab18c7920a | ||
|
|
5af1841d13 | ||
|
|
8c324e3b8b | ||
|
|
d99099d587 | ||
|
|
fa7b46a516 | ||
|
|
f7c746b536 | ||
|
|
b6e109133f | ||
|
|
0d694c1bde | ||
|
|
47f45fa46f | ||
|
|
481f4b41d1 | ||
|
|
359ad87faa | ||
|
|
ba47cb7da2 | ||
|
|
8927c49963 | ||
|
|
21f9668093 | ||
|
|
371571d13f | ||
|
|
b231f82c4c | ||
|
|
1741326253 | ||
|
|
1878ef5ad6 | ||
|
|
ae42f5edd7 | ||
|
|
4362ed71e0 | ||
|
|
f02904ea49 | ||
|
|
abf4eb9b7c | ||
|
|
1f942977cc | ||
|
|
7edadd4739 | ||
|
|
cb09816c63 | ||
|
|
48f0a826ca | ||
|
|
9c10842924 | ||
|
|
def499769f | ||
|
|
5e1e61a441 | ||
|
|
4896a12ec5 | ||
|
|
b28ad5d3cd | ||
|
|
4cdbbcedf0 | ||
|
|
6ab63ba651 | ||
|
|
d36c5099f3 | ||
|
|
8ba8347a0c | ||
|
|
4c5184eac5 | ||
|
|
9914b94716 | ||
|
|
150e69eca5 | ||
|
|
f09923974b | ||
|
|
d98bc28930 | ||
|
|
ee4a7ab653 | ||
|
|
6b20aee320 | ||
|
|
a5849325f3 | ||
|
|
86d2d83fbe | ||
|
|
308e0d6937 | ||
|
|
3541bca2d0 | ||
|
|
2496099532 | ||
|
|
6001395181 | ||
|
|
fa2192a284 | ||
|
|
f09155936e | ||
|
|
a07fca6a7b | ||
|
|
5fe7a1dac8 | ||
|
|
16ed2f9595 | ||
|
|
9004d2bc7b | ||
|
|
cc98b38290 | ||
|
|
18ef0cddff | ||
|
|
d9ca55d96c | ||
|
|
816c94de58 | ||
|
|
c2ba58148a | ||
|
|
d9533f2448 | ||
|
|
5126d1854c | ||
|
|
6baaf132a7 | ||
|
|
98a30e6558 | ||
|
|
0912b78568 | ||
|
|
d92a1d566d | ||
|
|
ff40d18ed3 | ||
|
|
4d9cd85ba6 | ||
|
|
b6ae390cee | ||
|
|
4692028cfb | ||
|
|
3ada7db916 | ||
|
|
aa4b2f415d | ||
|
|
56e91bf177 | ||
|
|
228c752585 | ||
|
|
6ce7ccfa91 | ||
|
|
c8c98278b6 | ||
|
|
0fc57c58cf | ||
|
|
3dcac28488 | ||
|
|
4cd7073bf6 | ||
|
|
1af21e7aba | ||
|
|
52adcd0b46 | ||
|
|
8a91593e58 | ||
|
|
729f8273fc | ||
|
|
78df6f6583 | ||
|
|
dc8dae4df7 | ||
|
|
53db9dbf81 | ||
|
|
22755a4af9 | ||
|
|
ba28b414ba | ||
|
|
95d10ecb1e | ||
|
|
c5ab2c747a | ||
|
|
476c494f4e | ||
|
|
758c49c1cc | ||
|
|
d80ad3b3cf | ||
|
|
3d06f80703 | ||
|
|
7344460409 | ||
|
|
19f58d9dde | ||
|
|
ad9f39f832 | ||
|
|
d51117b058 | ||
|
|
0d65d5114a | ||
|
|
30fec3c3d0 | ||
|
|
8eb86fde90 | ||
|
|
7616c6b2ba | ||
|
|
e0742cdfc7 | ||
|
|
a19937d630 | ||
|
|
fa92402bc5 | ||
|
|
cdc3da5839 | ||
|
|
3430f8c1db | ||
|
|
12fd55e76c | ||
|
|
0013606d61 | ||
|
|
4d520d7d63 | ||
|
|
7bfe174680 | ||
|
|
4951e7bf42 | ||
|
|
d7d9c468ae | ||
|
|
32faa33ad3 | ||
|
|
8a5475380a | ||
|
|
1dd5542390 | ||
|
|
5156b89eca | ||
|
|
83ea04c880 | ||
|
|
8695d2981e | ||
|
|
e0c299e6f0 | ||
|
|
16ec54f389 | ||
|
|
998bf5d5fa | ||
|
|
07ec821e9a | ||
|
|
56cf8f3574 | ||
|
|
c6ebfcd6d9 | ||
|
|
6ceffce63d | ||
|
|
f43aafc00d | ||
|
|
ab598d8377 | ||
|
|
dbd876a1c1 | ||
|
|
fd102cb56b | ||
|
|
0afb49b657 | ||
|
|
be980fe0c4 | ||
|
|
08b4e8d602 | ||
|
|
e483e1cdc6 | ||
|
|
5c2dce78e2 | ||
|
|
26ea383775 | ||
|
|
64938fd7f1 | ||
|
|
22318ee3c1 | ||
|
|
b2b54a2706 | ||
|
|
40f04e0321 | ||
|
|
a7638b8063 | ||
|
|
2abcad2a0f | ||
|
|
e088b05de4 | ||
|
|
a31d894b79 | ||
|
|
34fae4572d | ||
|
|
1ecf0ad1fa | ||
|
|
7c7509020f | ||
|
|
7c6b118282 | ||
|
|
c154a844e3 | ||
|
|
088f1b8545 | ||
|
|
fc3c636bdd | ||
|
|
14c807c882 | ||
|
|
d0e9c51786 | ||
|
|
b428bc0ba0 | ||
|
|
6dbbb91438 | ||
|
|
2066d36424 | ||
|
|
29552cd39d | ||
|
|
640e7091cc | ||
|
|
af12c18d2e | ||
|
|
f28cf614c3 | ||
|
|
888cb8ec9f | ||
|
|
04035ac524 | ||
|
|
600374eb2d | ||
|
|
6ecb932a82 | ||
|
|
4cbc9ac109 | ||
|
|
5ee0b7345b | ||
|
|
cc55330fad | ||
|
|
6589380fdf | ||
|
|
bd45482bf7 | ||
|
|
e14c8a8f03 | ||
|
|
4ac00e9a1a | ||
|
|
edf983e304 | ||
|
|
01a69ef9b3 | ||
|
|
a368afc14a | ||
|
|
d83fed16f5 | ||
|
|
949df08690 | ||
|
|
ab3abb5b3e | ||
|
|
d16e73a530 | ||
|
|
3f8c55ca47 | ||
|
|
2e9aaa50f9 | ||
|
|
36951dc5da | ||
|
|
b39c8dd738 | ||
|
|
a1155cf9b7 | ||
|
|
1d151d213e | ||
|
|
6dc0c1438a | ||
|
|
8739569db6 | ||
|
|
0dcba9ce71 | ||
|
|
8d37b85e12 | ||
|
|
43cf8e1a45 | ||
|
|
7317553483 | ||
|
|
0b342e265c | ||
|
|
08359dd45f | ||
|
|
a24415cae6 | ||
|
|
59f8161308 | ||
|
|
51f955a14c | ||
|
|
ddebdef00c | ||
|
|
6a4b6d613a | ||
|
|
97aef5e29c | ||
|
|
9549178c3a | ||
|
|
c531539c87 | ||
|
|
fa04461631 | ||
|
|
83d230dfe1 | ||
|
|
ca18bbb72c | ||
|
|
de0f2c1ad9 | ||
|
|
2f0019282e | ||
|
|
72ca479c9f | ||
|
|
de3c4362e7 | ||
|
|
987348fee8 | ||
|
|
100e012fe6 | ||
|
|
af59ebe1ff | ||
|
|
0762c88655 | ||
|
|
bbe9c94545 | ||
|
|
c7efda7da8 | ||
|
|
5c78f9bc29 | ||
|
|
e7c17c7b4b | ||
|
|
7a2497f168 | ||
|
|
5172f6f253 | ||
|
|
9017472fa4 | ||
|
|
6659c410c6 | ||
|
|
8c7b66a2f5 | ||
|
|
8f57d78200 | ||
|
|
ad2ee9efa8 | ||
|
|
192964f65a | ||
|
|
672f30af35 | ||
|
|
a7a17298f2 | ||
|
|
e17ad8e4a1 | ||
|
|
2a544676e6 | ||
|
|
6abfa9581d | ||
|
|
a6de548e5d | ||
|
|
9dc3e579b5 | ||
|
|
c5be7d7f73 | ||
|
|
70c0da703b | ||
|
|
1cbe81fba6 | ||
|
|
420e356f34 | ||
|
|
0e0ff0cb80 | ||
|
|
9003d05ae2 | ||
|
|
c247225cac | ||
|
|
4bccaa8ecf | ||
|
|
b579fec7ab | ||
|
|
37e4525c98 | ||
|
|
2269537848 | ||
|
|
441fb91222 | ||
|
|
89e0473019 | ||
|
|
3e13b35c84 | ||
|
|
fd4910fc36 | ||
|
|
964685770f | ||
|
|
634d64b5a1 | ||
|
|
19ec1c448f | ||
|
|
e3532d6f02 | ||
|
|
aaf0aabb55 | ||
|
|
575a5936ca | ||
|
|
391d265353 | ||
|
|
f90d19821c | ||
|
|
b79babf57e | ||
|
|
a929bb0022 | ||
|
|
975eb97e27 | ||
|
|
49800cf0ed | ||
|
|
de18609e00 | ||
|
|
9911446bf9 | ||
|
|
a9223211ab | ||
|
|
54c4eeba03 | ||
|
|
a30ee3e6ff | ||
|
|
1fa19489a3 | ||
|
|
f6298dfe89 | ||
|
|
79802a53f6 | ||
|
|
54bf43fd6b | ||
|
|
bb2b2f668b | ||
|
|
ecca27e012 | ||
|
|
fe0699ca48 | ||
|
|
73b2f0921f | ||
|
|
c56230c3af | ||
|
|
919b62822d | ||
|
|
21b0c9a08d | ||
|
|
63fafd05b3 | ||
|
|
0a2493a953 | ||
|
|
87ab750714 | ||
|
|
5cf3eca9eb | ||
|
|
b4a079b65f | ||
|
|
7f2d501edf | ||
|
|
c981f94092 | ||
|
|
afba46b8b0 | ||
|
|
4e416df3c8 | ||
|
|
1b54e52351 | ||
|
|
5e1568a148 | ||
|
|
8f0ac56cf8 | ||
|
|
37f0f9d4a4 | ||
|
|
a48f75c704 | ||
|
|
58a683e3c9 | ||
|
|
907637b128 | ||
|
|
85f3e97a44 | ||
|
|
beae9691fd | ||
|
|
b7876d54cc | ||
|
|
5b24223cb5 | ||
|
|
88cadff9ef | ||
|
|
b92b39e7eb | ||
|
|
8d29a29591 | ||
|
|
daf516bf9c | ||
|
|
115d9857af | ||
|
|
b322146e9e | ||
|
|
b6e3c9da82 | ||
|
|
6abdc632dc | ||
|
|
335ca6d5ac | ||
|
|
8752426694 | ||
|
|
5357ba5900 | ||
|
|
f2686096bd | ||
|
|
c4ef14ea3c | ||
|
|
03668bd6af | ||
|
|
2d90a63ca7 | ||
|
|
11e6e37331 | ||
|
|
b229a2d59e | ||
|
|
c61af9316f | ||
|
|
02e3040e1b | ||
|
|
60bbe29435 | ||
|
|
3274ea08aa | ||
|
|
97a61dea32 | ||
|
|
6cccf20b03 | ||
|
|
0a7e17ed00 | ||
|
|
706f75c0eb | ||
|
|
022e87c4bb | ||
|
|
8b29ac7e47 | ||
|
|
4d5b8baf6f | ||
|
|
e199e5a08c | ||
|
|
6cc2351bf7 | ||
|
|
c391dbd3c8 | ||
|
|
709d980b67 | ||
|
|
0903855d5c | ||
|
|
6ad2505bf8 | ||
|
|
5db8756639 | ||
|
|
5d176408a2 | ||
|
|
ab0b569798 | ||
|
|
ee76d49e56 | ||
|
|
c75d2dcae2 | ||
|
|
7c411d36db | ||
|
|
ca767cf576 | ||
|
|
b57e0bb97e | ||
|
|
5f059253a4 | ||
|
|
e0f8443653 | ||
|
|
4c23a4bbf3 | ||
|
|
0c1486bbce | ||
|
|
6f41c9a331 | ||
|
|
2879dd29d6 | ||
|
|
42e1ef45b4 | ||
|
|
19493fdf0c | ||
|
|
5870ad0673 | ||
|
|
cbd2850d1b | ||
|
|
e7c36c104a | ||
|
|
960840d9ba | ||
|
|
b8ac1bc9d4 | ||
|
|
89edf9a8f6 | ||
|
|
eb8db0b311 | ||
|
|
b10cf8b78a | ||
|
|
1dd5c0d6d0 | ||
|
|
a1e00d23a4 | ||
|
|
1a9d38dd0e | ||
|
|
2f3e7d105d | ||
|
|
a72b3c32b1 | ||
|
|
a479b16ae2 | ||
|
|
a9e7b6f5b3 | ||
|
|
60605d7d00 | ||
|
|
9dc7f726e9 | ||
|
|
46134032d6 | ||
|
|
9847c8f351 | ||
|
|
7b506586cd | ||
|
|
40bbd422b7 | ||
|
|
285fd69ab4 | ||
|
|
b436e19bbb | ||
|
|
bf6e079289 | ||
|
|
b623866b6e | ||
|
|
df7046723c | ||
|
|
3cd3047790 | ||
|
|
abd60612c5 | ||
|
|
02292acee1 | ||
|
|
c923899898 | ||
|
|
fcdd2ad036 | ||
|
|
02d3af0ac1 | ||
|
|
0516bccece | ||
|
|
2d6389d54d | ||
|
|
60d5a117b5 | ||
|
|
9b7e2e35f5 | ||
|
|
ddfb383f23 | ||
|
|
a1707486f4 | ||
|
|
451101ec67 | ||
|
|
83c583b7e6 | ||
|
|
33cb3d3c97 | ||
|
|
3d98f345c1 | ||
|
|
de8ce5c110 | ||
|
|
fd9ed77316 | ||
|
|
d974b73cce | ||
|
|
da8a67fcad | ||
|
|
471bc60ed7 | ||
|
|
fef46be34c | ||
|
|
9a92dbdedb | ||
|
|
d6be0a4154 | ||
|
|
d3ae372903 | ||
|
|
7621ef1a13 | ||
|
|
148fdd0590 | ||
|
|
232256310a | ||
|
|
5e938791ef | ||
|
|
08405dd9b4 | ||
|
|
512c637ea3 | ||
|
|
909ebd72a1 | ||
|
|
123136e90e | ||
|
|
299d25af27 | ||
|
|
9eee2f6444 | ||
|
|
2afb10b73b | ||
|
|
1b016e5915 | ||
|
|
1c403e1748 | ||
|
|
a372a133ca | ||
|
|
08a7df504b | ||
|
|
39f9d9a86d | ||
|
|
bdd5af65ce | ||
|
|
92d7d2ab91 | ||
|
|
8d4d7ce449 | ||
|
|
d2d650cace | ||
|
|
772cefd700 | ||
|
|
e20350160b | ||
|
|
08f4e91b27 | ||
|
|
021237bc38 | ||
|
|
78c36db2f9 | ||
|
|
7fd2562cb5 | ||
|
|
15a3be2e66 | ||
|
|
9b6a540ec6 | ||
|
|
2b51085bc2 | ||
|
|
822134070b | ||
|
|
26cd874779 | ||
|
|
9f21b68541 | ||
|
|
e49d95663b | ||
|
|
2c70392ada | ||
|
|
1b2248b1e7 | ||
|
|
a322dc6353 | ||
|
|
7c78ae47c6 | ||
|
|
a14dec1b7e | ||
|
|
53b5862697 | ||
|
|
83d379c7b5 | ||
|
|
be33db8339 | ||
|
|
97e9924a0b | ||
|
|
7daab55639 | ||
|
|
9d70b7469a | ||
|
|
b5504902c4 | ||
|
|
f5e82ccd75 | ||
|
|
f4c4c21a10 | ||
|
|
661d2440f2 | ||
|
|
f4af8097f6 | ||
|
|
e1822905e7 | ||
|
|
e5154dad5b | ||
|
|
30f8932039 | ||
|
|
c4f0792c77 | ||
|
|
79c7f13ff9 | ||
|
|
dbeeb0c69b | ||
|
|
58c53ff5e2 | ||
|
|
87c441887a | ||
|
|
8ee4dab239 | ||
|
|
e01ebf8d8e | ||
|
|
e92bcd378c | ||
|
|
80156e73d1 | ||
|
|
97803cd860 | ||
|
|
9a5feee095 | ||
|
|
84410056bd | ||
|
|
acd0e41703 | ||
|
|
c7dfb9dca7 | ||
|
|
0efd82bd59 | ||
|
|
426ceff451 | ||
|
|
f2be7ed34c | ||
|
|
5dcc783b95 | ||
|
|
d1c641e934 | ||
|
|
59be63785d | ||
|
|
0bf85ec729 | ||
|
|
b54c2dc254 | ||
|
|
021fcd0641 | ||
|
|
f2dadae7a3 | ||
|
|
7ce7df2625 | ||
|
|
09fcd384ab | ||
|
|
5f8625a384 | ||
|
|
621011af7a | ||
|
|
a0b81941d1 | ||
|
|
e735335773 | ||
|
|
7b62572a56 | ||
|
|
0f921c926b | ||
|
|
4d8a4d23c0 | ||
|
|
9d7eaa46fd | ||
|
|
cc9664f7d6 | ||
|
|
573b3e9d1c | ||
|
|
9808694d89 | ||
|
|
d70d8f5b6e | ||
|
|
b75c2d80bf | ||
|
|
aa747ff651 | ||
|
|
bcbf5daf0d | ||
|
|
aee1c05a45 | ||
|
|
0bb96a8dd3 | ||
|
|
1e1d2c7b63 | ||
|
|
f12375cddc | ||
|
|
ed26e6611a | ||
|
|
98f77eca07 | ||
|
|
1b2b560f8f | ||
|
|
b49cc0c9bd | ||
|
|
4ba58ea861 | ||
|
|
3f52a20c90 | ||
|
|
580089d06e | ||
|
|
1397ab0fa6 | ||
|
|
87400793eb | ||
|
|
45f79d95b1 | ||
|
|
c8a4a61028 | ||
|
|
44091981b2 | ||
|
|
d3352643fc | ||
|
|
114c7fb38a | ||
|
|
dc7da708dc | ||
|
|
72e56aa1c7 | ||
|
|
99ceab07ad | ||
|
|
c0f6c072ce | ||
|
|
e039124f6c | ||
|
|
c96e4a4c7a | ||
|
|
622a08acf3 | ||
|
|
f44b6bf0d0 | ||
|
|
a6d75e15ea | ||
|
|
a02f03c4cb | ||
|
|
d48f5132fb | ||
|
|
ed4ac24efa | ||
|
|
9f3b8a7c2c | ||
|
|
612d4bb1f5 | ||
|
|
b58a50d246 | ||
|
|
af83811d57 | ||
|
|
66835fe6ab | ||
|
|
34cc1d33c6 | ||
|
|
a42d14e3b8 | ||
|
|
87aa165edf | ||
|
|
d217d62007 | ||
|
|
27bcc0d40a | ||
|
|
e1df075cde | ||
|
|
8358692e8d | ||
|
|
e1fae01dab | ||
|
|
d206ab140a | ||
|
|
9d8722ab17 | ||
|
|
c4fa40c403 | ||
|
|
1d0b06bfbe | ||
|
|
2cdf473dcb | ||
|
|
1af9e2c2da | ||
|
|
9a1815fa1e | ||
|
|
f601961c49 | ||
|
|
406acd34c5 | ||
|
|
31cdcbbc25 | ||
|
|
2215ce71c9 | ||
|
|
1872fbb1c8 | ||
|
|
d99f912ac2 | ||
|
|
00a76fb648 | ||
|
|
187e951a47 | ||
|
|
c0b9b27aae | ||
|
|
b76bb17396 | ||
|
|
2efa6d3623 | ||
|
|
3972ce633d | ||
|
|
0dc7901393 | ||
|
|
a25ba6eaa5 | ||
|
|
4ea48dfe57 | ||
|
|
8f7ad8b2ee | ||
|
|
3b9800df07 | ||
|
|
4c80d3234e | ||
|
|
9e4af1d66b | ||
|
|
73555df2ea | ||
|
|
3ca78604fd | ||
|
|
0138046923 | ||
|
|
2129184209 | ||
|
|
dd2116f8a6 | ||
|
|
814c2d9149 | ||
|
|
b3f7276044 | ||
|
|
ad88aa980b | ||
|
|
aca55e9203 | ||
|
|
cc3330bb27 | ||
|
|
1370909db7 | ||
|
|
08cc0c394b | ||
|
|
cb6692aea3 | ||
|
|
b3badb3a55 | ||
|
|
cbf73901d3 | ||
|
|
4822e45d58 | ||
|
|
1d930d36bf | ||
|
|
9effcc403d | ||
|
|
05dcc039bd | ||
|
|
cb08656abc | ||
|
|
69b22fc736 | ||
|
|
bf857f6ce7 | ||
|
|
7ebf2378b3 | ||
|
|
aec25dab37 | ||
|
|
e11969780d | ||
|
|
02c98b1547 | ||
|
|
c864589478 | ||
|
|
bdea1593be | ||
|
|
f6b78c07ca | ||
|
|
982a217d32 | ||
|
|
06588752ad | ||
|
|
9b057d7141 | ||
|
|
93fb3a85b5 | ||
|
|
7320f9ba66 | ||
|
|
a94f43ae0c | ||
|
|
e19a3f02e5 | ||
|
|
375c2c896c | ||
|
|
b151e79aed | ||
|
|
b45901c133 | ||
|
|
fab54ca0ae | ||
|
|
31dd32f19b | ||
|
|
f51c79d282 | ||
|
|
743c943363 | ||
|
|
91f6e266d1 | ||
|
|
8e843647bf | ||
|
|
151402dc50 | ||
|
|
057f340c9c | ||
|
|
f3f9c63156 | ||
|
|
7910a79917 | ||
|
|
cc7acfcd00 | ||
|
|
5580e208f5 | ||
|
|
56ef06d651 | ||
|
|
204dfca126 | ||
|
|
eb337a8aaf | ||
|
|
aa6fd781e8 | ||
|
|
32bcfa1d42 | ||
|
|
3b2e14d0de | ||
|
|
88a0ce38f9 | ||
|
|
110c8337aa | ||
|
|
3dc79fea6b | ||
|
|
fb036385f9 | ||
|
|
428bb2817b | ||
|
|
791caff240 | ||
|
|
d92015d071 | ||
|
|
de98bd9e0b | ||
|
|
67f5a7794a | ||
|
|
fbef94f634 | ||
|
|
0e8363f06e | ||
|
|
a1bafe0426 | ||
|
|
c51461592c | ||
|
|
ef5f996ec4 | ||
|
|
aa5998a52e | ||
|
|
37207c1cb0 | ||
|
|
d1813fdbc7 | ||
|
|
faf9d1d6f8 | ||
|
|
0360df0604 | ||
|
|
fe6487eeb4 | ||
|
|
b9eedd1cf6 | ||
|
|
26281b8f0b | ||
|
|
f0508fdf93 | ||
|
|
166d69c7ec | ||
|
|
fabf08ba55 | ||
|
|
0e5d8af0e9 | ||
|
|
3e1263ebff | ||
|
|
c0ed830241 | ||
|
|
4209158807 | ||
|
|
6d79ebf449 | ||
|
|
88ab691f07 | ||
|
|
37b1dc574c | ||
|
|
ad56e249ed | ||
|
|
f28cfe3d69 | ||
|
|
9643442281 | ||
|
|
dd44ec7ac1 | ||
|
|
801c1126cf | ||
|
|
4fd72efc46 | ||
|
|
9ec045cb21 | ||
|
|
52801ee94c | ||
|
|
2077d53964 | ||
|
|
14046e9217 | ||
|
|
2766debffc | ||
|
|
4f4688899c | ||
|
|
8151db9696 | ||
|
|
227c61b588 | ||
|
|
61224fba7b | ||
|
|
1dbf5516d8 | ||
|
|
10fe6ac32a | ||
|
|
e39ee66726 | ||
|
|
fff2db75d0 | ||
|
|
3a14db53c6 | ||
|
|
8b76b82e0f | ||
|
|
e110c55e1a | ||
|
|
85c30fbad3 | ||
|
|
14e5c74c65 | ||
|
|
b180e31b56 | ||
|
|
d172a45891 | ||
|
|
e74289116e | ||
|
|
68b932bad0 | ||
|
|
c4793e849c | ||
|
|
1c525a6d2e | ||
|
|
c8610ee16a | ||
|
|
0fb3bc1106 | ||
|
|
3cf133d750 | ||
|
|
f0be6b0bc9 | ||
|
|
e0ccb8b10d | ||
|
|
940afae1fd | ||
|
|
94ba339ef2 | ||
|
|
bfead10d87 | ||
|
|
97f9a73fc1 | ||
|
|
7f4921a47c | ||
|
|
07359d2a63 | ||
|
|
e5ba670c04 | ||
|
|
3096c369d3 | ||
|
|
333368675a | ||
|
|
d78d744ae2 | ||
|
|
a8efe056c1 | ||
|
|
9b2475a854 | ||
|
|
33e2b27e6d | ||
|
|
91fe9d9bec | ||
|
|
b1b9b3134f | ||
|
|
ca5386b529 | ||
|
|
347beca874 | ||
|
|
27ca4fd1af | ||
|
|
6d57a05f8c | ||
|
|
4d81449cfa | ||
|
|
76fcb3ae10 | ||
|
|
b913f5f9ae | ||
|
|
68fa1eecfb | ||
|
|
dc84f17ac5 | ||
|
|
c91137130b | ||
|
|
972b07551f | ||
|
|
61562fe995 | ||
|
|
01b6c3cc98 | ||
|
|
b461dc76b9 | ||
|
|
03eeed4113 | ||
|
|
07615cb336 | ||
|
|
9a9a977cc4 | ||
|
|
ab5869331b | ||
|
|
1c9ebfd703 | ||
|
|
a9bc3587a6 | ||
|
|
b1af2b6061 | ||
|
|
2fccd53098 | ||
|
|
a7ad3e41d9 | ||
|
|
1fc6476f71 | ||
|
|
53e5a1b1b1 | ||
|
|
119e9dc4cc | ||
|
|
9263769906 | ||
|
|
ebaf9539b2 | ||
|
|
818fb5ab34 | ||
|
|
2f514082a9 | ||
|
|
5c5379e972 | ||
|
|
c39894d9fc | ||
|
|
2c6cf0e736 | ||
|
|
7e96bbee27 | ||
|
|
f7019546d1 | ||
|
|
086f54abf0 | ||
|
|
143a4149bd | ||
|
|
20aed68d58 | ||
|
|
7a2061a36e | ||
|
|
191c7b080e | ||
|
|
aab0813688 | ||
|
|
9ad564772c | ||
|
|
20bb34c3c7 | ||
|
|
b15ddf390b | ||
|
|
18d1562ad3 | ||
|
|
d6f2ecbd25 | ||
|
|
3620686afa | ||
|
|
1524224bc3 | ||
|
|
35200d53d6 | ||
|
|
4b7d4e05e7 | ||
|
|
7e73f69433 | ||
|
|
9e1a972df5 | ||
|
|
1edac34c1a | ||
|
|
9adc588c9c | ||
|
|
71bfdcba8f | ||
|
|
9854c9970a | ||
|
|
0c68164d6d | ||
|
|
a413357b57 | ||
|
|
5e283c9e80 | ||
|
|
500eb66e30 | ||
|
|
411e5245a5 | ||
|
|
d270501c2d | ||
|
|
f7f924593f | ||
|
|
5126a66eb9 | ||
|
|
7fddef238d | ||
|
|
ace673f90d | ||
|
|
e063df63df | ||
|
|
22b15710d0 | ||
|
|
87199c1b0d | ||
|
|
7e5952bbb8 | ||
|
|
1a52a3a205 | ||
|
|
742db4c854 | ||
|
|
4fd51dbe45 | ||
|
|
096a8a6a06 | ||
|
|
ad5cd5b8f9 | ||
|
|
3230d59f6a | ||
|
|
96fa8d8cef | ||
|
|
01bf3c9efb | ||
|
|
da0bf64e94 | ||
|
|
d39cf5d13f | ||
|
|
7c7e78fd4e | ||
|
|
d2c504898a | ||
|
|
58028b4f18 | ||
|
|
54e835687b | ||
|
|
10f2318541 | ||
|
|
eb4e95e5db | ||
|
|
203418eebc | ||
|
|
136c6c8fbf | ||
|
|
2c216d6a58 | ||
|
|
f7ec19cc5d | ||
|
|
e8c4a97158 | ||
|
|
5e5fc4c812 | ||
|
|
5e0d99ff5d | ||
|
|
e88e67c927 | ||
|
|
d963dbbd5f | ||
|
|
11ce78fca2 | ||
|
|
fd4022ba84 | ||
|
|
f91aecad8e | ||
|
|
709b7d357f | ||
|
|
59980fa160 | ||
|
|
180caf83cc | ||
|
|
7047ee832a | ||
|
|
4b5532e1b0 | ||
|
|
137155d3dc | ||
|
|
793dd8289a | ||
|
|
bcf298e40b | ||
|
|
589aef1e21 | ||
|
|
9ab0b7ab3b | ||
|
|
4c5ea13aac | ||
|
|
0cf48bbdaa | ||
|
|
8fc9cc18a3 | ||
|
|
b1e156eed6 | ||
|
|
a8b3a837ef | ||
|
|
3f3a1c9a44 | ||
|
|
cf4d2f5055 | ||
|
|
2d19d51bc7 | ||
|
|
4842bdd38b | ||
|
|
7cc4f6d6a7 | ||
|
|
b284ffd99d | ||
|
|
81d0cc2cc2 | ||
|
|
70f368d6e9 | ||
|
|
bfbae5cf5c | ||
|
|
7cc231f5fd | ||
|
|
eda34b3477 | ||
|
|
bfb67ec0c9 | ||
|
|
275fd0da48 | ||
|
|
1ad5c25ed0 | ||
|
|
512f7ae016 | ||
|
|
3a00efc7fd | ||
|
|
3068c74ad7 | ||
|
|
9bdead5616 | ||
|
|
0ed5feb96b | ||
|
|
ec80ef2ede | ||
|
|
617591cc26 | ||
|
|
c855aa5cef | ||
|
|
e2a27479f5 | ||
|
|
c8cfe76aa3 | ||
|
|
ecb0234258 | ||
|
|
24ce4bcc51 | ||
|
|
162af3de31 | ||
|
|
7c0af96dc9 | ||
|
|
026f999b46 | ||
|
|
bbbc3e98a0 | ||
|
|
f78ad4e7b6 | ||
|
|
d756ae2163 | ||
|
|
701738ce25 | ||
|
|
620bdc9132 | ||
|
|
71f474c26c | ||
|
|
c48137a5c1 | ||
|
|
e248368484 | ||
|
|
7225b8c32d | ||
|
|
20e475e130 | ||
|
|
88dc005592 | ||
|
|
40792ff0eb | ||
|
|
6ae59e91de | ||
|
|
5c7aa5f7f5 | ||
|
|
aaa1ed674f | ||
|
|
42def3bbe5 | ||
|
|
9cdab52556 | ||
|
|
5c104ebf3d | ||
|
|
7ab72e542c | ||
|
|
3a8420cd2f | ||
|
|
916aa9fd70 | ||
|
|
8809df10b6 | ||
|
|
34d65a9d70 | ||
|
|
2830d7c8a2 | ||
|
|
c702c8b0ac | ||
|
|
e1df9a9754 | ||
|
|
03fe5ce8a5 | ||
|
|
432a9dda16 | ||
|
|
1dfc0f731e | ||
|
|
af515e0adf | ||
|
|
2222b57143 | ||
|
|
d8cfa2e6a4 | ||
|
|
a622cdd4e0 | ||
|
|
2a4ec83d0d | ||
|
|
b1d144d091 | ||
|
|
23a8b66c16 | ||
|
|
381ebd5961 | ||
|
|
b4d1f7d6c3 | ||
|
|
b3eff64275 | ||
|
|
f15c00acca | ||
|
|
a66d0d4722 | ||
|
|
66cdade01b | ||
|
|
9e9f001768 | ||
|
|
3514bdcbc4 | ||
|
|
312df37365 | ||
|
|
6214d91940 | ||
|
|
23d500434b | ||
|
|
7ee5e3ab43 | ||
|
|
6be4bb7c85 | ||
|
|
d620c13e22 | ||
|
|
73b585cdd2 | ||
|
|
2f33581e4a | ||
|
|
f3dba9a026 | ||
|
|
d7d0af369c | ||
|
|
2ab9a45a71 | ||
|
|
dc0bf642a9 | ||
|
|
9e32435972 | ||
|
|
c2b724e949 | ||
|
|
195d3c6ff7 | ||
|
|
04a8079cee | ||
|
|
0b2daf7d30 | ||
|
|
1353ee603b | ||
|
|
e9d272d139 | ||
|
|
0308c7a81e | ||
|
|
4caad10c9b | ||
|
|
85c1623d33 | ||
|
|
5be1825bb5 | ||
|
|
ec5e533bd8 | ||
|
|
c3e4c7fa78 | ||
|
|
bf66a3fcbd | ||
|
|
101193cb78 | ||
|
|
b610c29be6 | ||
|
|
999c4dceb5 | ||
|
|
2957370fac | ||
|
|
5dd36d7b06 | ||
|
|
47b5bbe7e7 | ||
|
|
5a2e4ca77e | ||
|
|
c479ddb80b | ||
|
|
da1f59e0c1 | ||
|
|
9574832e86 | ||
|
|
3df7842d23 | ||
|
|
62a0f6e49a | ||
|
|
60c180e4dd | ||
|
|
343d937362 | ||
|
|
f20b321433 | ||
|
|
ba19b0825f | ||
|
|
5abb44957c | ||
|
|
b9d9cf812f | ||
|
|
d1529f428a | ||
|
|
5061a79c4c | ||
|
|
eab3e25cf2 | ||
|
|
33e73a8992 | ||
|
|
a63972b549 | ||
|
|
bc57df7fc1 | ||
|
|
e16f616286 | ||
|
|
c5497d787b | ||
|
|
64f60004c4 | ||
|
|
020c063177 | ||
|
|
a3e6f8157f | ||
|
|
f27040cd1c | ||
|
|
3a490cfc8d | ||
|
|
1f8c5998c1 | ||
|
|
f96d4ee37e | ||
|
|
d0d15f03af | ||
|
|
eff6407206 | ||
|
|
c1e559568a | ||
|
|
ef030b290c | ||
|
|
71630591a1 | ||
|
|
31505745be | ||
|
|
e40444b60c | ||
|
|
44edf387a6 | ||
|
|
d3d225644d | ||
|
|
3afd9200a9 | ||
|
|
c5c340ff12 | ||
|
|
b28b85f7c6 | ||
|
|
c60684b83c | ||
|
|
a183a1ec91 | ||
|
|
abd8488185 | ||
|
|
e6dea158e5 | ||
|
|
24150c7b3c | ||
|
|
040720a1c2 | ||
|
|
70d30d370f | ||
|
|
a50b99da07 | ||
|
|
2cecbe69ad | ||
|
|
557410660f | ||
|
|
0d75dbaaa2 | ||
|
|
d38f95c73a | ||
|
|
c9d59a90e4 | ||
|
|
fc6a34d987 | ||
|
|
8acabb692f | ||
|
|
61f8871839 | ||
|
|
69d61ad185 | ||
|
|
27391ed31b | ||
|
|
ed56c92101 | ||
|
|
ab8ae1524f | ||
|
|
de7c247583 | ||
|
|
997fd8f7ac | ||
|
|
e39e07246a | ||
|
|
e0b0406d76 | ||
|
|
1efbe9a784 | ||
|
|
fab921c2dd | ||
|
|
56896b4bea | ||
|
|
54eb7d8ecd | ||
|
|
114587b9f6 | ||
|
|
cad33c6c07 | ||
|
|
8154999f1c | ||
|
|
3a30c14085 | ||
|
|
db8b1df480 | ||
|
|
e20cac8a80 | ||
|
|
ce9b4e39f5 | ||
|
|
ddff4b2e58 | ||
|
|
16a470475c | ||
|
|
6e01473e87 | ||
|
|
8682089151 | ||
|
|
f45cb0075f | ||
|
|
afccb48798 | ||
|
|
ce6995fba0 | ||
|
|
3328d1adea | ||
|
|
33d79df9a8 | ||
|
|
4fb64b19d6 | ||
|
|
bf0f495c8b | ||
|
|
5d5f8e8d8c | ||
|
|
91aa248355 | ||
|
|
633c6c1efb | ||
|
|
facb1f673f | ||
|
|
9c8938c7f2 | ||
|
|
17f75f3ce6 | ||
|
|
b99c48afa2 | ||
|
|
9481fada23 | ||
|
|
7efbc5043c | ||
|
|
92880d3148 | ||
|
|
9800f2b8ae | ||
|
|
a5d01604cb | ||
|
|
aaab84f90a | ||
|
|
318a1a303c | ||
|
|
0061de6b2e | ||
|
|
b3db7c547f | ||
|
|
1da29464a8 | ||
|
|
d1d4ff41c6 | ||
|
|
673c2745a9 | ||
|
|
4ad88441cc | ||
|
|
71bb822856 | ||
|
|
2a7789bd12 | ||
|
|
1d0417cc1c | ||
|
|
905c578d90 | ||
|
|
a457c85e53 | ||
|
|
36630fc0ed | ||
|
|
f509ac60da | ||
|
|
bf8aac6f81 | ||
|
|
ceddcc0722 | ||
|
|
3a2bda6fb6 | ||
|
|
aea2403153 | ||
|
|
6e334df42f | ||
|
|
04cb68c9bd | ||
|
|
06060c071b | ||
|
|
02903686ad | ||
|
|
00cbed725d | ||
|
|
dabccd3a8a | ||
|
|
0e1e499a66 | ||
|
|
b4f485a0cc | ||
|
|
5bad68b9ff | ||
|
|
2db686fc47 | ||
|
|
d705a4b316 | ||
|
|
0154ee5bb6 | ||
|
|
29f14f5690 | ||
|
|
dd6f7b0794 | ||
|
|
e5c9591d64 | ||
|
|
df8da8bd46 | ||
|
|
c9b21006d1 | ||
|
|
2b37882322 | ||
|
|
8a2b3f2c89 | ||
|
|
c16a9aeb70 | ||
|
|
d5a0797c92 | ||
|
|
a55a769886 | ||
|
|
fd18c60f28 | ||
|
|
2704d1f88d | ||
|
|
c16c938fa4 | ||
|
|
497e2a09fd | ||
|
|
8bc8887ce6 | ||
|
|
7e5a5586f9 | ||
|
|
eee068db7e | ||
|
|
79e70173fa | ||
|
|
4f732557b9 | ||
|
|
6cbcebd661 | ||
|
|
f74dc01657 | ||
|
|
4735bef7b4 | ||
|
|
52a925df12 | ||
|
|
3e10199cb0 | ||
|
|
470e641a8c | ||
|
|
f61b84d058 | ||
|
|
5e77b43be1 | ||
|
|
46530a9fca | ||
|
|
12610c0d69 | ||
|
|
9bb9d3f407 | ||
|
|
a0ed20042c | ||
|
|
37c253aac2 | ||
|
|
68135c58ae | ||
|
|
f9efa71fcc | ||
|
|
184913ad28 | ||
|
|
ff52767a02 | ||
|
|
34ac5f9ba0 | ||
|
|
7296d109cc | ||
|
|
9be705ae30 | ||
|
|
af8b376f5a | ||
|
|
03f9668048 | ||
|
|
3bb23fa7cc | ||
|
|
f85c9d4e7e | ||
|
|
5de3d209be | ||
|
|
f1b136c817 | ||
|
|
c18829e0b3 | ||
|
|
d114b7f868 | ||
|
|
445cc173ce | ||
|
|
1e28fcad2e | ||
|
|
8d4e5155d9 | ||
|
|
027f562573 | ||
|
|
74caf084ca | ||
|
|
7396b02543 | ||
|
|
8035826b1b | ||
|
|
c6e73582c5 | ||
|
|
ca09d8c703 | ||
|
|
40b6551c7a | ||
|
|
95b664705d | ||
|
|
6bda7b35a2 | ||
|
|
6fedebf2a9 | ||
|
|
02f98c674b | ||
|
|
fb1e4130df | ||
|
|
c507f52b80 | ||
|
|
7b2784f1a2 | ||
|
|
7a7c83e8cf | ||
|
|
b86ef09763 | ||
|
|
71c050edf6 | ||
|
|
988f25b514 | ||
|
|
e878e8b904 | ||
|
|
bd3484cb3d | ||
|
|
3d769ed707 | ||
|
|
613be66f91 | ||
|
|
81e0ffbc3c | ||
|
|
8f3d325e7d | ||
|
|
a63472c6e6 | ||
|
|
8c98005605 | ||
|
|
8062d6cf17 | ||
|
|
78bf6e63ed | ||
|
|
68e0d759f7 | ||
|
|
26e72284af | ||
|
|
0efecd6601 | ||
|
|
9a3c2eb626 | ||
|
|
ca753b4526 | ||
|
|
230b6ca721 | ||
|
|
dc90f9af3f | ||
|
|
b5898c7ea3 | ||
|
|
36f1aea509 | ||
|
|
6742444182 | ||
|
|
e01fd37e6b | ||
|
|
ca7071f82a | ||
|
|
3cb67e3e65 | ||
|
|
e67dd589b5 | ||
|
|
74f491eaaa | ||
|
|
9ebd28ef5a | ||
|
|
2a58052bfd | ||
|
|
5ed73aff2b | ||
|
|
5a6ad09004 | ||
|
|
6a43b74043 | ||
|
|
69dc91aa1a | ||
|
|
f840db5143 | ||
|
|
f33cd41ebb | ||
|
|
da7896e62a | ||
|
|
08b5b4a1aa | ||
|
|
c397701818 | ||
|
|
617eb00f21 | ||
|
|
751fd0cf1e | ||
|
|
84c64e6073 | ||
|
|
ff5bc464ab | ||
|
|
b09a87c7e4 | ||
|
|
4032b48e63 | ||
|
|
12fad35065 | ||
|
|
c28be2da72 | ||
|
|
122cc0d20b | ||
|
|
89db32dd53 | ||
|
|
59040ae0f0 | ||
|
|
d8035745a9 | ||
|
|
c73e13c1f0 | ||
|
|
af75c7c949 | ||
|
|
896eb28308 | ||
|
|
6ca3d39e5f | ||
|
|
a7af0d0d7b | ||
|
|
04f5a7ea8d | ||
|
|
5d82cc5622 | ||
|
|
2f37f51c0c | ||
|
|
4690db61e1 | ||
|
|
672ec42903 | ||
|
|
cb39599a46 | ||
|
|
80b5ff3920 | ||
|
|
f27d3a91d3 | ||
|
|
b18357bff5 | ||
|
|
e3cd5b7082 | ||
|
|
16d7a6224b | ||
|
|
f8e32ec06c | ||
|
|
7073d4e298 | ||
|
|
f7f464920b | ||
|
|
e677dc20e9 | ||
|
|
9871eb9928 | ||
|
|
12b43cf688 | ||
|
|
7dd6db66ee | ||
|
|
d7726a070e | ||
|
|
fbc9d04782 | ||
|
|
24b08dd245 | ||
|
|
c7ebe69b05 | ||
|
|
22ffda4b5a | ||
|
|
b89c051d7d | ||
|
|
8b41f03472 | ||
|
|
700ff50eef | ||
|
|
95ac7236ff | ||
|
|
755f1b8e95 | ||
|
|
d431a4c87f | ||
|
|
04348d2d58 | ||
|
|
6f5214c1a4 | ||
|
|
1fe12be0b5 | ||
|
|
9c9ca16366 | ||
|
|
1702db71d1 | ||
|
|
0ba7ccdb9a | ||
|
|
3ccf65190e | ||
|
|
6b9ee3d5ed | ||
|
|
3ee0355034 | ||
|
|
7074fa1e2c | ||
|
|
44b91a6611 | ||
|
|
b2185dbed9 | ||
|
|
ec4b69b6e6 | ||
|
|
b6db873a00 | ||
|
|
db9222b737 | ||
|
|
b7bfa3ba28 | ||
|
|
ab25687119 | ||
|
|
9bfa0a01ad | ||
|
|
19c99fb7fe | ||
|
|
913244459e | ||
|
|
c66df99e22 | ||
|
|
03f4a6e2e4 | ||
|
|
31a243eeb4 | ||
|
|
043c0eff1c | ||
|
|
ff44313e13 | ||
|
|
e06ed31b21 | ||
|
|
aeaec9e805 | ||
|
|
6df0c76939 | ||
|
|
72efffcec4 | ||
|
|
930e4f7514 | ||
|
|
85f71032b5 | ||
|
|
ead530e2ec | ||
|
|
dc14b1ea52 | ||
|
|
4628be2855 | ||
|
|
1f24c08770 | ||
|
|
4889b9d0bf | ||
|
|
b3d7422a66 | ||
|
|
a0000528de | ||
|
|
2be46ec7a1 | ||
|
|
f2510a08a4 | ||
|
|
2787377250 | ||
|
|
e61ca31a3f | ||
|
|
1b735b5d06 | ||
|
|
ad3d26cdea | ||
|
|
f372ec4ccd | ||
|
|
44e75a50d7 | ||
|
|
6f0ea2de3e | ||
|
|
b4f0f7f4b3 | ||
|
|
5027d67731 | ||
|
|
6c8016345a | ||
|
|
7ba04e63c7 | ||
|
|
77dc1d0029 | ||
|
|
2b6538707f | ||
|
|
902e4e5715 | ||
|
|
28f6d50e5a | ||
|
|
d8ed7d6ad7 | ||
|
|
078d7d0ea3 | ||
|
|
5895a66c7a | ||
|
|
5ccff836e2 | ||
|
|
b2c2e8c4d9 | ||
|
|
890959cfe0 | ||
|
|
8729c7f20c | ||
|
|
74826909f4 | ||
|
|
eb4904afb4 | ||
|
|
06975316b9 | ||
|
|
e0be24e54f | ||
|
|
eb3cbc44a7 | ||
|
|
d6f39bfd4c | ||
|
|
576fa07a2c | ||
|
|
ae2b26f84e | ||
|
|
1d9a2c9be7 | ||
|
|
538c0bba09 | ||
|
|
57c31cad48 | ||
|
|
093db36f44 | ||
|
|
fd4db3f8f0 | ||
|
|
856a3161be | ||
|
|
dfafe9789a | ||
|
|
9f750d01c4 | ||
|
|
705ee82cbb | ||
|
|
d1f80efa41 | ||
|
|
f88e6617e5 | ||
|
|
1401422b08 | ||
|
|
4539066af0 | ||
|
|
ec23502275 | ||
|
|
331f875e7f | ||
|
|
e68f88062f | ||
|
|
d1bb8184d1 | ||
|
|
b7b3f36402 | ||
|
|
859af8efea | ||
|
|
8594d7884c | ||
|
|
946dc929f1 | ||
|
|
1e5b1a205e | ||
|
|
3271cd0ea3 | ||
|
|
647bab8cd3 | ||
|
|
89b2438dac | ||
|
|
1dcd36d640 | ||
|
|
2f63ebcd29 | ||
|
|
316953f38c | ||
|
|
62813ac701 | ||
|
|
22a936e60b | ||
|
|
6412205fa3 | ||
|
|
75c18ea3c4 | ||
|
|
6bdb93208e | ||
|
|
588ed19692 | ||
|
|
d56ab5ab37 | ||
|
|
819d18112e | ||
|
|
2e79cbb0db | ||
|
|
f024611de8 | ||
|
|
48e6283c37 | ||
|
|
fec70f17bc | ||
|
|
257f7ab7df | ||
|
|
c1ed3aca02 | ||
|
|
ace6a78236 | ||
|
|
f1e9f1d433 | ||
|
|
e83190c091 | ||
|
|
82eabc12ca | ||
|
|
76b549d773 | ||
|
|
227688d7dc | ||
|
|
4c9e2359af | ||
|
|
d179d7f464 | ||
|
|
fff19e75fd | ||
|
|
2175ccfb26 | ||
|
|
621923bdba | ||
|
|
3c216aa309 | ||
|
|
d85186e5cd | ||
|
|
1a94ad3d23 | ||
|
|
91776d939c | ||
|
|
3d058e633f | ||
|
|
94c0564fe8 | ||
|
|
87ecc7588e | ||
|
|
8a5c114a7e | ||
|
|
e307cb217f | ||
|
|
b8c56bea58 | ||
|
|
564a6af9b8 | ||
|
|
5bcde889b3 | ||
|
|
b39c96cdd2 | ||
|
|
2cc73a30f5 | ||
|
|
be7d745d10 | ||
|
|
d51adccab4 | ||
|
|
6141ce8539 | ||
|
|
96d6e8bd2f | ||
|
|
c034df4266 | ||
|
|
fe04c6df73 | ||
|
|
aff195eb71 | ||
|
|
3aca999b63 | ||
|
|
0e7ce76f01 | ||
|
|
6d72417512 | ||
|
|
ac89f06843 | ||
|
|
39ce56d579 | ||
|
|
9dbd30adba | ||
|
|
48de63513e | ||
|
|
56eaf7c2c5 | ||
|
|
c1bd30e008 | ||
|
|
4176f22d79 | ||
|
|
1e16e952cf | ||
|
|
9f493ae27a | ||
|
|
2021230836 | ||
|
|
12ba5702fd | ||
|
|
2cf083eb89 | ||
|
|
c1f22d47dc | ||
|
|
11287d081d | ||
|
|
8652af5697 | ||
|
|
69561cb1a0 | ||
|
|
d587d2b4b3 | ||
|
|
a19418e46f | ||
|
|
de22ead07c | ||
|
|
f777869103 | ||
|
|
aec1131271 | ||
|
|
308fad3ed2 | ||
|
|
61ce45667b | ||
|
|
ababfdd2ed | ||
|
|
9683074197 | ||
|
|
ab2bc8f50c | ||
|
|
aa86ddaf47 | ||
|
|
68afebace4 | ||
|
|
bebe3ab8a0 | ||
|
|
1f314df8c1 | ||
|
|
38c3638f21 | ||
|
|
cde4b671fc | ||
|
|
578854cd3b | ||
|
|
1cd7885194 | ||
|
|
c0de39c229 |
4
.editorconfig
Normal file
4
.editorconfig
Normal file
@@ -0,0 +1,4 @@
|
||||
[*.cs]
|
||||
|
||||
# SYSLIB1045: Convert to 'GeneratedRegexAttribute'.
|
||||
dotnet_diagnostic.SYSLIB1045.severity = silent
|
||||
28
.github/ISSUE_TEMPLATE/feature-request.md
vendored
Normal file
28
.github/ISSUE_TEMPLATE/feature-request.md
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
---
|
||||
name: Feature Request
|
||||
about: For when you know better than me what you want
|
||||
title: "[Request]"
|
||||
labels: enhancement
|
||||
assignees: mnadareski
|
||||
|
||||
---
|
||||
|
||||
**Before You Submit**
|
||||
|
||||
- Remember to try the [latest WIP build](https://github.com/SabreTools/MPF/releases/tag/rolling) to see if the feature already exists.
|
||||
- Is it copy protection related? If so, report the issue [here](https://github.com/SabreTools/BinaryObjectScanner/issues) instead.
|
||||
- Check [previous issues](https://github.com/SabreTools/MPF/issues) to see if any of those are related to what you're about to ask for.
|
||||
|
||||
If none of those apply, then continue...
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
22
.github/ISSUE_TEMPLATE/informational.md
vendored
Normal file
22
.github/ISSUE_TEMPLATE/informational.md
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
---
|
||||
name: Info
|
||||
about: Something you need to tell me
|
||||
title: "[Info]"
|
||||
labels: question
|
||||
assignees: mnadareski
|
||||
|
||||
---
|
||||
|
||||
**Before You Submit**
|
||||
|
||||
- Remember to try the [latest WIP build](https://github.com/SabreTools/MPF/releases/tag/rolling) to see if the feature already exists.
|
||||
- Is it copy protection related? If so, report the issue [here](https://github.com/SabreTools/BinaryObjectScanner/issues) instead.
|
||||
- Check [previous issues](https://github.com/SabreTools/MPF/issues) to see if any of those are related to what you're about to ask for.
|
||||
|
||||
If none of those apply, then continue...
|
||||
|
||||
**Is your information related to one of the dumping programs supported or something that isn't a bug in the code? Please describe.**
|
||||
A clear and concise description of what the information is. Ex. With the latest build of DumpingProgram, it [...]
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the information here.
|
||||
48
.github/ISSUE_TEMPLATE/issue-report.md
vendored
Normal file
48
.github/ISSUE_TEMPLATE/issue-report.md
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
---
|
||||
name: Issue Report
|
||||
about: Tell me what's wrong, seriously
|
||||
title: "[Problem]"
|
||||
labels: bug
|
||||
assignees: mnadareski
|
||||
|
||||
---
|
||||
|
||||
**Before You Submit**
|
||||
|
||||
- Remember to try the [latest WIP build](https://github.com/SabreTools/MPF/releases/tag/rolling) to see if the issue has already been addressed.
|
||||
- Is it copy protection related? If so, report the issue [here](https://github.com/SabreTools/BinaryObjectScanner/issues) instead.
|
||||
- Check multiple discs to help narrow down the issue
|
||||
- Check the Options to see if changing any of those affects your issue.
|
||||
|
||||
If all of those fail, then continue...
|
||||
|
||||
**Version**
|
||||
What version are you using?
|
||||
|
||||
- [ ] Stable release (version here)
|
||||
- [ ] WIP release (version here)
|
||||
|
||||
**Build**
|
||||
What runtime version are you using?
|
||||
|
||||
- [ ] .NET 8.0 running on (Operating System)
|
||||
|
||||
**Describe the issue**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
53
.github/workflows/build_check.yml
vendored
Normal file
53
.github/workflows/build_check.yml
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
name: MPF Check
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "master" ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
project: [MPF.Check]
|
||||
runtime: [win-x86, win-x64, win-arm64, linux-x64, linux-arm64, osx-x64, osx-arm64]
|
||||
framework: [net8.0] #[net20, net35, net40, net452, net472, net48, netcoreapp3.1, net5.0, net6.0, net7.0, net8.0]
|
||||
conf: [Debug] #[Release, Debug]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: 8.0.x
|
||||
|
||||
- name: Restore dependencies
|
||||
run: dotnet restore
|
||||
|
||||
- name: Build
|
||||
run: dotnet publish ${{ matrix.project }}/${{ matrix.project }}.csproj -f ${{ matrix.framework }} -r ${{ matrix.runtime }} -c ${{ matrix.conf == 'Release' && 'Release -p:DebugType=None -p:DebugSymbols=false' || 'Debug'}} --self-contained true --version-suffix ${{ github.sha }} ${{ (startsWith(matrix.framework, 'net5') || startsWith(matrix.framework, 'net6') || startsWith(matrix.framework, 'net7') || startsWith(matrix.framework, 'net8')) && '-p:PublishSingleFile=true' || ''}}
|
||||
|
||||
- name: Archive build
|
||||
run: |
|
||||
cd ${{ matrix.project }}/bin/${{ matrix.conf }}/${{ matrix.framework }}/${{ matrix.runtime }}/publish/
|
||||
zip -r ${{ github.workspace }}/${{ matrix.project }}_${{ matrix.framework }}_${{ matrix.runtime }}_${{ matrix.conf }}.zip ./
|
||||
|
||||
- name: Upload build
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.project }}_${{ matrix.framework }}_${{ matrix.runtime }}_${{ matrix.conf }}
|
||||
path: ${{ matrix.project }}_${{ matrix.framework }}_${{ matrix.runtime }}_${{ matrix.conf }}.zip
|
||||
|
||||
- name: Upload to rolling
|
||||
uses: ncipollo/release-action@v1.14.0
|
||||
with:
|
||||
allowUpdates: True
|
||||
artifacts: ${{ matrix.project }}_${{ matrix.framework }}_${{ matrix.runtime }}_${{ matrix.conf }}.zip
|
||||
body: 'Last built commit: ${{ github.sha }}'
|
||||
name: 'Rolling Release'
|
||||
prerelease: True
|
||||
replacesArtifacts: True
|
||||
tag: "rolling"
|
||||
updateOnlyUnreleased: True
|
||||
53
.github/workflows/build_cli.yml
vendored
Normal file
53
.github/workflows/build_cli.yml
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
name: MPF CLI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "master" ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
project: [MPF.CLI]
|
||||
runtime: [win-x86, win-x64, win-arm64, linux-x64, linux-arm64, osx-x64, osx-arm64]
|
||||
framework: [net8.0] #[net20, net35, net40, net452, net472, net48, netcoreapp3.1, net5.0, net6.0, net7.0, net8.0]
|
||||
conf: [Debug] #[Release, Debug]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: 8.0.x
|
||||
|
||||
- name: Restore dependencies
|
||||
run: dotnet restore
|
||||
|
||||
- name: Build
|
||||
run: dotnet publish ${{ matrix.project }}/${{ matrix.project }}.csproj -f ${{ matrix.framework }} -r ${{ matrix.runtime }} -c ${{ matrix.conf == 'Release' && 'Release -p:DebugType=None -p:DebugSymbols=false' || 'Debug'}} --self-contained true --version-suffix ${{ github.sha }} ${{ (startsWith(matrix.framework, 'net5') || startsWith(matrix.framework, 'net6') || startsWith(matrix.framework, 'net7') || startsWith(matrix.framework, 'net8')) && '-p:PublishSingleFile=true' || ''}}
|
||||
|
||||
- name: Archive build
|
||||
run: |
|
||||
cd ${{ matrix.project }}/bin/${{ matrix.conf }}/${{ matrix.framework }}/${{ matrix.runtime }}/publish/
|
||||
zip -r ${{ github.workspace }}/${{ matrix.project }}_${{ matrix.framework }}_${{ matrix.runtime }}_${{ matrix.conf }}.zip ./
|
||||
|
||||
- name: Upload build
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.project }}_${{ matrix.framework }}_${{ matrix.runtime }}_${{ matrix.conf }}
|
||||
path: ${{ matrix.project }}_${{ matrix.framework }}_${{ matrix.runtime }}_${{ matrix.conf }}.zip
|
||||
|
||||
- name: Upload to rolling
|
||||
uses: ncipollo/release-action@v1.14.0
|
||||
with:
|
||||
allowUpdates: True
|
||||
artifacts: ${{ matrix.project }}_${{ matrix.framework }}_${{ matrix.runtime }}_${{ matrix.conf }}.zip
|
||||
body: 'Last built commit: ${{ github.sha }}'
|
||||
name: 'Rolling Release'
|
||||
prerelease: True
|
||||
replacesArtifacts: True
|
||||
tag: "rolling"
|
||||
updateOnlyUnreleased: True
|
||||
61
.github/workflows/build_nupkg.yml
vendored
Normal file
61
.github/workflows/build_nupkg.yml
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
name: Nuget Pack
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "master" ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: 8.0.x
|
||||
|
||||
- name: Restore dependencies
|
||||
run: dotnet restore
|
||||
|
||||
- name: Pack
|
||||
run: dotnet pack
|
||||
|
||||
- name: Upload Execution Contexts package
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: 'Execution Contexts Package'
|
||||
path: 'MPF.ExecutionContexts/bin/Release/*.nupkg'
|
||||
|
||||
- name: Upload Execution Contexts to rolling
|
||||
uses: ncipollo/release-action@v1.14.0
|
||||
with:
|
||||
allowUpdates: True
|
||||
artifacts: 'MPF.ExecutionContexts/bin/Release/*.nupkg'
|
||||
body: 'Last built commit: ${{ github.sha }}'
|
||||
name: 'Rolling Release'
|
||||
prerelease: True
|
||||
replacesArtifacts: True
|
||||
tag: "rolling"
|
||||
updateOnlyUnreleased: True
|
||||
|
||||
- name: Upload Processors package
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: 'Processors Package'
|
||||
path: 'MPF.Processors/bin/Release/*.nupkg'
|
||||
|
||||
- name: Upload Execution Contexts to rolling
|
||||
uses: ncipollo/release-action@v1.14.0
|
||||
with:
|
||||
allowUpdates: True
|
||||
artifacts: 'MPF.Processors/bin/Release/*.nupkg'
|
||||
body: 'Last built commit: ${{ github.sha }}'
|
||||
name: 'Rolling Release'
|
||||
prerelease: True
|
||||
replacesArtifacts: True
|
||||
tag: "rolling"
|
||||
updateOnlyUnreleased: True
|
||||
67
.github/workflows/build_ui.yml
vendored
Normal file
67
.github/workflows/build_ui.yml
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
name: MPF UI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "master" ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
project: [MPF.UI]
|
||||
runtime: [win-x86, win-x64]
|
||||
framework: [net8.0-windows] #[net40, net452, net472, net48, netcoreapp3.1, net5.0-windows, net6.0-windows, net7.0-windows, net8.0-windows]
|
||||
conf: [Debug] #[Release, Debug]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: 8.0.x
|
||||
|
||||
- name: Restore dependencies
|
||||
run: dotnet restore
|
||||
|
||||
- name: Build
|
||||
run: dotnet publish ${{ matrix.project }}/${{ matrix.project }}.csproj -f ${{ matrix.framework }} -r ${{ matrix.runtime }} -c ${{ matrix.conf == 'Release' && 'Release -p:DebugType=None -p:DebugSymbols=false' || 'Debug'}} --self-contained true --version-suffix ${{ github.sha }} ${{ (startsWith(matrix.framework, 'net5') || startsWith(matrix.framework, 'net6') || startsWith(matrix.framework, 'net7') || startsWith(matrix.framework, 'net8')) && '-p:PublishSingleFile=true' || ''}}
|
||||
|
||||
- name: Bundle DiscImageCreator
|
||||
run: |
|
||||
wget https://github.com/user-attachments/files/17211434/DiscImageCreator_20241001.zip
|
||||
unzip -u DiscImageCreator_20241001.zip
|
||||
mkdir -p MPF.UI/bin/${{ matrix.conf }}/${{ matrix.framework }}/${{ matrix.runtime }}/publish/Programs/Creator
|
||||
mv Release_ANSI/* MPF.UI/bin/${{ matrix.conf }}/${{ matrix.framework }}/${{ matrix.runtime }}/publish/Programs/Creator/
|
||||
|
||||
- name: Bundle Redumper
|
||||
run: |
|
||||
wget https://github.com/superg/redumper/releases/download/build_416/redumper-2024.10.12_build416-win64.zip
|
||||
unzip redumper-2024.10.12_build416-win64.zip
|
||||
mkdir -p MPF.UI/bin/${{ matrix.conf }}/${{ matrix.framework }}/${{ matrix.runtime }}/publish/Programs/Redumper
|
||||
mv redumper-2024.10.12_build416-win64/bin/redumper.exe MPF.UI/bin/${{ matrix.conf }}/${{ matrix.framework }}/${{ matrix.runtime }}/publish/Programs/Redumper/
|
||||
|
||||
- name: Archive build
|
||||
run: |
|
||||
cd ${{ matrix.project }}/bin/${{ matrix.conf }}/${{ matrix.framework }}/${{ matrix.runtime }}/publish/
|
||||
zip -r ${{ github.workspace }}/${{ matrix.project }}_${{ matrix.framework }}_${{ matrix.runtime }}_${{ matrix.conf }}.zip ./
|
||||
|
||||
- name: Upload build
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.project }}_${{ matrix.framework }}_${{ matrix.runtime }}_${{ matrix.conf }}
|
||||
path: ${{ matrix.project }}_${{ matrix.framework }}_${{ matrix.runtime }}_${{ matrix.conf }}.zip
|
||||
|
||||
- name: Upload to rolling
|
||||
uses: ncipollo/release-action@v1.14.0
|
||||
with:
|
||||
allowUpdates: True
|
||||
artifacts: ${{ matrix.project }}_${{ matrix.framework }}_${{ matrix.runtime }}_${{ matrix.conf }}.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
23
.github/workflows/check_pr.yml
vendored
Normal 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
|
||||
28
.vscode/launch.json
vendored
Normal file
28
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
// Use IntelliSense to find out which attributes exist for C# debugging
|
||||
// Use hover for the description of the existing attributes
|
||||
// For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": ".NET Core Launch (console)",
|
||||
"type": "coreclr",
|
||||
"request": "launch",
|
||||
"preLaunchTask": "build",
|
||||
// If you have changed target frameworks, make sure to update the program path.
|
||||
"program": "${workspaceFolder}/MPF.Check/bin/Debug/net8.0/MPF.Check.dll",
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}/MPF.Check",
|
||||
// For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
|
||||
"console": "internalConsole",
|
||||
"stopAtEntry": false,
|
||||
"justMyCode": false
|
||||
},
|
||||
{
|
||||
"name": ".NET Core Attach",
|
||||
"type": "coreclr",
|
||||
"request": "attach",
|
||||
"processId": "${command:pickProcess}"
|
||||
}
|
||||
]
|
||||
}
|
||||
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"omnisharp.projectLoadTimeout": 480
|
||||
}
|
||||
28
.vscode/tasks.json
vendored
Normal file
28
.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "build",
|
||||
"command": "dotnet",
|
||||
"type": "process",
|
||||
"args": [
|
||||
"build",
|
||||
"${workspaceFolder}/MPF.sln",
|
||||
],
|
||||
"problemMatcher": "$msCompile"
|
||||
},
|
||||
{
|
||||
"label": "watch",
|
||||
"command": "dotnet",
|
||||
"type": "process",
|
||||
"args": [
|
||||
"watch",
|
||||
"run",
|
||||
"${workspaceFolder}/MPF/MPF.csproj",
|
||||
"/property:GenerateFullPaths=true",
|
||||
"/consoleloggerparameters:NoSummary"
|
||||
],
|
||||
"problemMatcher": "$msCompile"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<configuration>
|
||||
<appSettings>
|
||||
<add key="dicPath" value="Programs\DiscImageCreator.exe"/>
|
||||
<add key="psxt001zPath" value="psxt001z.exe"/>
|
||||
<add key="sgRawPath" value="sg_raw.exe"/>
|
||||
<add key="defaultOutputPath" value="ISO"/>
|
||||
</appSettings>
|
||||
</configuration>
|
||||
9
App.xaml
9
App.xaml
@@ -1,9 +0,0 @@
|
||||
<Application x:Class="DICUI.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:DICUI"
|
||||
StartupUri="MainWindow.xaml">
|
||||
<Application.Resources>
|
||||
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
17
App.xaml.cs
17
App.xaml.cs
@@ -1,17 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Configuration;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
|
||||
namespace DICUI
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for App.xaml
|
||||
/// </summary>
|
||||
public partial class App : Application
|
||||
{
|
||||
}
|
||||
}
|
||||
1701
CHANGELIST.md
Normal file
1701
CHANGELIST.md
Normal file
File diff suppressed because it is too large
Load Diff
102
Constants.cs
102
Constants.cs
@@ -1,102 +0,0 @@
|
||||
namespace DICUI
|
||||
{
|
||||
public static class UIElements
|
||||
{
|
||||
public const string StartDumping = "Start Dumping";
|
||||
public const string StopDumping = "Stop Dumping";
|
||||
}
|
||||
|
||||
public static class DICCommands
|
||||
{
|
||||
// Commands
|
||||
|
||||
public const string CompactDiscCommand = "cd";
|
||||
public const string DataCommand = "data";
|
||||
public const string AudioCommand = "audio";
|
||||
public const string GDROMCommand = "gd";
|
||||
public const string GDROMSwapCommand = "swap";
|
||||
public const string DVDCommand = "dvd";
|
||||
public const string BDCommand = "bd";
|
||||
public const string FloppyCommand = "fd";
|
||||
public const string StopCommand = "stop";
|
||||
public const string StartCommand = "start";
|
||||
public const string EjectCommand = "eject";
|
||||
public const string CloseCommand = "close";
|
||||
public const string ResetCommand = "reset";
|
||||
public const string SubCommand = "sub";
|
||||
public const string MDSCommand = "mds";
|
||||
public const string DriveSpeedCommand = "ls"; // Unlisted in help text
|
||||
|
||||
// DIC Flags
|
||||
|
||||
public const string ForceUnitAccessFlag = "/f";
|
||||
public const string DisableBeepFlag = "/q";
|
||||
public const string CDAddOffsetFlag = "/a";
|
||||
public const string CDBEOpcodeFlag = "/be";
|
||||
public const string CDD8OpcodeFlag = "/d8";
|
||||
public const string CDC2OpcodeFlag = "/c2";
|
||||
public const string CDMCNFlag = "/m";
|
||||
public const string CDAMSFFlag = "/p";
|
||||
public const string CDReverseFlag = "/r";
|
||||
public const string CDMultiSessionFlag = "/ms";
|
||||
public const string CDScanFileProtectFlag = "/sf";
|
||||
public const string CDScanSectorProtectFlag = "/ss";
|
||||
public const string CDScanAnitModFlag = "/am";
|
||||
public const string CDNoFixSubPFlag = "/np";
|
||||
public const string CDNoFixSubQFlag = "/nq";
|
||||
public const string CDNoFixSubRtoWFlag = "/nr";
|
||||
public const string CDNoFixSubQLibCryptFlag = "/nl";
|
||||
public const string CDNoFixSubQSecuROMFlag = "/ns";
|
||||
public const string CDSubchannelReadLevelFlag = "/s";
|
||||
public const string DVDCMIFlag = "/c";
|
||||
public const string DVDRawFlag = "/raw";
|
||||
}
|
||||
|
||||
public static class Template
|
||||
{
|
||||
// Manual information
|
||||
|
||||
public const string TitleField = "Title";
|
||||
public const string DiscNumberField = "Disc Number / Letter";
|
||||
public const string DiscTitleField = "Disc Title";
|
||||
public const string CategoryField = "Category";
|
||||
public const string RegionField = "Region";
|
||||
public const string LanguagesField = "Languages";
|
||||
public const string DiscSerialField = "Disc Serial";
|
||||
public const string BarcodeField = "Barcode";
|
||||
public const string ISBNField = "ISBN";
|
||||
public const string CommentsField = "Comments";
|
||||
public const string ContentsField = "Contents";
|
||||
public const string VersionField = "Version";
|
||||
public const string EditionField = "Edition/Release";
|
||||
public const string CopyProtectionField = "Copy Protection";
|
||||
public const string MasteringRingField = "Mastering Ring";
|
||||
public const string MasteringSIDField = "Mastering SID Code";
|
||||
public const string MouldSIDField = "Mould SID Code";
|
||||
public const string AdditionalMouldField = "Additional Mould";
|
||||
public const string ToolstampField = "Toolstamp or Mastering Code";
|
||||
|
||||
// Automatic Information
|
||||
|
||||
public const string PVDField = "Primary Volume Descriptor (PVD)";
|
||||
public const string DATField = "DAT";
|
||||
public const string ErrorCountField = "Error Count";
|
||||
public const string CuesheetField = "Cuesheet";
|
||||
public const string SubIntentionField = "SubIntention (SecuROM)";
|
||||
public const string WriteOffsetField = "WriteOffset";
|
||||
public const string LayerbreakField = "Layerbreak";
|
||||
public const string PlaystationEXEDateField = "EXE Date"; // TODO: Not automatic yet
|
||||
public const string PlayStationEDCField = "EDC"; // TODO: Not automatic yet
|
||||
public const string PlayStationAntiModchipField = "Anti-modchip"; // TODO: Not automatic yet
|
||||
public const string PlayStationLibCryptField = "LibCrypt"; // TODO: Not automatic yet
|
||||
public const string SaturnHeaderField = "Header"; // TODO: Not automatic yet
|
||||
public const string SaturnBuildDateField = "Build Date"; // TODO: Not automatic yet
|
||||
|
||||
// Default values
|
||||
|
||||
public const string RequiredValue = "(REQUIRED)";
|
||||
public const string RequiredIfExistsValue = "(REQUIRED, IF EXISTS)";
|
||||
public const string OptionalValue = "(OPTIONAL)";
|
||||
public const string YesNoValue = "Yes/No";
|
||||
}
|
||||
}
|
||||
131
DICUI.csproj
131
DICUI.csproj
@@ -1,131 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{7B1B75EB-8940-466F-BD51-76471A57F9BE}</ProjectGuid>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<RootNamespace>DICUI</RootNamespace>
|
||||
<AssemblyName>DICUI</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
<IsWebBootstrapper>false</IsWebBootstrapper>
|
||||
<PublishUrl>C:\Users\admin\Desktop\</PublishUrl>
|
||||
<Install>true</Install>
|
||||
<InstallFrom>Disk</InstallFrom>
|
||||
<UpdateEnabled>false</UpdateEnabled>
|
||||
<UpdateMode>Foreground</UpdateMode>
|
||||
<UpdateInterval>7</UpdateInterval>
|
||||
<UpdateIntervalUnits>Days</UpdateIntervalUnits>
|
||||
<UpdatePeriodically>false</UpdatePeriodically>
|
||||
<UpdateRequired>false</UpdateRequired>
|
||||
<MapFileExtensions>true</MapFileExtensions>
|
||||
<ApplicationRevision>1</ApplicationRevision>
|
||||
<ApplicationVersion>1.0.0.%2a</ApplicationVersion>
|
||||
<UseApplicationTrust>false</UseApplicationTrust>
|
||||
<PublishWizardCompleted>true</PublishWizardCompleted>
|
||||
<BootstrapperEnabled>true</BootstrapperEnabled>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<ApplicationIcon>Icon.ico</ApplicationIcon>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<ManifestCertificateThumbprint>654CEE5FEAF46C8C1C369D7ED34DA157828A8D2F</ManifestCertificateThumbprint>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<ManifestKeyFile>WpfApp1_TemporaryKey.pfx</ManifestKeyFile>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<GenerateManifests>true</GenerateManifests>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<SignManifests>false</SignManifests>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup />
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Configuration" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Windows.Forms" />
|
||||
<Reference Include="System.Xml" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Xaml">
|
||||
<RequiredTargetFramework>4.0</RequiredTargetFramework>
|
||||
</Reference>
|
||||
<Reference Include="WindowsBase" />
|
||||
<Reference Include="PresentationCore" />
|
||||
<Reference Include="PresentationFramework" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ApplicationDefinition Include="App.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</ApplicationDefinition>
|
||||
<Compile Include="Constants.cs" />
|
||||
<Compile Include="Utilities.cs" />
|
||||
<Page Include="MainWindow.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Compile Include="App.xaml.cs">
|
||||
<DependentUpon>App.xaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Enumerations.cs" />
|
||||
<Compile Include="MainWindow.xaml.cs">
|
||||
<DependentUpon>MainWindow.xaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Properties\AssemblyInfo.cs">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="App.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<BootstrapperPackage Include=".NETFramework,Version=v4.6.1">
|
||||
<Visible>False</Visible>
|
||||
<ProductName>Microsoft .NET Framework 4.6.1 %28x86 and x64%29</ProductName>
|
||||
<Install>true</Install>
|
||||
</BootstrapperPackage>
|
||||
<BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
|
||||
<Visible>False</Visible>
|
||||
<ProductName>.NET Framework 3.5 SP1</ProductName>
|
||||
<Install>false</Install>
|
||||
</BootstrapperPackage>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Icon.ico" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
||||
25
DICUI.sln
25
DICUI.sln
@@ -1,25 +0,0 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.27130.2036
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DICUI", "DICUI.csproj", "{7B1B75EB-8940-466F-BD51-76471A57F9BE}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{7B1B75EB-8940-466F-BD51-76471A57F9BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{7B1B75EB-8940-466F-BD51-76471A57F9BE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{7B1B75EB-8940-466F-BD51-76471A57F9BE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{7B1B75EB-8940-466F-BD51-76471A57F9BE}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {73C62E6A-6584-4D93-83B5-ECB1FBDB469B}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
107
Enumerations.cs
107
Enumerations.cs
@@ -1,107 +0,0 @@
|
||||
namespace DICUI
|
||||
{
|
||||
/// <summary>
|
||||
/// Known disc types
|
||||
/// </summary>
|
||||
public enum DiscType
|
||||
{
|
||||
NONE = 0,
|
||||
CD,
|
||||
DVD5,
|
||||
DVD9,
|
||||
GDROM,
|
||||
HDDVD,
|
||||
BD25,
|
||||
BD50,
|
||||
|
||||
// Special Formats
|
||||
GameCubeGameDisc,
|
||||
UMD,
|
||||
|
||||
// Keeping this separate since it's currently unsupported in the UI
|
||||
Floppy = 99,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Known systems
|
||||
/// </summary>
|
||||
/// <remarks>Ensure that Utilities methods are updated as well</remarks>
|
||||
public enum KnownSystem
|
||||
{
|
||||
NONE = 0,
|
||||
|
||||
#region Consoles
|
||||
|
||||
BandaiPlaydiaQuickInteractiveSystem,
|
||||
BandaiApplePippin,
|
||||
CommodoreAmigaCD32,
|
||||
CommodoreAmigaCDTV,
|
||||
MattelHyperscan,
|
||||
MicrosoftXBOX,
|
||||
MicrosoftXBOX360,
|
||||
MicrosoftXBOXOne,
|
||||
NECPCEngineTurboGrafxCD,
|
||||
NECPCFX,
|
||||
NintendoGameCube,
|
||||
NintendoWii,
|
||||
NintendoWiiU,
|
||||
Panasonic3DOInteractiveMultiplayer,
|
||||
PhilipsCDi,
|
||||
SegaCDMegaCD,
|
||||
SegaDreamcast,
|
||||
SegaSaturn,
|
||||
SNKNeoGeoCD,
|
||||
SonyPlayStation,
|
||||
SonyPlayStation2,
|
||||
SonyPlayStation3,
|
||||
SonyPlayStation4,
|
||||
SonyPlayStationPortable,
|
||||
VMLabsNuon,
|
||||
VTechVFlashVSmilePro,
|
||||
ZAPiTGamesGameWaveFamilyEntertainmentSystem,
|
||||
|
||||
#endregion
|
||||
|
||||
#region Computers
|
||||
|
||||
AcornArchimedes,
|
||||
AppleMacintosh,
|
||||
CommodoreAmigaCD,
|
||||
FujitsuFMTowns,
|
||||
IBMPCCompatible,
|
||||
NECPC88,
|
||||
NECPC98,
|
||||
SharpX68000,
|
||||
|
||||
#endregion
|
||||
|
||||
#region Arcade
|
||||
|
||||
NamcoSegaNintendoTriforce,
|
||||
NamcoSystem246,
|
||||
SegaChihiro,
|
||||
SegaLindbergh,
|
||||
SegaNaomi,
|
||||
SegaNaomi2,
|
||||
TABAustriaQuizard,
|
||||
TandyMemorexVisualInformationSystem,
|
||||
|
||||
#endregion
|
||||
|
||||
#region Other
|
||||
|
||||
AudioCD,
|
||||
BDVideo,
|
||||
DVDVideo,
|
||||
EnhancedCD,
|
||||
PalmOS,
|
||||
PhilipsCDiDigitalVideo,
|
||||
PhotoCD,
|
||||
PlayStationGameSharkUpdates,
|
||||
TaoiKTV,
|
||||
TomyKissSite,
|
||||
VideoCD,
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
2
LICENSE
2
LICENSE
@@ -652,7 +652,7 @@ Also add information on how to contact you by electronic and paper mail.
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
DICUI Copyright (C) 2018 ReignStumble
|
||||
MPF Copyright (C) 2018 ReignStumble
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
25
MPF.CLI/ConsoleLogger.cs
Normal file
25
MPF.CLI/ConsoleLogger.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using BinaryObjectScanner;
|
||||
using MPF.Frontend;
|
||||
|
||||
namespace MPF.CLI
|
||||
{
|
||||
public static class ConsoleLogger
|
||||
{
|
||||
/// <summary>
|
||||
/// Simple process counter to write to console
|
||||
/// </summary>
|
||||
public static void ProgressUpdated(object? sender, ResultEventArgs value)
|
||||
{
|
||||
Console.WriteLine(value.Message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Simple process counter to write to console
|
||||
/// </summary>
|
||||
public static void ProgressUpdated(object? sender, ProtectionProgress value)
|
||||
{
|
||||
Console.WriteLine($"{value.Percentage * 100:N2}%: {value.Filename} - {value.Protection}");
|
||||
}
|
||||
}
|
||||
}
|
||||
61
MPF.CLI/MPF.CLI.csproj
Normal file
61
MPF.CLI/MPF.CLI.csproj
Normal file
@@ -0,0 +1,61 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<!-- Assembly Properties -->
|
||||
<TargetFrameworks>net20;net35;net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0</TargetFrameworks>
|
||||
<OutputType>Exe</OutputType>
|
||||
<CheckEolTargetFramework>false</CheckEolTargetFramework>
|
||||
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<VersionPrefix>3.2.3</VersionPrefix>
|
||||
|
||||
<!-- Package Properties -->
|
||||
<Title>MPF CLI</Title>
|
||||
<Authors>Matt Nadareski;ReignStumble;Jakz</Authors>
|
||||
<Description>CLI frontend for various dumping programs</Description>
|
||||
<Copyright>Copyright (c) Matt Nadareski 2019-2024</Copyright>
|
||||
<PackageProjectUrl>https://github.com/SabreTools/</PackageProjectUrl>
|
||||
<RepositoryUrl>https://github.com/SabreTools/MPF</RepositoryUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Support All Frameworks -->
|
||||
<PropertyGroup Condition="$(TargetFramework.StartsWith(`net2`)) OR $(TargetFramework.StartsWith(`net3`)) OR $(TargetFramework.StartsWith(`net4`))">
|
||||
<RuntimeIdentifiers>win-x86;win-x64</RuntimeIdentifiers>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="$(TargetFramework.StartsWith(`netcoreapp`)) OR $(TargetFramework.StartsWith(`net5`))">
|
||||
<RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64</RuntimeIdentifiers>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="$(TargetFramework.StartsWith(`net6`)) OR $(TargetFramework.StartsWith(`net7`)) OR $(TargetFramework.StartsWith(`net8`))">
|
||||
<RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64;osx-arm64</RuntimeIdentifiers>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="$(RuntimeIdentifier.StartsWith(`osx-arm`))">
|
||||
<TargetFrameworks>net6.0;net7.0;net8.0</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MPF.Frontend\MPF.Frontend.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Support for old .NET versions -->
|
||||
<ItemGroup Condition="$(TargetFramework.StartsWith(`net2`)) OR $(TargetFramework.StartsWith(`net3`)) OR $(TargetFramework.StartsWith(`net40`))">
|
||||
<PackageReference Include="MinAsyncBridge" Version="0.12.4" />
|
||||
<PackageReference Include="MinTasksExtensionsBridge" Version="0.3.4" />
|
||||
<PackageReference Include="MinThreadingBridge" Version="0.11.4" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BinaryObjectScanner" PrivateAssets="build; analyzers" ExcludeAssets="contentFiles" Version="3.1.16" GeneratePathProperty="true">
|
||||
<IncludeAssets>runtime; compile; build; native; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="SabreTools.RedumpLib" Version="1.4.4" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="$(PkgBinaryObjectScanner)\content\**" PackagePath="contentFiles\any\any;content" CopyToOutputDirectory="Always" PackageCopyToOutput="true" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
350
MPF.CLI/Program.cs
Normal file
350
MPF.CLI/Program.cs
Normal file
@@ -0,0 +1,350 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
#if NET40
|
||||
using System.Threading.Tasks;
|
||||
#endif
|
||||
using BinaryObjectScanner;
|
||||
using MPF.Frontend;
|
||||
using MPF.Frontend.Tools;
|
||||
using SabreTools.RedumpLib.Data;
|
||||
using SabreTools.RedumpLib.Web;
|
||||
|
||||
namespace MPF.CLI
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
// Load options from the config file
|
||||
var options = OptionsLoader.LoadFromConfig();
|
||||
if (options.FirstRun)
|
||||
{
|
||||
// Application paths
|
||||
options.AaruPath = "FILL ME IN";
|
||||
options.DiscImageCreatorPath = "FILL ME IN";
|
||||
options.RedumperPath = "FILL ME IN";
|
||||
options.InternalProgram = InternalProgram.NONE;
|
||||
|
||||
// Reset first run
|
||||
options.FirstRun = false;
|
||||
OptionsLoader.SaveToConfig(options, saveDefault: true);
|
||||
}
|
||||
|
||||
// Try processing the standalone arguments
|
||||
bool? standaloneProcessed = OptionsLoader.ProcessStandaloneArguments(args);
|
||||
if (standaloneProcessed != false)
|
||||
{
|
||||
if (standaloneProcessed == null)
|
||||
DisplayHelp();
|
||||
return;
|
||||
}
|
||||
|
||||
// Try processing the common arguments
|
||||
bool success = OptionsLoader.ProcessCommonArguments(args, out MediaType mediaType, out RedumpSystem? knownSystem, out var error);
|
||||
if (!success)
|
||||
{
|
||||
DisplayHelp(error);
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate the supplied credentials
|
||||
bool? validated = RedumpClient.ValidateCredentials(options.RedumpUsername ?? string.Empty, options.RedumpPassword ?? string.Empty).GetAwaiter().GetResult();
|
||||
string message = validated switch
|
||||
{
|
||||
true => "Redump username and password accepted!",
|
||||
false => "Redump username and password denied!",
|
||||
null => "An error occurred validating your credentials!",
|
||||
};
|
||||
|
||||
if (!string.IsNullOrEmpty(message))
|
||||
Console.WriteLine(message);
|
||||
|
||||
// Process any custom parameters
|
||||
int startIndex = 2;
|
||||
CommandOptions opts = LoadFromArguments(args, options, ref startIndex);
|
||||
|
||||
// Validate the internal program
|
||||
switch (options.InternalProgram)
|
||||
{
|
||||
case InternalProgram.Aaru:
|
||||
if (!File.Exists(options.AaruPath))
|
||||
{
|
||||
DisplayHelp("A path needs to be supplied for Aaru, exiting...");
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case InternalProgram.DiscImageCreator:
|
||||
if (!File.Exists(options.DiscImageCreatorPath))
|
||||
{
|
||||
DisplayHelp("A path needs to be supplied for DIC, exiting...");
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case InternalProgram.Redumper:
|
||||
if (!File.Exists(options.RedumperPath))
|
||||
{
|
||||
DisplayHelp("A path needs to be supplied for Redumper, exiting...");
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
DisplayHelp($"{options.InternalProgram} is not a supported dumping program, exiting...");
|
||||
break;
|
||||
}
|
||||
|
||||
// Ensure we have the values we need
|
||||
if (opts.CustomParams == null && (opts.DevicePath == null || opts.FilePath == null))
|
||||
{
|
||||
DisplayHelp("Both a device path and file path need to be supplied, exiting...");
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the speed from the options
|
||||
int speed = opts.DriveSpeed ?? FrontendTool.GetDefaultSpeedForMediaType(mediaType, options);
|
||||
|
||||
// Populate an environment
|
||||
var drive = Drive.Create(null, opts.DevicePath ?? string.Empty);
|
||||
var env = new DumpEnvironment(options,
|
||||
opts.FilePath,
|
||||
drive,
|
||||
knownSystem,
|
||||
mediaType,
|
||||
options.InternalProgram,
|
||||
parameters: null);
|
||||
|
||||
// Process the parameters
|
||||
string? paramStr = opts.CustomParams ?? env.GetFullParameters(speed);
|
||||
if (string.IsNullOrEmpty(paramStr))
|
||||
{
|
||||
DisplayHelp("No valid environment could be created, exiting...");
|
||||
return;
|
||||
}
|
||||
env.SetExecutionContext(paramStr);
|
||||
|
||||
// Make new Progress objects
|
||||
var resultProgress = new Progress<ResultEventArgs>();
|
||||
resultProgress.ProgressChanged += ConsoleLogger.ProgressUpdated;
|
||||
var protectionProgress = new Progress<ProtectionProgress>();
|
||||
protectionProgress.ProgressChanged += ConsoleLogger.ProgressUpdated;
|
||||
|
||||
// Invoke the dumping program
|
||||
Console.WriteLine($"Invoking {options.InternalProgram} using '{paramStr}'");
|
||||
var dumpResult = env.Run(resultProgress).GetAwaiter().GetResult();
|
||||
Console.WriteLine(dumpResult.Message);
|
||||
if (!dumpResult)
|
||||
return;
|
||||
|
||||
// If it was not a dumping command
|
||||
if (!env.IsDumpingCommand())
|
||||
{
|
||||
Console.WriteLine("Execution not recognized as dumping command, skipping processing...");
|
||||
return;
|
||||
}
|
||||
|
||||
// If we have a mounted path, replace the environment
|
||||
if (opts.MountedPath != null && Directory.Exists(opts.MountedPath))
|
||||
{
|
||||
drive = Drive.Create(null, opts.MountedPath);
|
||||
env = new DumpEnvironment(options,
|
||||
opts.FilePath,
|
||||
drive,
|
||||
knownSystem,
|
||||
mediaType,
|
||||
internalProgram: null,
|
||||
parameters: null);
|
||||
}
|
||||
|
||||
// Finally, attempt to do the output dance
|
||||
#if NET40
|
||||
var verifyTask = env.VerifyAndSaveDumpOutput(resultProgress, protectionProgress);
|
||||
verifyTask.Wait();
|
||||
var verifyResult = verifyTask.Result;
|
||||
#else
|
||||
var verifyResult = env.VerifyAndSaveDumpOutput(resultProgress, protectionProgress).ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
#endif
|
||||
Console.WriteLine(verifyResult.Message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Display help for MPF.CLI
|
||||
/// </summary>
|
||||
/// <param name="error">Error string to prefix the help text with</param>
|
||||
private static void DisplayHelp(string? error = null)
|
||||
{
|
||||
if (error != null)
|
||||
Console.WriteLine(error);
|
||||
|
||||
Console.WriteLine("Usage:");
|
||||
Console.WriteLine("MPF.CLI <mediatype> <system> [options]");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Standalone Options:");
|
||||
Console.WriteLine("-h, -? Show this help text");
|
||||
Console.WriteLine("-lc, --listcodes List supported comment/content site codes");
|
||||
Console.WriteLine("-lm, --listmedia List supported media types");
|
||||
Console.WriteLine("-ls, --listsystems List supported system types");
|
||||
Console.WriteLine("-lp, --listprograms List supported dumping program outputs");
|
||||
Console.WriteLine();
|
||||
|
||||
Console.WriteLine("CLI Options:");
|
||||
Console.WriteLine("-u, --use <program> Override default dumping program");
|
||||
Console.WriteLine("-d, --device <devicepath> Physical drive path (Required if no custom parameters set)");
|
||||
Console.WriteLine("-m, --mounted <dirpath> Mounted filesystem path for additional checks");
|
||||
Console.WriteLine("-f, --file \"<filepath>\" Output file path (Required if no custom parameters set)");
|
||||
Console.WriteLine("-s, --speed <speed> Override default dumping speed");
|
||||
Console.WriteLine("-c, --custom \"<params>\" Custom parameters to use");
|
||||
Console.WriteLine();
|
||||
|
||||
Console.WriteLine("Custom dumping parameters, if used, will fully replace the default parameters.");
|
||||
Console.WriteLine("All dumping parameters need to be supplied if doing this.");
|
||||
Console.WriteLine("Otherwise, both a drive path and output file path are required.");
|
||||
Console.WriteLine();
|
||||
|
||||
Console.WriteLine("Mounted filesystem path is only recommended on OSes that require block");
|
||||
Console.WriteLine("device dumping, usually Linux and macOS.");
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load the current set of options from application arguments
|
||||
/// </summary>
|
||||
private static CommandOptions LoadFromArguments(string[] args, Frontend.Options options, ref int startIndex)
|
||||
{
|
||||
// Create return values
|
||||
var opts = new CommandOptions();
|
||||
|
||||
// If we have no arguments, just return
|
||||
if (args == null || args.Length == 0)
|
||||
{
|
||||
startIndex = 0;
|
||||
return opts;
|
||||
}
|
||||
|
||||
// If we have an invalid start index, just return
|
||||
if (startIndex < 0 || startIndex >= args.Length)
|
||||
return opts;
|
||||
|
||||
// Loop through the arguments and parse out values
|
||||
for (; startIndex < args.Length; startIndex++)
|
||||
{
|
||||
// Use specific program
|
||||
if (args[startIndex].StartsWith("-u=") || args[startIndex].StartsWith("--use="))
|
||||
{
|
||||
string internalProgram = args[startIndex].Split('=')[1];
|
||||
options.InternalProgram = Frontend.Options.ToInternalProgram(internalProgram);
|
||||
}
|
||||
else if (args[startIndex] == "-u" || args[startIndex] == "--use")
|
||||
{
|
||||
string internalProgram = args[startIndex + 1];
|
||||
options.InternalProgram = Frontend.Options.ToInternalProgram(internalProgram);
|
||||
startIndex++;
|
||||
}
|
||||
|
||||
// Use a device path
|
||||
else if (args[startIndex].StartsWith("-d=") || args[startIndex].StartsWith("--device="))
|
||||
{
|
||||
opts.DevicePath = args[startIndex].Split('=')[1].Trim('"');
|
||||
}
|
||||
else if (args[startIndex] == "-d" || args[startIndex] == "--device")
|
||||
{
|
||||
opts.DevicePath = args[startIndex + 1].Trim('"');
|
||||
startIndex++;
|
||||
}
|
||||
|
||||
// Use a mounted path for physical checks
|
||||
else if (args[startIndex].StartsWith("-m=") || args[startIndex].StartsWith("--mounted="))
|
||||
{
|
||||
opts.MountedPath = args[startIndex].Split('=')[1];
|
||||
}
|
||||
else if (args[startIndex] == "-m" || args[startIndex] == "--mounted")
|
||||
{
|
||||
opts.MountedPath = args[startIndex + 1];
|
||||
startIndex++;
|
||||
}
|
||||
|
||||
// Use a file path
|
||||
else if (args[startIndex].StartsWith("-f=") || args[startIndex].StartsWith("--file="))
|
||||
{
|
||||
opts.FilePath = args[startIndex].Split('=')[1].Trim('"');
|
||||
}
|
||||
else if (args[startIndex] == "-f" || args[startIndex] == "--file")
|
||||
{
|
||||
opts.FilePath = args[startIndex + 1].Trim('"');
|
||||
startIndex++;
|
||||
}
|
||||
|
||||
// Set an override speed
|
||||
else if (args[startIndex].StartsWith("-s=") || args[startIndex].StartsWith("--speed="))
|
||||
{
|
||||
if (!int.TryParse(args[startIndex].Split('=')[1].Trim('"'), out int speed))
|
||||
speed = -1;
|
||||
|
||||
opts.DriveSpeed = speed;
|
||||
}
|
||||
else if (args[startIndex] == "-s" || args[startIndex] == "--speed")
|
||||
{
|
||||
if (!int.TryParse(args[startIndex + 1].Trim('"'), out int speed))
|
||||
speed = -1;
|
||||
|
||||
opts.DriveSpeed = speed;
|
||||
startIndex++;
|
||||
}
|
||||
|
||||
// Use a custom parameters
|
||||
else if (args[startIndex].StartsWith("-c=") || args[startIndex].StartsWith("--custom="))
|
||||
{
|
||||
opts.CustomParams = args[startIndex].Split('=')[1].Trim('"');
|
||||
}
|
||||
else if (args[startIndex] == "-c" || args[startIndex] == "--custom")
|
||||
{
|
||||
opts.CustomParams = args[startIndex + 1].Trim('"');
|
||||
startIndex++;
|
||||
}
|
||||
|
||||
// Default, we fall out
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return opts;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents commandline options
|
||||
/// </summary>
|
||||
private class CommandOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Path to the device to dump
|
||||
/// </summary>
|
||||
/// <remarks>Required if custom parameters are not set</remarks>
|
||||
public string? DevicePath { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Path to the mounted filesystem to check
|
||||
/// </summary>
|
||||
/// <remarks>Should only be used when the device path is not readable</remarks>
|
||||
public string? MountedPath { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Path to the output file
|
||||
/// </summary>
|
||||
/// <remarks>Required if custom parameters are not set</remarks>
|
||||
public string? FilePath { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Override drive speed
|
||||
/// </summary>
|
||||
public int? DriveSpeed { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Custom parameters for dumping
|
||||
/// </summary>
|
||||
public string? CustomParams { get; set; } = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
6
MPF.Check/App.config
Normal file
6
MPF.Check/App.config
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.2"/>
|
||||
</startup>
|
||||
</configuration>
|
||||
25
MPF.Check/ConsoleLogger.cs
Normal file
25
MPF.Check/ConsoleLogger.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using BinaryObjectScanner;
|
||||
using MPF.Frontend;
|
||||
|
||||
namespace MPF.Check
|
||||
{
|
||||
public static class ConsoleLogger
|
||||
{
|
||||
/// <summary>
|
||||
/// Simple process counter to write to console
|
||||
/// </summary>
|
||||
public static void ProgressUpdated(object? sender, ResultEventArgs value)
|
||||
{
|
||||
Console.WriteLine(value.Message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Simple process counter to write to console
|
||||
/// </summary>
|
||||
public static void ProgressUpdated(object? sender, ProtectionProgress value)
|
||||
{
|
||||
Console.WriteLine($"{value.Percentage * 100:N2}%: {value.Filename} - {value.Protection}");
|
||||
}
|
||||
}
|
||||
}
|
||||
65
MPF.Check/MPF.Check.csproj
Normal file
65
MPF.Check/MPF.Check.csproj
Normal file
@@ -0,0 +1,65 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<!-- Assembly Properties -->
|
||||
<TargetFrameworks>net20;net35;net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0</TargetFrameworks>
|
||||
<OutputType>Exe</OutputType>
|
||||
<CheckEolTargetFramework>false</CheckEolTargetFramework>
|
||||
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<VersionPrefix>3.2.3</VersionPrefix>
|
||||
|
||||
<!-- Package Properties -->
|
||||
<Title>MPF Check</Title>
|
||||
<Authors>Matt Nadareski;ReignStumble;Jakz</Authors>
|
||||
<Description>Validator for various dumping programs</Description>
|
||||
<Copyright>Copyright (c) Matt Nadareski 2019-2024</Copyright>
|
||||
<PackageProjectUrl>https://github.com/SabreTools/</PackageProjectUrl>
|
||||
<RepositoryUrl>https://github.com/SabreTools/MPF</RepositoryUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Support All Frameworks -->
|
||||
<PropertyGroup Condition="$(TargetFramework.StartsWith(`net2`)) OR $(TargetFramework.StartsWith(`net3`)) OR $(TargetFramework.StartsWith(`net4`))">
|
||||
<RuntimeIdentifiers>win-x86;win-x64</RuntimeIdentifiers>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="$(TargetFramework.StartsWith(`netcoreapp`)) OR $(TargetFramework.StartsWith(`net5`))">
|
||||
<RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64</RuntimeIdentifiers>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="$(TargetFramework.StartsWith(`net6`)) OR $(TargetFramework.StartsWith(`net7`)) OR $(TargetFramework.StartsWith(`net8`))">
|
||||
<RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64;osx-arm64</RuntimeIdentifiers>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="$(RuntimeIdentifier.StartsWith(`osx-arm`))">
|
||||
<TargetFrameworks>net6.0;net7.0;net8.0</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="App.config" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MPF.Frontend\MPF.Frontend.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Support for old .NET versions -->
|
||||
<ItemGroup Condition="$(TargetFramework.StartsWith(`net2`)) OR $(TargetFramework.StartsWith(`net3`)) OR $(TargetFramework.StartsWith(`net40`))">
|
||||
<PackageReference Include="MinAsyncBridge" Version="0.12.4" />
|
||||
<PackageReference Include="MinTasksExtensionsBridge" Version="0.3.4" />
|
||||
<PackageReference Include="MinThreadingBridge" Version="0.11.4" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BinaryObjectScanner" PrivateAssets="build; analyzers" ExcludeAssets="contentFiles" Version="3.1.16" GeneratePathProperty="true">
|
||||
<IncludeAssets>runtime; compile; build; native; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="SabreTools.RedumpLib" Version="1.4.4" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="$(PkgBinaryObjectScanner)\content\**" PackagePath="contentFiles\any\any;content" CopyToOutputDirectory="Always" PackageCopyToOutput="true" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
350
MPF.Check/Program.cs
Normal file
350
MPF.Check/Program.cs
Normal file
@@ -0,0 +1,350 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
#if NET40
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
#endif
|
||||
using BinaryObjectScanner;
|
||||
using MPF.Frontend;
|
||||
using MPF.Frontend.Tools;
|
||||
using SabreTools.RedumpLib;
|
||||
using SabreTools.RedumpLib.Data;
|
||||
using SabreTools.RedumpLib.Web;
|
||||
|
||||
namespace MPF.Check
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
// Create a default options object
|
||||
var options = new Frontend.Options()
|
||||
{
|
||||
// Internal Program
|
||||
InternalProgram = InternalProgram.NONE,
|
||||
|
||||
// Extra Dumping Options
|
||||
ScanForProtection = false,
|
||||
AddPlaceholders = true,
|
||||
PullAllInformation = false,
|
||||
AddFilenameSuffix = false,
|
||||
OutputSubmissionJSON = false,
|
||||
IncludeArtifacts = false,
|
||||
CompressLogFiles = false,
|
||||
DeleteUnnecessaryFiles = false,
|
||||
CreateIRDAfterDumping = false,
|
||||
|
||||
// Protection Scanning Options
|
||||
ScanArchivesForProtection = true,
|
||||
ScanPackersForProtection = false,
|
||||
IncludeDebugProtectionInformation = false,
|
||||
HideDriveLetters = false,
|
||||
|
||||
// Redump Login Information
|
||||
RedumpUsername = null,
|
||||
RedumpPassword = null,
|
||||
};
|
||||
|
||||
// Try processing the standalone arguments
|
||||
bool? standaloneProcessed = OptionsLoader.ProcessStandaloneArguments(args);
|
||||
if (standaloneProcessed != false)
|
||||
{
|
||||
if (standaloneProcessed == null)
|
||||
DisplayHelp();
|
||||
return;
|
||||
}
|
||||
|
||||
// Try processing the common arguments
|
||||
bool success = OptionsLoader.ProcessCommonArguments(args, out MediaType mediaType, out RedumpSystem? knownSystem, out var error);
|
||||
if (!success)
|
||||
{
|
||||
DisplayHelp(error);
|
||||
return;
|
||||
}
|
||||
|
||||
// Loop through and process options
|
||||
int startIndex = 2;
|
||||
CommandOptions opts = LoadFromArguments(args, options, ref startIndex);
|
||||
if (options.InternalProgram == InternalProgram.NONE)
|
||||
{
|
||||
DisplayHelp("A program name needs to be provided");
|
||||
return;
|
||||
}
|
||||
|
||||
// Make new Progress objects
|
||||
var resultProgress = new Progress<ResultEventArgs>();
|
||||
resultProgress.ProgressChanged += ConsoleLogger.ProgressUpdated;
|
||||
var protectionProgress = new Progress<ProtectionProgress>();
|
||||
protectionProgress.ProgressChanged += ConsoleLogger.ProgressUpdated;
|
||||
|
||||
// Validate the supplied credentials
|
||||
bool? validated = RedumpClient.ValidateCredentials(options.RedumpUsername ?? string.Empty, options.RedumpPassword ?? string.Empty).GetAwaiter().GetResult();
|
||||
string message = validated switch
|
||||
{
|
||||
true => "Redump username and password accepted!",
|
||||
false => "Redump username and password denied!",
|
||||
null => "An error occurred validating your credentials!",
|
||||
};
|
||||
|
||||
if (!string.IsNullOrEmpty(message))
|
||||
Console.WriteLine(message);
|
||||
|
||||
// Loop through all the rest of the args
|
||||
for (int i = startIndex; i < args.Length; i++)
|
||||
{
|
||||
// Check for a file
|
||||
if (!File.Exists(args[i].Trim('"')))
|
||||
{
|
||||
DisplayHelp($"{args[i].Trim('"')} does not exist");
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the full file path
|
||||
string filepath = Path.GetFullPath(args[i].Trim('"'));
|
||||
|
||||
// Now populate an environment
|
||||
Drive? drive = null;
|
||||
if (!string.IsNullOrEmpty(opts.DevicePath))
|
||||
drive = Drive.Create(null, opts.DevicePath!);
|
||||
|
||||
var env = new DumpEnvironment(options, filepath, drive, knownSystem, mediaType, internalProgram: null, parameters: null);
|
||||
|
||||
// Finally, attempt to do the output dance
|
||||
var result = env.VerifyAndSaveDumpOutput(resultProgress, protectionProgress, seedInfo: opts.Seed).GetAwaiter().GetResult();
|
||||
Console.WriteLine(result.Message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Display help for MPF.Check
|
||||
/// </summary>
|
||||
/// <param name="error">Error string to prefix the help text with</param>
|
||||
private static void DisplayHelp(string? error = null)
|
||||
{
|
||||
if (error != null)
|
||||
Console.WriteLine(error);
|
||||
|
||||
Console.WriteLine("Usage:");
|
||||
Console.WriteLine("MPF.Check <mediatype> <system> [options] </path/to/output.cue/iso> ...");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Standalone Options:");
|
||||
Console.WriteLine("-h, -? Show this help text");
|
||||
Console.WriteLine("-lc, --listcodes List supported comment/content site codes");
|
||||
Console.WriteLine("-lm, --listmedia List supported media types");
|
||||
Console.WriteLine("-ls, --listsystems List supported system types");
|
||||
Console.WriteLine("-lp, --listprograms List supported dumping program outputs");
|
||||
Console.WriteLine();
|
||||
|
||||
Console.WriteLine("Check Options:");
|
||||
Console.WriteLine("-u, --use <program> Dumping program output type [REQUIRED]");
|
||||
Console.WriteLine(" --load-seed <path> Load a seed submission JSON for user information");
|
||||
Console.WriteLine(" --no-placeholders Disable placeholder values in submission info");
|
||||
Console.WriteLine(" --create-ird Create IRD from output files (PS3 only)");
|
||||
Console.WriteLine("-c, --credentials <user> <pw> Redump username and password");
|
||||
Console.WriteLine(" --pull-all Pull all information from Redump (requires --credentials)");
|
||||
Console.WriteLine("-p, --path <drivepath> Physical drive path for additional checks");
|
||||
Console.WriteLine("-s, --scan Enable copy protection scan (requires --path)");
|
||||
Console.WriteLine(" --disable-archives Disable scanning archives (requires --scan)");
|
||||
Console.WriteLine(" --enable-packers Enable scanning for packers (requires --scan)");
|
||||
Console.WriteLine(" --enable-debug Enable debug protection information (requires --scan)");
|
||||
Console.WriteLine(" --hide-drive-letters Hide drive letters from scan output (requires --scan)");
|
||||
Console.WriteLine("-x, --suffix Enable adding filename suffix");
|
||||
Console.WriteLine("-j, --json Enable submission JSON output");
|
||||
Console.WriteLine(" --include-artifacts Include artifacts in JSON (requires --json)");
|
||||
Console.WriteLine("-z, --zip Enable log file compression");
|
||||
Console.WriteLine("-d, --delete Enable unnecessary file deletion");
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load the current set of options from application arguments
|
||||
/// </summary>
|
||||
private static CommandOptions LoadFromArguments(string[] args, Frontend.Options options, ref int startIndex)
|
||||
{
|
||||
// Create return values
|
||||
var opts = new CommandOptions();
|
||||
|
||||
// These values require multiple parts to be active
|
||||
bool scan = false,
|
||||
enableArchives = true,
|
||||
enablePackers = false,
|
||||
enableDebug = false,
|
||||
hideDriveLetters = false;
|
||||
|
||||
// If we have no arguments, just return
|
||||
if (args == null || args.Length == 0)
|
||||
{
|
||||
startIndex = 0;
|
||||
return opts;
|
||||
}
|
||||
|
||||
// If we have an invalid start index, just return
|
||||
if (startIndex < 0 || startIndex >= args.Length)
|
||||
return opts;
|
||||
|
||||
// Loop through the arguments and parse out values
|
||||
for (; startIndex < args.Length; startIndex++)
|
||||
{
|
||||
// Use specific program
|
||||
if (args[startIndex].StartsWith("-u=") || args[startIndex].StartsWith("--use="))
|
||||
{
|
||||
string internalProgram = args[startIndex].Split('=')[1];
|
||||
options.InternalProgram = Frontend.Options.ToInternalProgram(internalProgram);
|
||||
}
|
||||
else if (args[startIndex] == "-u" || args[startIndex] == "--use")
|
||||
{
|
||||
string internalProgram = args[startIndex + 1];
|
||||
options.InternalProgram = Frontend.Options.ToInternalProgram(internalProgram);
|
||||
startIndex++;
|
||||
}
|
||||
|
||||
// Include seed info file
|
||||
else if (args[startIndex].StartsWith("--load-seed="))
|
||||
{
|
||||
string seedInfo = args[startIndex].Split('=')[1];
|
||||
opts.Seed = Builder.CreateFromFile(seedInfo);
|
||||
}
|
||||
else if (args[startIndex] == "--load-seed")
|
||||
{
|
||||
string seedInfo = args[startIndex + 1];
|
||||
opts.Seed = Builder.CreateFromFile(seedInfo);
|
||||
startIndex++;
|
||||
}
|
||||
|
||||
// Disable placeholder values in submission info
|
||||
else if (args[startIndex].Equals("--no-placeholders"))
|
||||
{
|
||||
options.AddPlaceholders = false;
|
||||
}
|
||||
|
||||
// Create IRD from output files (PS3 only)
|
||||
else if (args[startIndex].Equals("--create-ird"))
|
||||
{
|
||||
options.CreateIRDAfterDumping = true;
|
||||
}
|
||||
|
||||
// Redump login
|
||||
else if (args[startIndex].StartsWith("-c=") || args[startIndex].StartsWith("--credentials="))
|
||||
{
|
||||
string[] credentials = args[startIndex].Split('=')[1].Split(';');
|
||||
options.RedumpUsername = credentials[0];
|
||||
options.RedumpPassword = credentials[1];
|
||||
}
|
||||
else if (args[startIndex] == "-c" || args[startIndex] == "--credentials")
|
||||
{
|
||||
options.RedumpUsername = args[startIndex + 1];
|
||||
options.RedumpPassword = args[startIndex + 2];
|
||||
startIndex += 2;
|
||||
}
|
||||
|
||||
// Pull all information (requires Redump login)
|
||||
else if (args[startIndex].Equals("--pull-all"))
|
||||
{
|
||||
options.PullAllInformation = true;
|
||||
}
|
||||
|
||||
// Use a device path for physical checks
|
||||
else if (args[startIndex].StartsWith("-p=") || args[startIndex].StartsWith("--path="))
|
||||
{
|
||||
opts.DevicePath = args[startIndex].Split('=')[1];
|
||||
}
|
||||
else if (args[startIndex] == "-p" || args[startIndex] == "--path")
|
||||
{
|
||||
opts.DevicePath = args[startIndex + 1];
|
||||
startIndex++;
|
||||
}
|
||||
|
||||
// Scan for protection (requires device path)
|
||||
else if (args[startIndex].Equals("-s") || args[startIndex].Equals("--scan"))
|
||||
{
|
||||
scan = true;
|
||||
}
|
||||
|
||||
// Disable scanning archives (requires --scan)
|
||||
else if (args[startIndex].Equals("--disable-archives"))
|
||||
{
|
||||
enableArchives = false;
|
||||
}
|
||||
|
||||
// Enable scanning for packers (requires --scan)
|
||||
else if (args[startIndex].Equals("--enable-packers"))
|
||||
{
|
||||
enablePackers = true;
|
||||
}
|
||||
|
||||
// Enable debug protection information (requires --scan)
|
||||
else if (args[startIndex].Equals("--enable-debug"))
|
||||
{
|
||||
enableDebug = true;
|
||||
}
|
||||
|
||||
// Hide drive letters from scan output (requires --scan)
|
||||
else if (args[startIndex].Equals("--hide-drive-letters"))
|
||||
{
|
||||
hideDriveLetters = true;
|
||||
}
|
||||
|
||||
// Add filename suffix
|
||||
else if (args[startIndex].Equals("-x") || args[startIndex].Equals("--suffix"))
|
||||
{
|
||||
options.AddFilenameSuffix = true;
|
||||
}
|
||||
|
||||
// Output submission JSON
|
||||
else if (args[startIndex].Equals("-j") || args[startIndex].Equals("--json"))
|
||||
{
|
||||
options.OutputSubmissionJSON = true;
|
||||
}
|
||||
|
||||
// Output submission JSON
|
||||
else if (args[startIndex].Equals("--include-artifacts"))
|
||||
{
|
||||
options.IncludeArtifacts = true;
|
||||
}
|
||||
|
||||
// Compress log and extraneous files
|
||||
else if (args[startIndex].Equals("-z") || args[startIndex].Equals("--zip"))
|
||||
{
|
||||
options.CompressLogFiles = true;
|
||||
}
|
||||
|
||||
// Delete unnecessary files files
|
||||
else if (args[startIndex].Equals("-d") || args[startIndex].Equals("--delete"))
|
||||
{
|
||||
options.DeleteUnnecessaryFiles = true;
|
||||
}
|
||||
|
||||
// Default, we fall out
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Now deal with the complex options
|
||||
options.ScanForProtection = scan && !string.IsNullOrEmpty(opts.DevicePath);
|
||||
options.ScanArchivesForProtection = enableArchives && scan && !string.IsNullOrEmpty(opts.DevicePath);
|
||||
options.ScanPackersForProtection = enablePackers && scan && !string.IsNullOrEmpty(opts.DevicePath);
|
||||
options.IncludeDebugProtectionInformation = enableDebug && scan && !string.IsNullOrEmpty(opts.DevicePath);
|
||||
options.HideDriveLetters = hideDriveLetters && scan && !string.IsNullOrEmpty(opts.DevicePath);
|
||||
|
||||
return opts;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents commandline options
|
||||
/// </summary>
|
||||
private class CommandOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Seed submission info from an input file
|
||||
/// </summary>
|
||||
public SubmissionInfo? Seed { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Path to the device to scan
|
||||
/// </summary>
|
||||
public string? DevicePath { get; set; } = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
7
MPF.Check/launchSettings.json
Normal file
7
MPF.Check/launchSettings.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"profiles": {
|
||||
"MPF.Check": {
|
||||
"commandName": "Project"
|
||||
}
|
||||
}
|
||||
}
|
||||
68
MPF.ExecutionContexts/Aaru/CommandStrings.cs
Normal file
68
MPF.ExecutionContexts/Aaru/CommandStrings.cs
Normal file
@@ -0,0 +1,68 @@
|
||||
namespace MPF.ExecutionContexts.Aaru
|
||||
{
|
||||
/// <summary>
|
||||
/// Top-level commands for Aaru
|
||||
/// </summary>
|
||||
public static class CommandStrings
|
||||
{
|
||||
public const string NONE = "";
|
||||
|
||||
// Archive Family
|
||||
public const string ArchivePrefixShort = "arc";
|
||||
public const string ArchivePrefixLong = "archive";
|
||||
public const string ArchiveInfo = "info";
|
||||
|
||||
// Database Family
|
||||
public const string DatabasePrefixShort = "db";
|
||||
public const string DatabasePrefixLong = "database";
|
||||
public const string DatabaseStats = "stats";
|
||||
public const string DatabaseUpdate = "update";
|
||||
|
||||
// Device Family
|
||||
public const string DevicePrefixShort = "dev";
|
||||
public const string DevicePrefixLong = "device";
|
||||
public const string DeviceInfo = "info";
|
||||
public const string DeviceList = "list";
|
||||
public const string DeviceReport = "report";
|
||||
|
||||
// Filesystem Family
|
||||
public const string FilesystemPrefixShort = "fi";
|
||||
public const string FilesystemPrefixShortAlt = "fs";
|
||||
public const string FilesystemPrefixLong = "filesystem";
|
||||
public const string FilesystemExtract = "extract";
|
||||
public const string FilesystemInfo = "info";
|
||||
public const string FilesystemListShort = "ls";
|
||||
public const string FilesystemListLong = "list";
|
||||
public const string FilesystemOptions = "options";
|
||||
|
||||
// Image Family
|
||||
public const string ImagePrefixShort = "i";
|
||||
public const string ImagePrefixLong = "image";
|
||||
public const string ImageChecksumShort = "chk";
|
||||
public const string ImageChecksumLong = "checksum";
|
||||
public const string ImageCompareShort = "cmp";
|
||||
public const string ImageCompareLong = "compare";
|
||||
public const string ImageConvert = "convert";
|
||||
public const string ImageCreateSidecar = "create-sidecar";
|
||||
public const string ImageDecode = "decode";
|
||||
public const string ImageEntropy = "entropy";
|
||||
public const string ImageInfo = "info";
|
||||
public const string ImageOptions = "options";
|
||||
public const string ImagePrint = "print";
|
||||
public const string ImageVerify = "verify";
|
||||
|
||||
// Media Family
|
||||
public const string MediaPrefixShort = "m";
|
||||
public const string MediaPrefixLong = "media";
|
||||
public const string MediaDump = "dump";
|
||||
public const string MediaInfo = "info";
|
||||
public const string MediaScan = "scan";
|
||||
|
||||
// Standalone Commands
|
||||
public const string Configure = "configure";
|
||||
public const string Formats = "formats";
|
||||
public const string ListEncodings = "list-encodings";
|
||||
public const string ListNamespaces = "list-namespaces";
|
||||
public const string Remote = "remote";
|
||||
}
|
||||
}
|
||||
22
MPF.ExecutionContexts/Aaru/Converters.cs
Normal file
22
MPF.ExecutionContexts/Aaru/Converters.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using SabreTools.RedumpLib.Data;
|
||||
|
||||
namespace MPF.ExecutionContexts.Aaru
|
||||
{
|
||||
public static class Converters
|
||||
{
|
||||
#region Cross-enumeration conversions
|
||||
|
||||
/// <summary>
|
||||
/// Get the default extension for a given disc type
|
||||
/// </summary>
|
||||
/// <param name="type">MediaType value to check</param>
|
||||
/// <returns>Valid extension (with leading '.'), null on error</returns>
|
||||
public static string Extension(MediaType? type)
|
||||
{
|
||||
// Aaru has a single, unified output format by default
|
||||
return ".aaruf";
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
43
MPF.ExecutionContexts/Aaru/EncodingStrings.cs
Normal file
43
MPF.ExecutionContexts/Aaru/EncodingStrings.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
namespace MPF.ExecutionContexts.Aaru
|
||||
{
|
||||
/// <summary>
|
||||
/// Supported encodings for Aaru
|
||||
/// </summary>
|
||||
/// TODO: Use to verify encoding settings
|
||||
public static class EncodingStrings
|
||||
{
|
||||
public const string ArabicMac = "x-mac-arabic";
|
||||
public const string AtariASCII = "atascii";
|
||||
public const string CentralEuropeanMac = "x-mac-ce";
|
||||
public const string CommodorePETSCII = "petscii";
|
||||
public const string CroatianMac = "x-mac-croatian";
|
||||
public const string CyrillicMac = "x-mac-cryillic";
|
||||
public const string FarsiMac = "x-mac-farsi";
|
||||
public const string GreekMac = "x-mac-greek";
|
||||
public const string HebrewMac = "x-mac-hebrew";
|
||||
public const string RomanianMac = "x-mac-romanian";
|
||||
public const string SinclairZXSpectrum = "spectrum";
|
||||
public const string SinclairZX80 = "zx80";
|
||||
public const string SinclairZX81 = "zx81";
|
||||
public const string TurkishMac = "x-mac-turkish";
|
||||
public const string UkrainianMac = "x-mac-ukrainian";
|
||||
public const string Unicode = "utf-16";
|
||||
public const string UnicodeBigEndian = "utf-16BE";
|
||||
public const string UnicodeUTF32BigEndian = "utf-32BE";
|
||||
public const string UnicodeUTF32 = "utf-32";
|
||||
public const string UnicodeUTF7 = "utf-7";
|
||||
public const string UnicodeUTF8 = "utf-8";
|
||||
public const string USASCII = "us-ascii";
|
||||
public const string WesternEuropeanAppleII = "apple2";
|
||||
public const string WesternEuropeanAppleIIc = "apple2c";
|
||||
public const string WesternEuropeanAppleIIe = "apple2e";
|
||||
public const string WesternEuropeanAppleIIgs = "apple2gs";
|
||||
public const string WesternEuropeanAppleLisa = "lisa";
|
||||
public const string WesternEuropeanAtariST = "atarist";
|
||||
public const string WesternEuropeanGEM = "gem";
|
||||
public const string WesternEuropeanGEOS = "geos";
|
||||
public const string WesternEuropeanISO = "iso-8859-1";
|
||||
public const string WesternEuropeanMac = "macintosh";
|
||||
public const string WesternEuropeanRadix50 = "radix50";
|
||||
}
|
||||
}
|
||||
1963
MPF.ExecutionContexts/Aaru/ExecutionContext.cs
Normal file
1963
MPF.ExecutionContexts/Aaru/ExecutionContext.cs
Normal file
File diff suppressed because it is too large
Load Diff
151
MPF.ExecutionContexts/Aaru/FlagStrings.cs
Normal file
151
MPF.ExecutionContexts/Aaru/FlagStrings.cs
Normal file
@@ -0,0 +1,151 @@
|
||||
namespace MPF.ExecutionContexts.Aaru
|
||||
{
|
||||
/// <summary>
|
||||
/// Dumping flags for Aaru
|
||||
/// </summary>
|
||||
public static class FlagStrings
|
||||
{
|
||||
// Boolean flags
|
||||
public const string Adler32Short = "-a";
|
||||
public const string Adler32Long = "--adler32";
|
||||
public const string ClearLong = "--clear";
|
||||
public const string ClearAllLong = "--clear-all";
|
||||
public const string CRC16Long = "--crc16";
|
||||
public const string CRC32Short = "-c";
|
||||
public const string CRC32Long = "--crc32";
|
||||
public const string CRC64Long = "--crc64";
|
||||
public const string DebugShort = "-d";
|
||||
public const string DebugLong = "--debug";
|
||||
public const string DiskTagsShort = "-f";
|
||||
public const string DiskTagsLong = "--disk-tags";
|
||||
public const string DuplicatedSectorsShort = "-p";
|
||||
public const string DuplicatedSectorsLong = "--duplicated-sectors";
|
||||
public const string EjectLong = "--eject";
|
||||
public const string ExtendedAttributesShort = "-x";
|
||||
public const string ExtendedAttributesLong = "--xattrs";
|
||||
public const string FilesystemsShort = "-f";
|
||||
public const string FilesystemsLong = "--filesystems";
|
||||
public const string FirstPregapLong = "--first-pregap";
|
||||
public const string FixOffsetLong = "--fix-offset";
|
||||
public const string FixSubchannelLong = "--fix-subchannel";
|
||||
public const string FixSubchannelCrcLong = "--fix-subchannel-crc";
|
||||
public const string FixSubchannelPositionLong = "--fix-subchannel-position";
|
||||
public const string Fletcher16Long = "--fletcher16";
|
||||
public const string Fletcher32Long = "--fletcher32";
|
||||
public const string ForceShort = "-f";
|
||||
public const string ForceLong = "--force";
|
||||
public const string GenerateSubchannelsLong = "--generate-subchannels";
|
||||
public const string HelpShort = "-h";
|
||||
public const string HelpShortAlt = "-?";
|
||||
public const string HelpLong = "--help";
|
||||
public const string LongFormatShort = "-l";
|
||||
public const string LongFormatLong = "--long-format";
|
||||
public const string LongSectorsShort = "-r";
|
||||
public const string LongSectorsLong = "--long-sectors";
|
||||
public const string MD5Short = "-m";
|
||||
public const string MD5Long = "--md5";
|
||||
public const string MetadataLong = "--metadata";
|
||||
public const string PartitionsShort = "-p";
|
||||
public const string PartitionsLong = "--partitions";
|
||||
public const string PauseLong = "--pause";
|
||||
public const string PersistentLong = "--persistent";
|
||||
public const string PrivateLong = "--private";
|
||||
public const string ResumeShort = "-r";
|
||||
public const string ResumeLong = "--resume";
|
||||
public const string RetrySubchannelLong = "--retry-subchannel";
|
||||
public const string SectorTagsShort = "-p";
|
||||
public const string SectorTagsLong = "--sector-tags";
|
||||
public const string SeparatedTracksShort = "-t";
|
||||
public const string SeparatedTracksLong = "--separated-tracks";
|
||||
public const string SHA1Short = "-s";
|
||||
public const string SHA1Long = "--sha1";
|
||||
public const string SHA256Long = "--sha256";
|
||||
public const string SHA384Long = "--sha384";
|
||||
public const string SHA512Long = "--sha512";
|
||||
public const string SkipCdiReadyHoleLong = "--skip-cdiready-hole";
|
||||
public const string SpamSumShort = "-f";
|
||||
public const string SpamSumLong = "--spamsum";
|
||||
public const string StopOnErrorShort = "-s";
|
||||
public const string StopOnErrorLong = "--stop-on-error";
|
||||
public const string StoreEncryptedLong = "--store-encrypted";
|
||||
public const string TapeShort = "-t";
|
||||
public const string TapeLong = "--tape";
|
||||
public const string TitleKeysLong = "--title-keys";
|
||||
public const string TrapDiscShort = "-t";
|
||||
public const string TrapDiscLong = "--trap-disc";
|
||||
public const string TrimLong = "--trim";
|
||||
public const string UseBufferedReadsLong = "--use-buffered-reads";
|
||||
public const string VerboseShort = "-v";
|
||||
public const string VerboseLong = "--verbose";
|
||||
public const string VerifyDiscShort = "-w";
|
||||
public const string VerifyDiscLong = "--verify-disc";
|
||||
public const string VerifySectorsShort = "-s";
|
||||
public const string VerifySectorsLong = "--verify-sectors";
|
||||
public const string VersionLong = "--version";
|
||||
public const string WholeDiscShort = "-w";
|
||||
public const string WholeDiscLong = "--whole-disc";
|
||||
|
||||
// Int8 flags
|
||||
public const string SpeedLong = "--speed";
|
||||
|
||||
// Int16 flags
|
||||
public const string RetryPassesShort = "-p";
|
||||
public const string RetryPassesLong = "--retry-passes";
|
||||
public const string WidthShort = "-w";
|
||||
public const string WidthLong = "--width";
|
||||
|
||||
// Int32 flags
|
||||
public const string BlockSizeShort = "-b";
|
||||
public const string BlockSizeLong = "--block-size";
|
||||
public const string CountShort = "-c";
|
||||
public const string CountLong = "--count";
|
||||
public const string MaxBlocksLong = "--max-blocks";
|
||||
public const string MediaLastSequenceLong = "--media-lastsequence";
|
||||
public const string MediaSequenceLong = "--media-sequence";
|
||||
public const string SkipShort = "-k";
|
||||
public const string SkipLong = "--skip";
|
||||
|
||||
// Int64 flags
|
||||
public const string LengthShort = "-l"; // or "all"
|
||||
public const string LengthLong = "--length"; // or "all"
|
||||
public const string StartShort = "-s";
|
||||
public const string StartLong = "--start";
|
||||
|
||||
// String flags
|
||||
public const string CommentsLong = "--comments";
|
||||
public const string CreatorLong = "--creator";
|
||||
public const string DriveManufacturerLong = "--drive-manufacturer";
|
||||
public const string DriveModelLong = "--drive-model";
|
||||
public const string DriveRevisionLong = "--drive-revision";
|
||||
public const string DriveSerialLong = "--drive-serial";
|
||||
public const string EncodingShort = "-e";
|
||||
public const string EncodingLong = "--encoding";
|
||||
public const string FormatConvertShort = "-p";
|
||||
public const string FormatConvertLong = "--format";
|
||||
public const string FormatDumpShort = "-t";
|
||||
public const string FormatDumpLong = "--format";
|
||||
public const string GeometryShort = "-g";
|
||||
public const string GeometryLong = "--geometry";
|
||||
public const string ImgBurnLogShort = "-b";
|
||||
public const string ImgBurnLogLong = "--ibg-log";
|
||||
public const string MediaBarcodeLong = "--media-barcode";
|
||||
public const string MediaManufacturerLong = "--media-manufacturer";
|
||||
public const string MediaModelLong = "--media-model";
|
||||
public const string MediaPartNumberLong = "--media-partnumber";
|
||||
public const string MediaSerialLong = "--media-serial";
|
||||
public const string MediaTitleLong = "--media-title";
|
||||
public const string MHDDLogShort = "-m";
|
||||
public const string MHDDLogLong = "--mhdd-log";
|
||||
public const string NamespaceShort = "-n";
|
||||
public const string NamespaceLong = "--namespace";
|
||||
public const string OptionsShort = "-O";
|
||||
public const string OptionsLong = "--options";
|
||||
public const string OutputPrefixShort = "-w";
|
||||
public const string OutputPrefixLong = "--output-prefix";
|
||||
public const string ResumeFileShort = "-r";
|
||||
public const string ResumeFileLong = "--resume-file";
|
||||
public const string SubchannelLong = "--subchannel";
|
||||
public const string XMLSidecarShort = "-x";
|
||||
public const string XMLSidecarLong = "--cicm-xml";
|
||||
}
|
||||
}
|
||||
169
MPF.ExecutionContexts/Aaru/FormatStrings.cs
Normal file
169
MPF.ExecutionContexts/Aaru/FormatStrings.cs
Normal file
@@ -0,0 +1,169 @@
|
||||
namespace MPF.ExecutionContexts.Aaru
|
||||
{
|
||||
/// <summary>
|
||||
/// Supported formats for Aaru
|
||||
/// </summary>
|
||||
/// TODO: Use to verify format settings
|
||||
public static class FormatStrings
|
||||
{
|
||||
// Supported filters
|
||||
public const string AppleDouble = "AppleDouble";
|
||||
public const string AppleSingle = "AppleSingle";
|
||||
public const string BZip2 = "BZip2";
|
||||
public const string GZip = "GZip";
|
||||
public const string LZip = "LZip";
|
||||
public const string MacBinary = "MacBinary";
|
||||
public const string NoFilter = "No filter";
|
||||
public const string PCExchange = "PCExchange";
|
||||
public const string XZ = "XZ";
|
||||
|
||||
// Read-only media image formats
|
||||
public const string AppleDiskArchivalRetrievalTool = "Apple Disk Archival/Retrieval Tool";
|
||||
public const string AppleNewDiskImageFormat = "Apple New Disk Image Format";
|
||||
public const string AppleNIB = "Apple NIB";
|
||||
public const string BlindWrite4 = "BlindWrite 4";
|
||||
public const string BlindWrite5 = "BlindWrite 5";
|
||||
public const string CPCEMUDiskFileAndExtendedCPCDiskFile = "CPCEMU Disk-File and Extended CPC Disk-File";
|
||||
public const string D2FDiskImage = "d2f disk image";
|
||||
public const string D88DiskImage = "D88 Disk Image";
|
||||
public const string DIMDiskImage = "DIM Disk Image";
|
||||
public const string DiscFerret = "DiscFerret";
|
||||
public const string DiscJuggler = "DiscJuggler";
|
||||
public const string DreamcastGDIImage = "Dreamcast GDI image";
|
||||
public const string DunfieldsIMD = "Dunfield's IMD";
|
||||
public const string HDCopyDiskImage = "HD-Copy disk image";
|
||||
public const string KryoFluxSTREAM = "KryoFlux STREAM";
|
||||
public const string MAMECompressedHunksOfData = "MAME Compressed Hunks of Data";
|
||||
public const string MicrosoftVHDX = "Microsoft VHDX";
|
||||
public const string NeroBurningROMImage = "Nero Burning ROM image";
|
||||
public const string PartCloneDiskImage = "PartClone disk image";
|
||||
public const string PartimageDiskImage = "Partimage disk image";
|
||||
public const string SpectrumFloppyDiskImage = "Spectrum Floppy Disk Image";
|
||||
public const string SuperCardPro = "SuperCardPro";
|
||||
public const string SydexCopyQM = "Sydex CopyQM";
|
||||
public const string SydexTeleDisk = "Sydex TeleDisk";
|
||||
|
||||
// Read/write media image formats
|
||||
public const string AaruFormat = "Aaru Format";
|
||||
public const string ACTApricotDiskImage = "ACT Apricot Disk Image";
|
||||
public const string Alcohol120MediaDescriptorStructure = "Alcohol 120% Media Descriptor Structure";
|
||||
public const string Anex86DiskImage = "Anex86 Disk Image";
|
||||
public const string Apple2InterleavedDiskImage = "Apple ][Interleaved Disk Image";
|
||||
public const string Apple2IMG = "Apple 2IMG";
|
||||
public const string AppleDiskCopy42 = "Apple DiskCopy 4.2";
|
||||
public const string AppleUniversalDiskImageFormat = "Apple Universal Disk Image Format";
|
||||
public const string BasicLisaUtility = "Basic Lisa Utility";
|
||||
public const string CDRDAOTocfile = "CDRDAO tocfile";
|
||||
public const string CDRWinCuesheet = "CDRWin cuesheet";
|
||||
public const string CisCopyDiskImageDCFile = "CisCopy Disk Image(DC-File)";
|
||||
public const string CloneCD = "CloneCD";
|
||||
public const string CopyTape = "CopyTape";
|
||||
public const string DigitalResearchDiskCopy = "Digital Research DiskCopy";
|
||||
public const string IBMSaveDskF = "IBM SaveDskF";
|
||||
public const string MAXIDiskImage = "MAXI Disk image";
|
||||
public const string ParallelsDiskImage = "Parallels disk image";
|
||||
public const string QEMUCopyOnWriteDiskImage = "QEMU Copy-On-Write disk image";
|
||||
public const string QEMUCopyOnWriteDiskImageV2 = "QEMU Copy-On-Write disk image v2";
|
||||
public const string QEMUEnhancedDiskImage = "QEMU Enhanced Disk image";
|
||||
public const string RawDiskImage = "Raw Disk Image";
|
||||
public const string RayAracheliansDiskIMage = "Ray Arachelian's Disk IMage";
|
||||
public const string RSIDEHardDiskImage = "RS-IDE Hard Disk Image";
|
||||
public const string T98HardDiskImage = "T98 Hard Disk Image";
|
||||
public const string T98NextNHDr0DiskImage = "T98-Next NHD r0 Disk Image";
|
||||
public const string Virtual98DiskImage = "Virtual98 Disk Image";
|
||||
public const string VirtualBoxDiskImage = "VirtualBox Disk Image";
|
||||
public const string VirtualPC = "VirtualPC";
|
||||
public const string VMwareDiskImage = "VMware disk image";
|
||||
|
||||
// Supported filesystems for identification and information only
|
||||
public const string AcornAdvancedDiscFilingSystem = "Acorn Advanced Disc Filing System";
|
||||
public const string AlexanderOsipovDOSFileSystem = "Alexander Osipov DOS file system";
|
||||
public const string AmigaDOSFilesystem = "Amiga DOS filesystem";
|
||||
public const string AppleFileSystem = "Apple File System";
|
||||
public const string AppleHFSPlusFilesystem = "Apple HFS+ filesystem";
|
||||
public const string AppleHierarchicalFileSystem = "Apple Hierarchical File System";
|
||||
public const string AppleProDOSFilesystem = "Apple ProDOS filesystem";
|
||||
public const string AtheOSFilesystem = "AtheOS Filesystem";
|
||||
public const string BeFilesystem = "Be Filesystem";
|
||||
public const string BSDFastFileSystem = "BSD Fast File System(aka UNIX File System, UFS)";
|
||||
public const string BTreeFileSystem = "B-tree file system";
|
||||
public const string CommodoreFileSystem = "Commodore file system";
|
||||
public const string CramFilesystem = "Cram filesystem";
|
||||
public const string DumpEightPlugin = "dump(8) Plugin";
|
||||
public const string ECMA67 = "ECMA-67";
|
||||
public const string ExtentFileSystemPlugin = "Extent File System Plugin";
|
||||
public const string F2FSPlugin = "F2FS Plugin";
|
||||
public const string Files11OnDiskStructure = "Files-11 On-Disk Structure";
|
||||
public const string FossilFilesystemPlugin = "Fossil Filesystem Plugin";
|
||||
public const string HAMMERFilesystem = "HAMMER Filesystem";
|
||||
public const string HighPerformanceOpticalFileSystem = "High Performance Optical File System";
|
||||
public const string HPLogicalInterchangeFormatPlugin = "HP Logical Interchange Format Plugin";
|
||||
public const string JFSPlugin = "JFS Plugin";
|
||||
public const string LinuxExtendedFilesystem = "Linux extended Filesystem";
|
||||
public const string LinuxExtendedFilesystem234 = "Linux extended Filesystem 2, 3 and 4";
|
||||
public const string LocusFilesystemPlugin = "Locus Filesystem Plugin";
|
||||
public const string MicroDOSFileSystem = "MicroDOS file system";
|
||||
public const string MicrosoftExtendedFileAllocationTable = "Microsoft Extended File Allocation Table";
|
||||
public const string MinixFilesystem = "Minix Filesystem";
|
||||
public const string NewTechnologyFileSystem = "New Technology File System(NTFS)";
|
||||
public const string NILFS2Plugin = "NILFS2 Plugin";
|
||||
public const string NintendoOpticalFilesystems = "Nintendo optical filesystems";
|
||||
public const string OS2HighPerformanceFileSystem = "OS/2 High Performance File System";
|
||||
public const string OS9RandomBlockFilePlugin = "OS-9 Random Block File Plugin";
|
||||
public const string PCEngineCDPlugin = "PC Engine CD Plugin";
|
||||
public const string PCFXPlugin = "PC-FX Plugin";
|
||||
public const string ProfessionalFileSystem = "Professional File System";
|
||||
public const string QNX4Plugin = "QNX4 Plugin";
|
||||
public const string QNX6Plugin = "QNX6 Plugin";
|
||||
public const string ReiserFilesystemPlugin = "Reiser Filesystem Plugin";
|
||||
public const string Reiser4FilesystemPlugin = "Reiser4 Filesystem Plugin";
|
||||
public const string ResilientFileSystemPlugin = "Resilient File System plugin";
|
||||
public const string RT11FileSystem = "RT-11 file system";
|
||||
public const string SmartFileSystem = "SmartFileSystem";
|
||||
public const string SolarOSFilesystem = "Solar_OS filesystem";
|
||||
public const string SquashFilesystem = "Squash filesystem";
|
||||
public const string UNICOSFilesystemPlugin = "UNICOS Filesystem Plugin";
|
||||
public const string UniversalDiskFormat = "Universal Disk Format";
|
||||
public const string UNIXBootFilesystem = "UNIX Boot filesystem";
|
||||
public const string UNIXSystemVFilesystem = "UNIX System V filesystem";
|
||||
public const string VeritasFilesystem = "Veritas filesystem";
|
||||
public const string VMwareFilesystem = "VMware filesystem";
|
||||
public const string XFSFilesystemPlugin = "XFS Filesystem Plugin";
|
||||
public const string XiaFilesystem = "Xia filesystem";
|
||||
public const string ZFSFilesystemPlugin = "ZFS Filesystem Plugin";
|
||||
|
||||
// Supported filesystems that can read their contents
|
||||
public const string AppleDOSFileSystem = "Apple DOS File System";
|
||||
public const string AppleLisaFileSystem = "Apple Lisa File System";
|
||||
public const string AppleMacintoshFileSystem = "Apple Macintosh File System";
|
||||
public const string CPMFileSystem = "CP/M File System";
|
||||
public const string FATXFilesystemPlugin = "FATX Filesystem Plugin";
|
||||
public const string ISO9660Filesystem = "ISO9660 Filesystem";
|
||||
public const string MicrosoftFileAllocationTable = "Microsoft File Allocation Table";
|
||||
public const string OperaFilesystemPlugin = "Opera Filesystem Plugin";
|
||||
public const string UCSDPascalFilesystem = "U.C.S.D.Pascal filesystem";
|
||||
|
||||
// Supported partitioning schemes
|
||||
public const string AcornFileCorePartitions = "Acorn FileCore partitions";
|
||||
public const string ACTApricotPartitions = "ACT Apricot partitions";
|
||||
public const string AmigaRigidDiskBlock = "Amiga Rigid Disk Block";
|
||||
public const string ApplePartitionMap = "Apple Partition Map";
|
||||
public const string AtariPartitions = "Atari partitions";
|
||||
public const string BSDDisklabel = "BSD disklabel";
|
||||
public const string DECDisklabel = "DEC disklabel";
|
||||
public const string DragonFlyBSD64bitDisklabel = "DragonFly BSD 64-bit disklabel";
|
||||
public const string GUIDPartitionTable = "GUID Partition Table";
|
||||
public const string Human68kPartitions = "Human 68k partitions";
|
||||
public const string MasterBootRecord = "Master Boot Record";
|
||||
public const string NECPC9800PartitionTable = "NEC PC-9800 partition table";
|
||||
public const string NeXTDisklabel = "NeXT Disklabel";
|
||||
public const string Plan9PartitionTable = "Plan9 partition table";
|
||||
public const string RioKarmaPartitioning = "Rio Karma partitioning";
|
||||
public const string SGIDiskVolumeHeader = "SGI Disk Volume Header";
|
||||
public const string SunDisklabel = "Sun Disklabel";
|
||||
public const string UNIXHardwired = "UNIX hardwired";
|
||||
public const string UNIXVTOC = "UNIX VTOC";
|
||||
public const string XboxPartitioning = "Xbox partitioning";
|
||||
public const string XENIX = "XENIX";
|
||||
}
|
||||
}
|
||||
27
MPF.ExecutionContexts/Aaru/NamespaceStrings.cs
Normal file
27
MPF.ExecutionContexts/Aaru/NamespaceStrings.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
namespace MPF.ExecutionContexts.Aaru
|
||||
{
|
||||
/// <summary>
|
||||
/// Supported namespaces for Aaru
|
||||
/// </summary>
|
||||
/// TODO: Use to verify namespace settings
|
||||
public static class NamespaceStrings
|
||||
{
|
||||
// Namespaces for Apple Lisa File System
|
||||
public const string LisaOfficeSystem = "office";
|
||||
public const string LisaPascalWorkshop = "workshop"; // Default
|
||||
|
||||
// Namespaces for ISO9660 Filesystem
|
||||
public const string JolietVolumeDescriptor = "joliet"; // Default
|
||||
public const string PrimaryVolumeDescriptor = "normal";
|
||||
public const string PrimaryVolumeDescriptorwithEncoding = "romeo";
|
||||
public const string RockRidge = "rrip";
|
||||
public const string PrimaryVolumeDescriptorVersionSuffix = "vms";
|
||||
|
||||
// Namespaces for Microsoft File Allocation Table
|
||||
public const string DOS83UpperCase = "dos";
|
||||
public const string LFNWhenAvailableWithFallback = "ecs"; // Default
|
||||
public const string LongFileNames = "lfn";
|
||||
public const string WindowsNT83MixedCase = "nt";
|
||||
public const string OS2Extended = "os2";
|
||||
}
|
||||
}
|
||||
43
MPF.ExecutionContexts/Aaru/OptionStrings.cs
Normal file
43
MPF.ExecutionContexts/Aaru/OptionStrings.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
namespace MPF.ExecutionContexts.Aaru
|
||||
{
|
||||
/// <summary>
|
||||
/// Supported options for Aaru
|
||||
/// </summary>
|
||||
/// TODO: Use to verify option settings
|
||||
public static class OptionStrings
|
||||
{
|
||||
// Aaru format
|
||||
public const string AaruCompress = "compress"; // boolean, default true;
|
||||
public const string AaruDeduplicate = "deduplicate"; // boolean, default true
|
||||
public const string AaruDictionary = "dictionary"; // number, default 33554432
|
||||
public const string AaruMaxDDTSize = "max_ddt_size"; // number, default 256
|
||||
public const string AaruMD5 = "md5"; // boolean, default false
|
||||
public const string AaruSectorsPerBlock = "sectors_per_block"; // number, default 4096 [power of 2]
|
||||
public const string AaruSHA1 = "sha1"; // boolean, default false
|
||||
public const string AaruSHA256 = "sha256"; // boolean, default false
|
||||
public const string AaruSpamSum = "spamsum"; // boolean, default false
|
||||
|
||||
// ACT Apricot Disk Image
|
||||
public const string ACTApricotDiskImageCompress = "compress"; // boolean, default false
|
||||
|
||||
// Apple DiskCopy 4.2
|
||||
public const string AppleDiskCopyMacOSX = "macosx"; // boolean, default false
|
||||
|
||||
// CDRDAO tocfile
|
||||
public const string CDRDAOTocfileSeparate = "separate"; // boolean, default false
|
||||
|
||||
// CDRWin cuesheet
|
||||
public const string CDRWinCuesheetSeparate = "separate"; // boolean, default false
|
||||
|
||||
// ISO9660 Filesystem
|
||||
public const string ISO9660FSUseEvd = "use_evd"; // boolean, default false
|
||||
public const string ISO9660FSUsePathTable = "use_path_table"; // boolean, default false
|
||||
public const string ISO9660FSUseTransTbl = "use_trans_tbl"; // boolean, default false
|
||||
|
||||
// VMware disk image
|
||||
public const string VMwareDiskImageAdapterType = "adapter_type"; // string, default ide [ide, lsilogic, buslogic, legacyESX]
|
||||
public const string VMwareDiskImageHWVersion = "hwversion"; // number, default 4
|
||||
public const string VMwareDiskImageSparse = "sparse"; // boolean, default false
|
||||
public const string VMwareDiskImageSplit = "split"; // boolean, default false
|
||||
}
|
||||
}
|
||||
20
MPF.ExecutionContexts/Aaru/SettingConstants.cs
Normal file
20
MPF.ExecutionContexts/Aaru/SettingConstants.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
namespace MPF.ExecutionContexts.Aaru
|
||||
{
|
||||
public static class SettingConstants
|
||||
{
|
||||
public const string EnableDebug = "AaruEnableDebug";
|
||||
public const bool EnableDebugDefault = false;
|
||||
|
||||
public const string EnableVerbose = "AaruEnableVerbose";
|
||||
public const bool EnableVerboseDefault = true;
|
||||
|
||||
public const string ForceDumping = "AaruForceDumping";
|
||||
public const bool ForceDumpingDefault = true;
|
||||
|
||||
public const string RereadCount = "AaruRereadCount";
|
||||
public const int RereadCountDefault = 5;
|
||||
|
||||
public const string StripPersonalData = "AaruStripPersonalData";
|
||||
public const bool StripPersonalDataDefault = false;
|
||||
}
|
||||
}
|
||||
1106
MPF.ExecutionContexts/BaseExecutionContext.cs
Normal file
1106
MPF.ExecutionContexts/BaseExecutionContext.cs
Normal file
File diff suppressed because it is too large
Load Diff
35
MPF.ExecutionContexts/DiscImageCreator/CommandStrings.cs
Normal file
35
MPF.ExecutionContexts/DiscImageCreator/CommandStrings.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
namespace MPF.ExecutionContexts.DiscImageCreator
|
||||
{
|
||||
/// <summary>
|
||||
/// Top-level commands for DiscImageCreator
|
||||
/// </summary>
|
||||
public static class CommandStrings
|
||||
{
|
||||
public const string NONE = "";
|
||||
public const string Audio = "audio";
|
||||
public const string BluRay = "bd";
|
||||
public const string Close = "close";
|
||||
public const string CompactDisc = "cd";
|
||||
public const string Data = "data";
|
||||
public const string DigitalVideoDisc = "dvd";
|
||||
public const string Disk = "disk";
|
||||
public const string DriveSpeed = "ls";
|
||||
public const string Eject = "eject";
|
||||
public const string Floppy = "fd";
|
||||
public const string GDROM = "gd";
|
||||
public const string MDS = "mds";
|
||||
public const string Merge = "merge";
|
||||
public const string Reset = "reset";
|
||||
public const string SACD = "sacd";
|
||||
public const string Start = "start";
|
||||
public const string Stop = "stop";
|
||||
public const string Sub = "sub";
|
||||
public const string Swap = "swap";
|
||||
public const string Tape = "tape";
|
||||
public const string Version = "/v";
|
||||
public const string XBOX = "xbox";
|
||||
public const string XBOXSwap = "xboxswap";
|
||||
public const string XGD2Swap = "xgd2swap";
|
||||
public const string XGD3Swap = "xgd3swap";
|
||||
}
|
||||
}
|
||||
100
MPF.ExecutionContexts/DiscImageCreator/Converters.cs
Normal file
100
MPF.ExecutionContexts/DiscImageCreator/Converters.cs
Normal file
@@ -0,0 +1,100 @@
|
||||
using SabreTools.RedumpLib.Data;
|
||||
|
||||
namespace MPF.ExecutionContexts.DiscImageCreator
|
||||
{
|
||||
public static class Converters
|
||||
{
|
||||
#region Cross-enumeration conversions
|
||||
|
||||
/// <summary>
|
||||
/// Get the most common known system for a given MediaType
|
||||
/// </summary>
|
||||
/// <param name="baseCommand">Command value to check</param>
|
||||
/// <returns>RedumpSystem if possible, null on error</returns>
|
||||
public static RedumpSystem? ToRedumpSystem(string baseCommand)
|
||||
{
|
||||
return baseCommand switch
|
||||
{
|
||||
CommandStrings.Audio => (RedumpSystem?)RedumpSystem.AudioCD,
|
||||
CommandStrings.CompactDisc
|
||||
or CommandStrings.Data
|
||||
or CommandStrings.DigitalVideoDisc
|
||||
or CommandStrings.Disk
|
||||
or CommandStrings.Floppy
|
||||
or CommandStrings.Tape => (RedumpSystem?)RedumpSystem.IBMPCcompatible,
|
||||
CommandStrings.GDROM
|
||||
or CommandStrings.Swap => (RedumpSystem?)RedumpSystem.SegaDreamcast,
|
||||
CommandStrings.BluRay => (RedumpSystem?)RedumpSystem.SonyPlayStation3,
|
||||
CommandStrings.SACD => (RedumpSystem?)RedumpSystem.SuperAudioCD,
|
||||
CommandStrings.XBOX
|
||||
or CommandStrings.XBOXSwap => (RedumpSystem?)RedumpSystem.MicrosoftXbox,
|
||||
CommandStrings.XGD2Swap
|
||||
or CommandStrings.XGD3Swap => (RedumpSystem?)RedumpSystem.MicrosoftXbox360,
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the MediaType associated with a given base command
|
||||
/// </summary>
|
||||
/// <param name="baseCommand">Command value to check</param>
|
||||
/// <returns>MediaType if possible, null on error</returns>
|
||||
/// <remarks>This takes the "safe" route by assuming the larger of any given format</remarks>
|
||||
public static MediaType? ToMediaType(string? baseCommand)
|
||||
{
|
||||
return baseCommand switch
|
||||
{
|
||||
CommandStrings.Audio
|
||||
or CommandStrings.CompactDisc
|
||||
or CommandStrings.Data
|
||||
or CommandStrings.SACD => (MediaType?)MediaType.CDROM,
|
||||
CommandStrings.GDROM
|
||||
or CommandStrings.Swap => (MediaType?)MediaType.GDROM,
|
||||
CommandStrings.DigitalVideoDisc
|
||||
or CommandStrings.XBOX
|
||||
or CommandStrings.XBOXSwap
|
||||
or CommandStrings.XGD2Swap
|
||||
or CommandStrings.XGD3Swap => (MediaType?)MediaType.DVD,
|
||||
CommandStrings.BluRay => (MediaType?)MediaType.BluRay,
|
||||
|
||||
// Non-optical
|
||||
CommandStrings.Floppy => (MediaType?)MediaType.FloppyDisk,
|
||||
CommandStrings.Disk => (MediaType?)MediaType.HardDisk,
|
||||
CommandStrings.Tape => (MediaType?)MediaType.DataCartridge,
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the default extension for a given disc type
|
||||
/// </summary>
|
||||
/// <param name="type">MediaType value to check</param>
|
||||
/// <returns>Valid extension (with leading '.'), null on error</returns>
|
||||
public static string? Extension(MediaType? type)
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
MediaType.CDROM
|
||||
or MediaType.GDROM
|
||||
or MediaType.Cartridge
|
||||
or MediaType.HardDisk
|
||||
or MediaType.CompactFlash
|
||||
or MediaType.MMC
|
||||
or MediaType.SDCard
|
||||
or MediaType.FlashDrive => ".bin",
|
||||
MediaType.DVD
|
||||
or MediaType.HDDVD
|
||||
or MediaType.BluRay
|
||||
or MediaType.NintendoWiiOpticalDisc => ".iso",
|
||||
MediaType.LaserDisc
|
||||
or MediaType.NintendoGameCubeGameDisc => ".raw",
|
||||
MediaType.NintendoWiiUOpticalDisc => ".wud",
|
||||
MediaType.FloppyDisk => ".img",
|
||||
MediaType.Cassette => ".wav",
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
1753
MPF.ExecutionContexts/DiscImageCreator/ExecutionContext.cs
Normal file
1753
MPF.ExecutionContexts/DiscImageCreator/ExecutionContext.cs
Normal file
File diff suppressed because it is too large
Load Diff
46
MPF.ExecutionContexts/DiscImageCreator/FlagStrings.cs
Normal file
46
MPF.ExecutionContexts/DiscImageCreator/FlagStrings.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
namespace MPF.ExecutionContexts.DiscImageCreator
|
||||
{
|
||||
/// <summary>
|
||||
/// Dumping flags for DiscImageCreator
|
||||
/// </summary>
|
||||
public static class FlagStrings
|
||||
{
|
||||
public const string AddOffset = "/a";
|
||||
public const string AMSF = "/p";
|
||||
public const string AtariJaguar = "/aj";
|
||||
public const string BEOpcode = "/be";
|
||||
public const string C2Opcode = "/c2";
|
||||
public const string CopyrightManagementInformation = "/c";
|
||||
public const string D8Opcode = "/d8";
|
||||
public const string DatExpand = "/d";
|
||||
public const string DisableBeep = "/q";
|
||||
public const string DVDReread = "/rr";
|
||||
public const string ExtractMicroSoftCabFile = "/mscf";
|
||||
public const string Fix = "/fix";
|
||||
public const string ForceUnitAccess = "/f";
|
||||
public const string MultiSectorRead = "/mr";
|
||||
public const string NoFixSubP = "/np";
|
||||
public const string NoFixSubQ = "/nq";
|
||||
public const string NoFixSubQLibCrypt = "/nl";
|
||||
public const string NoFixSubRtoW = "/nr";
|
||||
public const string NoFixSubQSecuROM = "/ns";
|
||||
public const string NoSkipSS = "/nss";
|
||||
public const string PadSector = "/ps";
|
||||
public const string Range = "/ra";
|
||||
public const string Raw = "/raw";
|
||||
public const string Resume = "/re";
|
||||
public const string Reverse = "/r";
|
||||
public const string ScanAntiMod = "/am";
|
||||
public const string ScanFileProtect = "/sf";
|
||||
public const string ScanSectorProtect = "/ss";
|
||||
public const string SeventyFour = "/74";
|
||||
public const string SkipSector = "/sk";
|
||||
public const string SubchannelReadLevel = "/s";
|
||||
public const string Tages = "/t";
|
||||
public const string TryReadingPregap = "/trp";
|
||||
public const string UseAnchorVolumeDescriptorPointer = "/avdp";
|
||||
public const string VideoNow = "/vn";
|
||||
public const string VideoNowColor = "/vnc";
|
||||
public const string VideoNowXP = "/vnx";
|
||||
}
|
||||
}
|
||||
26
MPF.ExecutionContexts/DiscImageCreator/SettingConstants.cs
Normal file
26
MPF.ExecutionContexts/DiscImageCreator/SettingConstants.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
namespace MPF.ExecutionContexts.DiscImageCreator
|
||||
{
|
||||
public static class SettingConstants
|
||||
{
|
||||
public const string DVDRereadCount = "DICDVDRereadCount";
|
||||
public const int DVDRereadCountDefault = 10;
|
||||
|
||||
public const string MultiSectorRead = "DICMultiSectorRead";
|
||||
public const bool MultiSectorReadDefault = false;
|
||||
|
||||
public const string MultiSectorReadValue = "DICMultiSectorReadValue";
|
||||
public const int MultiSectorReadValueDefault = 0;
|
||||
|
||||
public const string ParanoidMode = "DICParanoidMode";
|
||||
public const bool ParanoidModeDefault = false;
|
||||
|
||||
public const string QuietMode = "DICQuietMode";
|
||||
public const bool QuietModeDefault = false;
|
||||
|
||||
public const string RereadCount = "DICRereadCount";
|
||||
public const int RereadCountDefault = 20;
|
||||
|
||||
public const string UseCMIFlag = "DICUseCMIFlag";
|
||||
public const bool UseCMIFlagDefault = false;
|
||||
}
|
||||
}
|
||||
53
MPF.ExecutionContexts/MPF.ExecutionContexts.csproj
Normal file
53
MPF.ExecutionContexts/MPF.ExecutionContexts.csproj
Normal file
@@ -0,0 +1,53 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<!-- Assembly Properties -->
|
||||
<TargetFrameworks>net20;net35;net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0</TargetFrameworks>
|
||||
<CheckEolTargetFramework>false</CheckEolTargetFramework>
|
||||
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<VersionPrefix>3.2.3</VersionPrefix>
|
||||
<WarningsNotAsErrors>NU5104</WarningsNotAsErrors>
|
||||
|
||||
<!-- Package Properties -->
|
||||
<Authors>Matt Nadareski;ReignStumble;Jakz</Authors>
|
||||
<Description>Common code for all MPF execution contexts</Description>
|
||||
<Copyright>Copyright (c) Matt Nadareski 2019-2024</Copyright>
|
||||
<PackageProjectUrl>https://github.com/SabreTools/</PackageProjectUrl>
|
||||
<RepositoryUrl>https://github.com/SabreTools/MPF</RepositoryUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<InternalsVisibleTo Include="MPF.Test" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Support All Frameworks -->
|
||||
<PropertyGroup Condition="$(TargetFramework.StartsWith(`net2`)) OR $(TargetFramework.StartsWith(`net3`)) OR $(TargetFramework.StartsWith(`net4`))">
|
||||
<RuntimeIdentifiers>win-x86;win-x64</RuntimeIdentifiers>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="$(TargetFramework.StartsWith(`netcoreapp`)) OR $(TargetFramework.StartsWith(`net5`))">
|
||||
<RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64</RuntimeIdentifiers>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="$(TargetFramework.StartsWith(`net6`)) OR $(TargetFramework.StartsWith(`net7`)) OR $(TargetFramework.StartsWith(`net8`))">
|
||||
<RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64;osx-arm64</RuntimeIdentifiers>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="$(RuntimeIdentifier.StartsWith(`osx-arm`))">
|
||||
<TargetFrameworks>net6.0;net7.0;net8.0</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Support for old .NET versions -->
|
||||
<ItemGroup Condition="$(TargetFramework.StartsWith(`net2`)) OR $(TargetFramework.StartsWith(`net3`)) OR $(TargetFramework.StartsWith(`net40`))">
|
||||
<PackageReference Include="MinAsyncBridge" Version="0.12.4" />
|
||||
<PackageReference Include="MinTasksExtensionsBridge" Version="0.3.4" />
|
||||
<PackageReference Include="MinThreadingBridge" Version="0.11.4" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SabreTools.RedumpLib" Version="1.4.4" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
28
MPF.ExecutionContexts/Redumper/CommandStrings.cs
Normal file
28
MPF.ExecutionContexts/Redumper/CommandStrings.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
namespace MPF.ExecutionContexts.Redumper
|
||||
{
|
||||
/// <summary>
|
||||
/// Top-level commands for Redumper
|
||||
/// </summary>
|
||||
public static class CommandStrings
|
||||
{
|
||||
public const string NONE = "";
|
||||
public const string CD = "cd";
|
||||
public const string DVD = "dvd"; // Synonym for CD
|
||||
public const string BluRay = "bd"; // Synonym for CD
|
||||
public const string SACD = "sacd"; // Synonym for CD
|
||||
public const string New = "new"; // Synonym for CD; Temporary command, to be removed later
|
||||
public const string Rings = "rings";
|
||||
public const string Dump = "dump";
|
||||
public const string DumpNew = "dumpnew"; // Temporary command, to be removed later
|
||||
public const string Refine = "refine";
|
||||
public const string RefineNew = "refinenew"; // Temporary command, to be removed later
|
||||
public const string Verify = "verify";
|
||||
public const string DVDKey = "dvdkey";
|
||||
public const string DVDIsoKey = "dvdisokey";
|
||||
public const string Protection = "protection";
|
||||
public const string Split = "split";
|
||||
public const string Hash = "hash";
|
||||
public const string Info = "info";
|
||||
public const string Skeleton = "skeleton";
|
||||
}
|
||||
}
|
||||
28
MPF.ExecutionContexts/Redumper/Converters.cs
Normal file
28
MPF.ExecutionContexts/Redumper/Converters.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using SabreTools.RedumpLib.Data;
|
||||
|
||||
namespace MPF.ExecutionContexts.Redumper
|
||||
{
|
||||
public static class Converters
|
||||
{
|
||||
#region Cross-enumeration conversions
|
||||
|
||||
/// <summary>
|
||||
/// Get the default extension for a given disc type
|
||||
/// </summary>
|
||||
/// <param name="type">MediaType value to check</param>
|
||||
/// <returns>Valid extension (with leading '.'), null on error</returns>
|
||||
public static string? Extension(MediaType? type)
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
MediaType.CDROM => ".bin",
|
||||
MediaType.DVD
|
||||
or MediaType.HDDVD
|
||||
or MediaType.BluRay => ".iso",
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
27
MPF.ExecutionContexts/Redumper/Enumerations.cs
Normal file
27
MPF.ExecutionContexts/Redumper/Enumerations.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
namespace MPF.ExecutionContexts.Redumper
|
||||
{
|
||||
/// <summary>
|
||||
/// Drive read method option
|
||||
/// </summary>
|
||||
public enum ReadMethod
|
||||
{
|
||||
NONE = 0,
|
||||
|
||||
BE,
|
||||
D8,
|
||||
BE_CDDA,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Drive sector order option
|
||||
/// </summary>
|
||||
public enum SectorOrder
|
||||
{
|
||||
NONE = 0,
|
||||
|
||||
DATA_C2_SUB,
|
||||
DATA_SUB_C2,
|
||||
DATA_SUB,
|
||||
DATA_C2,
|
||||
}
|
||||
}
|
||||
909
MPF.ExecutionContexts/Redumper/ExecutionContext.cs
Normal file
909
MPF.ExecutionContexts/Redumper/ExecutionContext.cs
Normal file
@@ -0,0 +1,909 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using SabreTools.RedumpLib.Data;
|
||||
|
||||
namespace MPF.ExecutionContexts.Redumper
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a generic set of Redumper parameters
|
||||
/// </summary>
|
||||
public sealed class ExecutionContext : BaseExecutionContext
|
||||
{
|
||||
#region Generic Dumping Information
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string? InputPath => DriveValue?.Trim('"');
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string? OutputPath => Path.Combine(
|
||||
ImagePathValue?.Trim('"') ?? string.Empty,
|
||||
ImageNameValue?.Trim('"') ?? string.Empty)
|
||||
+ GetDefaultExtension(this.Type);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int? Speed => SpeedValue;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Flag Values
|
||||
|
||||
/// <summary>
|
||||
/// List of all modes being run
|
||||
/// </summary>
|
||||
public List<string>? ModeValues { get; set; }
|
||||
|
||||
#region General
|
||||
|
||||
/// <summary>
|
||||
/// Drive to use, first available drive with disc, if not provided
|
||||
/// </summary>
|
||||
public string? DriveValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Drive read speed, optimal drive speed will be used if not provided
|
||||
/// </summary>
|
||||
public int? SpeedValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of sector retries in case of SCSI/C2 error (default: 0)
|
||||
/// </summary>
|
||||
public int? RetriesValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Dump files base directory
|
||||
/// </summary>
|
||||
public string? ImagePathValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Dump files prefix, autogenerated in dump mode, if not provided
|
||||
/// </summary>
|
||||
public string? ImageNameValue { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Drive Configuration
|
||||
|
||||
/// <summary>
|
||||
/// Override drive type, possible values: GENERIC, PLEXTOR, LG_ASUS
|
||||
/// </summary>
|
||||
public string? DriveTypeValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Override drive read offset
|
||||
/// </summary>
|
||||
public int? DriveReadOffsetValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Override drive C2 shift
|
||||
/// </summary>
|
||||
public int? DriveC2ShiftValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Override drive pre-gap start LBA
|
||||
/// </summary>
|
||||
public int? DrivePregapStartValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Override drive read method, possible values: BE, D8, BE_CDDA
|
||||
/// </summary>
|
||||
public string? DriveReadMethodValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Override drive sector order, possible values: DATA_C2_SUB, DATA_SUB_C2
|
||||
/// </summary>
|
||||
public string? DriveSectorOrderValue { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Offset
|
||||
|
||||
/// <summary>
|
||||
/// Override offset autodetection and use supplied value
|
||||
/// </summary>
|
||||
public int? ForceOffsetValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Maximum absolute sample value to treat it as silence (default: 32)
|
||||
/// </summary>
|
||||
public int? AudioSilenceThresholdValue { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Split
|
||||
|
||||
/// <summary>
|
||||
/// Fill byte value for skipped sectors (default: 0x55)
|
||||
/// </summary>
|
||||
public byte? SkipFillValue { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Miscellaneous
|
||||
|
||||
/// <summary>
|
||||
/// LBA to start dumping from
|
||||
/// </summary>
|
||||
public int? LBAStartValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// LBA to stop dumping at (everything before the value), useful for discs with fake TOC
|
||||
/// </summary>
|
||||
public int? LBAEndValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// LBA ranges of sectors to skip
|
||||
/// </summary>
|
||||
public string? SkipValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Write offset for dumps when reading as data
|
||||
/// </summary>
|
||||
public int? DumpWriteOffsetValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of sectors to read at once on initial dump, DVD only (Default 32)
|
||||
/// </summary>
|
||||
public int? DumpReadSizeValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Maximum number of lead-in retries per session (Default 4)
|
||||
/// </summary>
|
||||
public int? PlextorLeadinRetriesValue { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ExecutionContext(string? parameters) : base(parameters) { }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ExecutionContext(RedumpSystem? system, MediaType? type, string? drivePath, string filename, int? driveSpeed, Dictionary<string, string?> options)
|
||||
: base(system, type, drivePath, filename, driveSpeed, options)
|
||||
{
|
||||
}
|
||||
|
||||
#region BaseExecutionContext Implementations
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <remarks>Command support is irrelevant for redumper</remarks>
|
||||
public override Dictionary<string, List<string>> GetCommandSupport()
|
||||
{
|
||||
return new Dictionary<string, List<string>>()
|
||||
{
|
||||
[CommandStrings.NONE] =
|
||||
[
|
||||
// General
|
||||
FlagStrings.HelpLong,
|
||||
FlagStrings.HelpShort,
|
||||
FlagStrings.Version,
|
||||
FlagStrings.Verbose,
|
||||
FlagStrings.Debug,
|
||||
FlagStrings.Drive,
|
||||
FlagStrings.Speed,
|
||||
FlagStrings.Retries,
|
||||
FlagStrings.ImagePath,
|
||||
FlagStrings.ImageName,
|
||||
FlagStrings.Overwrite,
|
||||
|
||||
// Drive Configuration
|
||||
FlagStrings.DriveType,
|
||||
FlagStrings.DriveReadOffset,
|
||||
FlagStrings.DriveC2Shift,
|
||||
FlagStrings.DrivePregapStart,
|
||||
FlagStrings.DriveReadMethod,
|
||||
FlagStrings.DriveSectorOrder,
|
||||
|
||||
// Drive Specific
|
||||
FlagStrings.PlextorSkipLeadin,
|
||||
FlagStrings.PlextorLeadinRetries,
|
||||
FlagStrings.AsusSkipLeadout,
|
||||
|
||||
// Offset
|
||||
FlagStrings.ForceOffset,
|
||||
FlagStrings.AudioSilenceThreshold,
|
||||
FlagStrings.CorrectOffsetShift,
|
||||
FlagStrings.OffsetShiftRelocate,
|
||||
|
||||
// Split
|
||||
FlagStrings.ForceSplit,
|
||||
FlagStrings.LeaveUnchanged,
|
||||
FlagStrings.ForceQTOC,
|
||||
FlagStrings.SkipFill,
|
||||
FlagStrings.ISO9660Trim,
|
||||
|
||||
// Miscellaneous
|
||||
FlagStrings.LBAStart,
|
||||
FlagStrings.LBAEnd,
|
||||
FlagStrings.RefineSubchannel,
|
||||
FlagStrings.Skip,
|
||||
FlagStrings.DumpWriteOffset,
|
||||
FlagStrings.DumpReadSize,
|
||||
FlagStrings.OverreadLeadout,
|
||||
FlagStrings.ForceUnscrambled,
|
||||
FlagStrings.LegacySubs,
|
||||
FlagStrings.DisableCDText,
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <remarks>
|
||||
/// Redumper is unique in that the base command can be multiple
|
||||
/// modes all listed together. It is also unique in that "all
|
||||
/// flags are supported for everything" and it filters out internally
|
||||
/// </remarks>
|
||||
public override string GenerateParameters()
|
||||
{
|
||||
var parameters = new List<string>();
|
||||
|
||||
ModeValues ??= [CommandStrings.NONE];
|
||||
|
||||
// Modes
|
||||
parameters.AddRange(ModeValues);
|
||||
|
||||
#region General
|
||||
|
||||
// Help
|
||||
if (this[FlagStrings.HelpLong] == true)
|
||||
parameters.Add(FlagStrings.HelpLong);
|
||||
|
||||
// Version
|
||||
if (this[FlagStrings.Version] == true)
|
||||
parameters.Add(FlagStrings.Version);
|
||||
|
||||
// Verbose
|
||||
if (this[FlagStrings.Verbose] == true)
|
||||
parameters.Add(FlagStrings.Verbose);
|
||||
|
||||
// Debug
|
||||
if (this[FlagStrings.Debug] == true)
|
||||
parameters.Add(FlagStrings.Debug);
|
||||
|
||||
// Drive
|
||||
if (this[FlagStrings.Drive] == true)
|
||||
{
|
||||
if (DriveValue != null)
|
||||
{
|
||||
if (DriveValue.Contains(' '))
|
||||
parameters.Add($"{FlagStrings.Drive}=\"{DriveValue}\"");
|
||||
else
|
||||
parameters.Add($"{FlagStrings.Drive}={DriveValue}");
|
||||
}
|
||||
}
|
||||
|
||||
// Speed
|
||||
if (this[FlagStrings.Speed] == true)
|
||||
{
|
||||
if (SpeedValue != null)
|
||||
parameters.Add($"{FlagStrings.Speed}={SpeedValue}");
|
||||
}
|
||||
|
||||
// Retries
|
||||
if (this[FlagStrings.Retries] == true)
|
||||
{
|
||||
if (RetriesValue != null)
|
||||
parameters.Add($"{FlagStrings.Retries}={RetriesValue}");
|
||||
}
|
||||
|
||||
// Image Path
|
||||
if (this[FlagStrings.ImagePath] == true)
|
||||
{
|
||||
if (ImagePathValue != null)
|
||||
parameters.Add($"{FlagStrings.ImagePath}={ImagePathValue}");
|
||||
}
|
||||
|
||||
// Image Name
|
||||
if (this[FlagStrings.ImageName] == true)
|
||||
{
|
||||
if (ImageNameValue != null)
|
||||
parameters.Add($"{FlagStrings.ImageName}={ImageNameValue}");
|
||||
}
|
||||
|
||||
// Overwrite
|
||||
if (this[FlagStrings.Overwrite] == true)
|
||||
parameters.Add(FlagStrings.Overwrite);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Drive Configuration
|
||||
|
||||
// Drive Type
|
||||
if (this[FlagStrings.DriveType] == true)
|
||||
{
|
||||
if (DriveTypeValue != null)
|
||||
parameters.Add($"{FlagStrings.DriveType}={DriveTypeValue}");
|
||||
}
|
||||
|
||||
// Drive Read Offset
|
||||
if (this[FlagStrings.DriveReadOffset] == true)
|
||||
{
|
||||
if (DriveReadOffsetValue != null)
|
||||
parameters.Add($"{FlagStrings.DriveReadOffset}={DriveReadOffsetValue}");
|
||||
}
|
||||
|
||||
// Drive C2 Shift
|
||||
if (this[FlagStrings.DriveC2Shift] == true)
|
||||
{
|
||||
if (DriveC2ShiftValue != null)
|
||||
parameters.Add($"{FlagStrings.DriveC2Shift}={DriveC2ShiftValue}");
|
||||
}
|
||||
|
||||
// Drive Pregap Start
|
||||
if (this[FlagStrings.DrivePregapStart] == true)
|
||||
{
|
||||
if (DrivePregapStartValue != null)
|
||||
parameters.Add($"{FlagStrings.DrivePregapStart}={DrivePregapStartValue}");
|
||||
}
|
||||
|
||||
// Drive Read Method
|
||||
if (this[FlagStrings.DriveReadMethod] == true)
|
||||
{
|
||||
if (DriveReadMethodValue != null)
|
||||
parameters.Add($"{FlagStrings.DriveReadMethod}={DriveReadMethodValue}");
|
||||
}
|
||||
|
||||
// Drive Sector Order
|
||||
if (this[FlagStrings.DriveSectorOrder] == true)
|
||||
{
|
||||
if (DriveSectorOrderValue != null)
|
||||
parameters.Add($"{FlagStrings.DriveSectorOrder}={DriveSectorOrderValue}");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Drive Specific
|
||||
|
||||
// Plextor Leadin Skip
|
||||
if (this[FlagStrings.PlextorSkipLeadin] == true)
|
||||
parameters.Add(FlagStrings.PlextorSkipLeadin);
|
||||
|
||||
// Plextor Leadin Retries
|
||||
if (this[FlagStrings.PlextorLeadinRetries] == true)
|
||||
{
|
||||
if (PlextorLeadinRetriesValue != null)
|
||||
parameters.Add($"{FlagStrings.PlextorLeadinRetries}={PlextorLeadinRetriesValue}");
|
||||
}
|
||||
|
||||
// Asus Skip Leadout
|
||||
if (this[FlagStrings.AsusSkipLeadout] == true)
|
||||
parameters.Add(FlagStrings.AsusSkipLeadout);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Offset
|
||||
|
||||
// Force Offset
|
||||
if (this[FlagStrings.ForceOffset] == true)
|
||||
{
|
||||
if (ForceOffsetValue != null)
|
||||
parameters.Add($"{FlagStrings.ForceOffset}={ForceOffsetValue}");
|
||||
}
|
||||
|
||||
// Audio Silence Threshold
|
||||
if (this[FlagStrings.AudioSilenceThreshold] == true)
|
||||
{
|
||||
if (AudioSilenceThresholdValue != null)
|
||||
parameters.Add($"{FlagStrings.AudioSilenceThreshold}={AudioSilenceThresholdValue}");
|
||||
}
|
||||
|
||||
// Correct Offset Shift
|
||||
if (this[FlagStrings.CorrectOffsetShift] == true)
|
||||
parameters.Add(FlagStrings.CorrectOffsetShift);
|
||||
|
||||
// Offset Shift Relocate
|
||||
if (this[FlagStrings.OffsetShiftRelocate] == true)
|
||||
parameters.Add(FlagStrings.OffsetShiftRelocate);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Split
|
||||
|
||||
// Force Split
|
||||
if (this[FlagStrings.ForceSplit] == true)
|
||||
parameters.Add(FlagStrings.ForceSplit);
|
||||
|
||||
// Leave Unchanged
|
||||
if (this[FlagStrings.LeaveUnchanged] == true)
|
||||
parameters.Add(FlagStrings.LeaveUnchanged);
|
||||
|
||||
// Force QTOC
|
||||
if (this[FlagStrings.ForceQTOC] == true)
|
||||
parameters.Add(FlagStrings.ForceQTOC);
|
||||
|
||||
// Skip Fill
|
||||
if (this[FlagStrings.SkipFill] == true)
|
||||
{
|
||||
if (SkipFillValue != null)
|
||||
parameters.Add($"{FlagStrings.SkipFill}={SkipFillValue:x}");
|
||||
}
|
||||
|
||||
// ISO9660 Trim
|
||||
if (this[FlagStrings.ISO9660Trim] == true)
|
||||
parameters.Add(FlagStrings.ISO9660Trim);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Miscellaneous
|
||||
|
||||
// LBA Start
|
||||
if (this[FlagStrings.LBAStart] == true)
|
||||
{
|
||||
if (LBAStartValue != null)
|
||||
parameters.Add($"{FlagStrings.LBAStart}={LBAStartValue}");
|
||||
}
|
||||
|
||||
// LBA End
|
||||
if (this[FlagStrings.LBAEnd] == true)
|
||||
{
|
||||
if (LBAEndValue != null)
|
||||
parameters.Add($"{FlagStrings.LBAEnd}={LBAEndValue}");
|
||||
}
|
||||
|
||||
// Refine Subchannel
|
||||
if (this[FlagStrings.RefineSubchannel] == true)
|
||||
parameters.Add(FlagStrings.RefineSubchannel);
|
||||
|
||||
// Skip
|
||||
if (this[FlagStrings.Skip] == true)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(SkipValue))
|
||||
parameters.Add($"{FlagStrings.Skip}={SkipValue}");
|
||||
}
|
||||
|
||||
// Dump Write Offset
|
||||
if (this[FlagStrings.DumpWriteOffset] == true)
|
||||
{
|
||||
if (DumpWriteOffsetValue != null)
|
||||
parameters.Add($"{FlagStrings.DumpWriteOffset}={DumpWriteOffsetValue}");
|
||||
}
|
||||
|
||||
// Dump Read Size
|
||||
if (this[FlagStrings.DumpReadSize] == true)
|
||||
{
|
||||
if (DumpReadSizeValue != null && DumpReadSizeValue > 0)
|
||||
parameters.Add($"{FlagStrings.DumpReadSize}={DumpReadSizeValue}");
|
||||
}
|
||||
|
||||
// Overread Leadout
|
||||
if (this[FlagStrings.OverreadLeadout] == true)
|
||||
parameters.Add(FlagStrings.OverreadLeadout);
|
||||
|
||||
// Force Unscrambled
|
||||
if (this[FlagStrings.ForceUnscrambled] == true)
|
||||
parameters.Add(FlagStrings.ForceUnscrambled);
|
||||
|
||||
// Legacy Subs
|
||||
if (this[FlagStrings.LegacySubs] == true)
|
||||
parameters.Add(FlagStrings.LegacySubs);
|
||||
|
||||
// Disable CD Text
|
||||
if (this[FlagStrings.DisableCDText] == true)
|
||||
parameters.Add(FlagStrings.DisableCDText);
|
||||
|
||||
#endregion
|
||||
|
||||
return string.Join(" ", [.. parameters]);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string? GetDefaultExtension(MediaType? mediaType) => Converters.Extension(mediaType);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override MediaType? GetMediaType() => null;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool IsDumpingCommand()
|
||||
{
|
||||
return this.BaseCommand == CommandStrings.NONE
|
||||
|| this.BaseCommand?.Contains(CommandStrings.CD) == true
|
||||
|| this.BaseCommand?.Contains(CommandStrings.DVD) == true
|
||||
|| this.BaseCommand?.Contains(CommandStrings.BluRay) == true
|
||||
|| this.BaseCommand?.Contains(CommandStrings.SACD) == true
|
||||
|| this.BaseCommand?.Contains(CommandStrings.New) == true
|
||||
|| this.BaseCommand?.Contains(CommandStrings.Dump) == true
|
||||
|| this.BaseCommand?.Contains(CommandStrings.DumpNew) == true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void ResetValues()
|
||||
{
|
||||
BaseCommand = CommandStrings.NONE;
|
||||
|
||||
flags = [];
|
||||
|
||||
// General
|
||||
DriveValue = null;
|
||||
SpeedValue = null;
|
||||
RetriesValue = null;
|
||||
ImagePathValue = null;
|
||||
ImageNameValue = null;
|
||||
|
||||
// Drive Configuration
|
||||
DriveTypeValue = null;
|
||||
DriveReadOffsetValue = null;
|
||||
DriveC2ShiftValue = null;
|
||||
DrivePregapStartValue = null;
|
||||
DriveReadMethodValue = null;
|
||||
DriveSectorOrderValue = null;
|
||||
|
||||
// Offset
|
||||
ForceOffsetValue = null;
|
||||
AudioSilenceThresholdValue = null;
|
||||
|
||||
// Split
|
||||
SkipFillValue = null;
|
||||
|
||||
// Miscellaneous
|
||||
LBAStartValue = null;
|
||||
LBAEndValue = null;
|
||||
SkipValue = null;
|
||||
DumpReadSizeValue = null;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void SetDefaultParameters(string? drivePath, string filename, int? driveSpeed, Dictionary<string, string?> options)
|
||||
{
|
||||
// If we don't have a CD, DVD, HD-DVD, or BD, we can't dump using redumper
|
||||
if (this.Type != MediaType.CDROM
|
||||
&& this.Type != MediaType.DVD
|
||||
&& this.Type != MediaType.HDDVD
|
||||
&& this.Type != MediaType.BluRay)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
BaseCommand = CommandStrings.NONE;
|
||||
switch (this.Type)
|
||||
{
|
||||
case MediaType.CDROM:
|
||||
ModeValues = this.System switch
|
||||
{
|
||||
RedumpSystem.SuperAudioCD => [CommandStrings.SACD],
|
||||
_ => [CommandStrings.CD],
|
||||
};
|
||||
break;
|
||||
case MediaType.DVD:
|
||||
ModeValues = [CommandStrings.DVD];
|
||||
break;
|
||||
case MediaType.HDDVD: // TODO: Keep in sync if another command string shows up
|
||||
ModeValues = [CommandStrings.DVD];
|
||||
break;
|
||||
case MediaType.BluRay:
|
||||
ModeValues = [CommandStrings.BluRay];
|
||||
break;
|
||||
default:
|
||||
BaseCommand = null;
|
||||
return;
|
||||
}
|
||||
|
||||
this[FlagStrings.Drive] = true;
|
||||
DriveValue = drivePath;
|
||||
|
||||
this[FlagStrings.Speed] = true;
|
||||
SpeedValue = driveSpeed;
|
||||
|
||||
// Set user-defined options
|
||||
if (GetBooleanSetting(options, SettingConstants.EnableVerbose, SettingConstants.EnableVerboseDefault))
|
||||
this[FlagStrings.Verbose] = true;
|
||||
if (GetBooleanSetting(options, SettingConstants.EnableDebug, SettingConstants.EnableDebugDefault))
|
||||
this[FlagStrings.Debug] = true;
|
||||
|
||||
string? readMethod = GetStringSetting(options, SettingConstants.ReadMethod, SettingConstants.ReadMethodDefault);
|
||||
|
||||
if (!string.IsNullOrEmpty(readMethod) && readMethod != ReadMethod.NONE.ToString())
|
||||
{
|
||||
this[FlagStrings.DriveReadMethod] = true;
|
||||
DriveReadMethodValue = readMethod;
|
||||
}
|
||||
|
||||
string? sectorOrder = GetStringSetting(options, SettingConstants.SectorOrder, SettingConstants.SectorOrderDefault);
|
||||
if (!string.IsNullOrEmpty(sectorOrder) && sectorOrder != SectorOrder.NONE.ToString())
|
||||
{
|
||||
this[FlagStrings.DriveSectorOrder] = true;
|
||||
DriveSectorOrderValue = sectorOrder;
|
||||
}
|
||||
|
||||
if (GetBooleanSetting(options, SettingConstants.UseGenericDriveType, SettingConstants.UseGenericDriveTypeDefault))
|
||||
{
|
||||
this[FlagStrings.DriveType] = true;
|
||||
DriveTypeValue = "GENERIC";
|
||||
}
|
||||
|
||||
// Set the output paths
|
||||
if (!string.IsNullOrEmpty(filename))
|
||||
{
|
||||
var imagePath = Path.GetDirectoryName(filename);
|
||||
if (!string.IsNullOrEmpty(imagePath))
|
||||
{
|
||||
this[FlagStrings.ImagePath] = true;
|
||||
ImagePathValue = $"\"{imagePath}\"";
|
||||
}
|
||||
|
||||
string imageName = Path.GetFileNameWithoutExtension(filename);
|
||||
if (!string.IsNullOrEmpty(imageName))
|
||||
{
|
||||
this[FlagStrings.ImageName] = true;
|
||||
ImageNameValue = $"\"{imageName}\"";
|
||||
}
|
||||
}
|
||||
|
||||
this[FlagStrings.Retries] = true;
|
||||
RetriesValue = GetInt32Setting(options, SettingConstants.RereadCount, SettingConstants.RereadCountDefault);
|
||||
|
||||
if (GetBooleanSetting(options, SettingConstants.EnableLeadinRetry, SettingConstants.EnableLeadinRetryDefault))
|
||||
{
|
||||
this[FlagStrings.PlextorLeadinRetries] = true;
|
||||
PlextorLeadinRetriesValue = GetInt32Setting(options, SettingConstants.LeadinRetryCount, SettingConstants.LeadinRetryCountDefault);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override bool ValidateAndSetParameters(string? parameters)
|
||||
{
|
||||
BaseCommand = CommandStrings.NONE;
|
||||
|
||||
// The string has to be valid by itself first
|
||||
if (string.IsNullOrEmpty(parameters))
|
||||
return false;
|
||||
|
||||
// Now split the string into parts for easier validation
|
||||
// https://stackoverflow.com/questions/14655023/split-a-string-that-has-white-spaces-unless-they-are-enclosed-within-quotes
|
||||
parameters = parameters!.Trim();
|
||||
List<string> parts = Regex.Matches(parameters, @"([a-zA-Z\-]*=)?[\""].+?[\""]|[^ ]+", RegexOptions.Compiled)
|
||||
.Cast<Match>()
|
||||
.Select(m => m.Value)
|
||||
.ToList();
|
||||
|
||||
// Setup the modes
|
||||
ModeValues = [];
|
||||
|
||||
// All modes should be cached separately
|
||||
int index = 0;
|
||||
for (; index < parts.Count; index++)
|
||||
{
|
||||
// Flag to see if we have a flag
|
||||
bool isFlag = false;
|
||||
|
||||
string part = parts[index];
|
||||
switch (part)
|
||||
{
|
||||
case CommandStrings.CD:
|
||||
case CommandStrings.DVD:
|
||||
case CommandStrings.BluRay:
|
||||
case CommandStrings.SACD:
|
||||
case CommandStrings.New: // Temporary command, to be removed later
|
||||
case CommandStrings.Rings:
|
||||
case CommandStrings.Dump:
|
||||
case CommandStrings.DumpNew: // Temporary command, to be removed later
|
||||
case CommandStrings.Refine:
|
||||
case CommandStrings.RefineNew: // Temporary command, to be removed later
|
||||
case CommandStrings.Verify:
|
||||
case CommandStrings.DVDKey:
|
||||
case CommandStrings.DVDIsoKey:
|
||||
case CommandStrings.Protection:
|
||||
case CommandStrings.Split:
|
||||
case CommandStrings.Hash:
|
||||
case CommandStrings.Info:
|
||||
case CommandStrings.Skeleton:
|
||||
ModeValues.Add(part);
|
||||
break;
|
||||
|
||||
// Default is either a flag or an invalid mode
|
||||
default:
|
||||
if (part.StartsWith("-"))
|
||||
{
|
||||
isFlag = true;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// If we had a flag, break out
|
||||
if (isFlag)
|
||||
break;
|
||||
}
|
||||
|
||||
// Loop through all auxiliary flags, if necessary
|
||||
for (int i = index; i < parts.Count; i++)
|
||||
{
|
||||
// Flag read-out values
|
||||
byte? byteValue = null;
|
||||
int? intValue = null;
|
||||
string? stringValue = null;
|
||||
|
||||
#region General
|
||||
|
||||
// Help
|
||||
ProcessFlagParameter(parts, FlagStrings.HelpShort, FlagStrings.HelpLong, ref i);
|
||||
|
||||
// Version
|
||||
ProcessFlagParameter(parts, FlagStrings.Version, ref i);
|
||||
|
||||
// Verbose
|
||||
ProcessFlagParameter(parts, FlagStrings.Verbose, ref i);
|
||||
|
||||
// Debug
|
||||
ProcessFlagParameter(parts, FlagStrings.Debug, ref i);
|
||||
|
||||
// Drive
|
||||
stringValue = ProcessStringParameter(parts, FlagStrings.Drive, ref i);
|
||||
if (!string.IsNullOrEmpty(stringValue))
|
||||
DriveValue = stringValue;
|
||||
|
||||
// Speed
|
||||
intValue = ProcessInt32Parameter(parts, FlagStrings.Speed, ref i);
|
||||
if (intValue != null && intValue != Int32.MinValue)
|
||||
SpeedValue = intValue;
|
||||
|
||||
// Retries
|
||||
intValue = ProcessInt32Parameter(parts, FlagStrings.Retries, ref i);
|
||||
if (intValue != null && intValue != Int32.MinValue)
|
||||
RetriesValue = intValue;
|
||||
|
||||
// Image Path
|
||||
stringValue = ProcessStringParameter(parts, FlagStrings.ImagePath, ref i);
|
||||
if (!string.IsNullOrEmpty(stringValue))
|
||||
ImagePathValue = $"\"{stringValue!.Trim('"')}\"";
|
||||
|
||||
// Image Name
|
||||
stringValue = ProcessStringParameter(parts, FlagStrings.ImageName, ref i);
|
||||
if (!string.IsNullOrEmpty(stringValue))
|
||||
ImageNameValue = $"\"{stringValue!.Trim('"')}\"";
|
||||
|
||||
// Overwrite
|
||||
ProcessFlagParameter(parts, FlagStrings.Overwrite, ref i);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Drive Configuration
|
||||
|
||||
// Drive Type
|
||||
stringValue = ProcessStringParameter(parts, FlagStrings.DriveType, ref i);
|
||||
if (!string.IsNullOrEmpty(stringValue))
|
||||
DriveTypeValue = stringValue;
|
||||
|
||||
// Drive Read Offset
|
||||
intValue = ProcessInt32Parameter(parts, FlagStrings.DriveReadOffset, ref i);
|
||||
if (intValue != null && intValue != Int32.MinValue)
|
||||
DriveReadOffsetValue = intValue;
|
||||
|
||||
// Drive C2 Shift
|
||||
intValue = ProcessInt32Parameter(parts, FlagStrings.DriveC2Shift, ref i);
|
||||
if (intValue != null && intValue != Int32.MinValue)
|
||||
DriveC2ShiftValue = intValue;
|
||||
|
||||
// Drive Pregap Start
|
||||
intValue = ProcessInt32Parameter(parts, FlagStrings.DrivePregapStart, ref i);
|
||||
if (intValue != null && intValue != Int32.MinValue)
|
||||
DrivePregapStartValue = intValue;
|
||||
|
||||
// Drive Read Method
|
||||
stringValue = ProcessStringParameter(parts, FlagStrings.DriveReadMethod, ref i);
|
||||
if (!string.IsNullOrEmpty(stringValue))
|
||||
DriveReadMethodValue = stringValue;
|
||||
|
||||
// Drive Sector Order
|
||||
stringValue = ProcessStringParameter(parts, FlagStrings.DriveSectorOrder, ref i);
|
||||
if (!string.IsNullOrEmpty(stringValue))
|
||||
DriveSectorOrderValue = stringValue;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Drive Specific
|
||||
|
||||
// Plextor Skip Leadin
|
||||
ProcessFlagParameter(parts, FlagStrings.PlextorSkipLeadin, ref i);
|
||||
|
||||
// Plextor Leadin Retries
|
||||
intValue = ProcessInt32Parameter(parts, FlagStrings.PlextorLeadinRetries, ref i);
|
||||
if (intValue != null && intValue != Int32.MinValue)
|
||||
PlextorLeadinRetriesValue = intValue;
|
||||
|
||||
// Asus Skip Leadout
|
||||
ProcessFlagParameter(parts, FlagStrings.AsusSkipLeadout, ref i);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Offset
|
||||
|
||||
// Force Offset
|
||||
intValue = ProcessInt32Parameter(parts, FlagStrings.ForceOffset, ref i);
|
||||
if (intValue != null && intValue != Int32.MinValue)
|
||||
ForceOffsetValue = intValue;
|
||||
|
||||
// Audio Silence Threshold
|
||||
intValue = ProcessInt32Parameter(parts, FlagStrings.AudioSilenceThreshold, ref i);
|
||||
if (intValue != null && intValue != Int32.MinValue)
|
||||
AudioSilenceThresholdValue = intValue;
|
||||
|
||||
// Correct Offset Shift
|
||||
ProcessFlagParameter(parts, FlagStrings.CorrectOffsetShift, ref i);
|
||||
|
||||
// Correct Shift Relocate
|
||||
ProcessFlagParameter(parts, FlagStrings.OffsetShiftRelocate, ref i);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Split
|
||||
|
||||
// Force Split
|
||||
ProcessFlagParameter(parts, FlagStrings.ForceSplit, ref i);
|
||||
|
||||
// Leave Unchanged
|
||||
ProcessFlagParameter(parts, FlagStrings.LeaveUnchanged, ref i);
|
||||
|
||||
// Force QTOC
|
||||
ProcessFlagParameter(parts, FlagStrings.ForceQTOC, ref i);
|
||||
|
||||
// Skip Fill
|
||||
byteValue = ProcessUInt8Parameter(parts, FlagStrings.SkipFill, ref i);
|
||||
if (byteValue != null && byteValue != Byte.MinValue)
|
||||
SkipFillValue = byteValue;
|
||||
|
||||
// ISO9660 Trim
|
||||
ProcessFlagParameter(parts, FlagStrings.ISO9660Trim, ref i);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Miscellaneous
|
||||
|
||||
// LBA Start
|
||||
intValue = ProcessInt32Parameter(parts, FlagStrings.LBAStart, ref i);
|
||||
if (intValue != null && intValue != Int32.MinValue)
|
||||
LBAStartValue = intValue;
|
||||
|
||||
// LBA End
|
||||
intValue = ProcessInt32Parameter(parts, FlagStrings.LBAEnd, ref i);
|
||||
if (intValue != null && intValue != Int32.MinValue)
|
||||
LBAEndValue = intValue;
|
||||
|
||||
// Refine Subchannel
|
||||
ProcessFlagParameter(parts, FlagStrings.RefineSubchannel, ref i);
|
||||
|
||||
// Skip
|
||||
stringValue = ProcessStringParameter(parts, FlagStrings.Skip, ref i);
|
||||
if (!string.IsNullOrEmpty(stringValue))
|
||||
SkipValue = stringValue;
|
||||
|
||||
// Dump Write Offset
|
||||
intValue = ProcessInt32Parameter(parts, FlagStrings.DumpWriteOffset, ref i);
|
||||
if (intValue != null && intValue != Int32.MinValue)
|
||||
DumpWriteOffsetValue = intValue;
|
||||
|
||||
// Dump Read Size
|
||||
intValue = ProcessInt32Parameter(parts, FlagStrings.DumpReadSize, ref i);
|
||||
if (intValue != null && intValue != Int32.MinValue)
|
||||
DumpReadSizeValue = intValue;
|
||||
|
||||
// Overread Leadout
|
||||
ProcessFlagParameter(parts, FlagStrings.OverreadLeadout, ref i);
|
||||
|
||||
// Force Unscrambled
|
||||
ProcessFlagParameter(parts, FlagStrings.ForceUnscrambled, ref i);
|
||||
|
||||
// Legacy Subs
|
||||
ProcessFlagParameter(parts, FlagStrings.LegacySubs, ref i);
|
||||
|
||||
// Disable CD Text
|
||||
ProcessFlagParameter(parts, FlagStrings.DisableCDText, ref i);
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
// If the image name was not set, set it with a default value
|
||||
if (string.IsNullOrEmpty(this.ImageNameValue))
|
||||
this.ImageNameValue = "track";
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
59
MPF.ExecutionContexts/Redumper/FlagStrings.cs
Normal file
59
MPF.ExecutionContexts/Redumper/FlagStrings.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
namespace MPF.ExecutionContexts.Redumper
|
||||
{
|
||||
/// <summary>
|
||||
/// Dumping flags for Redumper
|
||||
/// </summary>
|
||||
public static class FlagStrings
|
||||
{
|
||||
// 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";
|
||||
public const string Speed = "--speed";
|
||||
public const string Retries = "--retries";
|
||||
public const string ImagePath = "--image-path";
|
||||
public const string ImageName = "--image-name";
|
||||
public const string Overwrite = "--overwrite";
|
||||
|
||||
// Drive Configuration
|
||||
public const string DriveType = "--drive-type";
|
||||
public const string DriveReadOffset = "--drive-read-offset";
|
||||
public const string DriveC2Shift = "--drive-c2-shift";
|
||||
public const string DrivePregapStart = "--drive-pregap-start";
|
||||
public const string DriveReadMethod = "--drive-read-method";
|
||||
public const string DriveSectorOrder = "--drive-sector-order";
|
||||
|
||||
// Drive Specific
|
||||
public const string PlextorSkipLeadin = "--plextor-skip-leadin";
|
||||
public const string PlextorLeadinRetries = "--plextor-leadin-retries";
|
||||
public const string AsusSkipLeadout = "--asus-skip-leadout";
|
||||
|
||||
// Offset
|
||||
public const string ForceOffset = "--force-offset";
|
||||
public const string AudioSilenceThreshold = "--audio-silence-threshold";
|
||||
public const string CorrectOffsetShift = "--correct-offset-shift";
|
||||
public const string OffsetShiftRelocate = "--offset-shift-relocate";
|
||||
|
||||
// Split
|
||||
public const string ForceSplit = "--force-split";
|
||||
public const string LeaveUnchanged = "--leave-unchanged";
|
||||
public const string ForceQTOC = "--force-qtoc";
|
||||
public const string SkipFill = "--skip-fill";
|
||||
public const string ISO9660Trim = "--iso9660-trim";
|
||||
|
||||
// Miscellaneous
|
||||
public const string LBAStart = "--lba-start";
|
||||
public const string LBAEnd = "--lba-end";
|
||||
public const string RefineSubchannel = "--refine-subchannel";
|
||||
public const string Skip = "--skip";
|
||||
public const string 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";
|
||||
}
|
||||
}
|
||||
29
MPF.ExecutionContexts/Redumper/SettingConstants.cs
Normal file
29
MPF.ExecutionContexts/Redumper/SettingConstants.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
namespace MPF.ExecutionContexts.Redumper
|
||||
{
|
||||
public static class SettingConstants
|
||||
{
|
||||
public const string EnableDebug = "RedumperEnableDebug";
|
||||
public const bool EnableDebugDefault = false;
|
||||
|
||||
public const string EnableLeadinRetry = "RedumperEnableLeadinRetry";
|
||||
public const bool EnableLeadinRetryDefault = false;
|
||||
|
||||
public const string EnableVerbose = "RedumperEnableVerbose";
|
||||
public const bool EnableVerboseDefault = true;
|
||||
|
||||
public const string LeadinRetryCount = "RedumperLeadinRetryCount";
|
||||
public const int LeadinRetryCountDefault = 4;
|
||||
|
||||
public const string ReadMethod = "RedumperReadMethod";
|
||||
public static readonly string ReadMethodDefault = Redumper.ReadMethod.NONE.ToString();
|
||||
|
||||
public const string RereadCount = "RedumperRereadCount";
|
||||
public const int RereadCountDefault = 20;
|
||||
|
||||
public const string SectorOrder = "RedumperSectorOrder";
|
||||
public static readonly string SectorOrderDefault = Redumper.SectorOrder.NONE.ToString();
|
||||
|
||||
public const string UseGenericDriveType = "RedumperUseGenericDriveType";
|
||||
public const bool UseGenericDriveTypeDefault = false;
|
||||
}
|
||||
}
|
||||
68
MPF.Frontend/ComboBoxItems/Element.cs
Normal file
68
MPF.Frontend/ComboBoxItems/Element.cs
Normal file
@@ -0,0 +1,68 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace MPF.Frontend.ComboBoxItems
|
||||
{
|
||||
/// <summary>
|
||||
/// A generic combo box element
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Enum type representing the possible values</typeparam>
|
||||
public class Element<T> : IEquatable<Element<T>>, IElement where T : struct, Enum
|
||||
{
|
||||
private readonly T Data;
|
||||
|
||||
public Element(T data) => Data = data;
|
||||
|
||||
/// <summary>
|
||||
/// Allow elements to be used as their internal enum type
|
||||
/// </summary>
|
||||
/// <param name="item"></param>
|
||||
public static implicit operator T? (Element<T> item) => item?.Data;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string Name => EnumExtensions.GetLongName(Data);
|
||||
|
||||
public override string ToString() => Name;
|
||||
|
||||
/// <summary>
|
||||
/// Internal enum value
|
||||
/// </summary>
|
||||
public T Value => Data;
|
||||
|
||||
/// <summary>
|
||||
/// Determine if the item is selected or not
|
||||
/// </summary>
|
||||
/// <remarks>Only applies to CheckBox type</remarks>
|
||||
public bool IsChecked { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Generate all elements associated with the data enum type
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static IEnumerable<Element<T>> GenerateElements()
|
||||
{
|
||||
return Enum.GetValues(typeof(T))
|
||||
.OfType<T>()
|
||||
.Select(e => new Element<T>(e));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return Equals(obj as Element<T>);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Equals(Element<T>? other)
|
||||
{
|
||||
if (other == null)
|
||||
return false;
|
||||
|
||||
return Name == other.Name;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int GetHashCode() => base.GetHashCode();
|
||||
}
|
||||
}
|
||||
10
MPF.Frontend/ComboBoxItems/IElement.cs
Normal file
10
MPF.Frontend/ComboBoxItems/IElement.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace MPF.Frontend.ComboBoxItems
|
||||
{
|
||||
public interface IElement
|
||||
{
|
||||
/// <summary>
|
||||
/// Display name for the combo box element
|
||||
/// </summary>
|
||||
string Name { get; }
|
||||
}
|
||||
}
|
||||
101
MPF.Frontend/ComboBoxItems/RedumpSystemComboBoxItem.cs
Normal file
101
MPF.Frontend/ComboBoxItems/RedumpSystemComboBoxItem.cs
Normal file
@@ -0,0 +1,101 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using SabreTools.RedumpLib.Data;
|
||||
|
||||
namespace MPF.Frontend.ComboBoxItems
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a single item in the System combo box
|
||||
/// </summary>
|
||||
public class RedumpSystemComboBoxItem : IEquatable<RedumpSystemComboBoxItem>, IElement
|
||||
{
|
||||
private readonly object? Data;
|
||||
|
||||
public RedumpSystemComboBoxItem(RedumpSystem? system) => Data = system;
|
||||
public RedumpSystemComboBoxItem(SystemCategory? category) => Data = category;
|
||||
|
||||
public static implicit operator RedumpSystem?(RedumpSystemComboBoxItem item) => item.Data as RedumpSystem?;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string Name
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IsHeader)
|
||||
return "---------- " + (Data as SystemCategory?).LongName() + " ----------";
|
||||
else
|
||||
return (Data as RedumpSystem?).LongName() ?? "No system selected";
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString() => Name;
|
||||
|
||||
/// <summary>
|
||||
/// Internal enum value
|
||||
/// </summary>
|
||||
public RedumpSystem? Value => Data as RedumpSystem?;
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the item is a header value
|
||||
/// </summary>
|
||||
public bool IsHeader => Data is SystemCategory?;
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the item is a standard system value
|
||||
/// </summary>
|
||||
public bool IsSystem => Data is RedumpSystem?;
|
||||
|
||||
/// <summary>
|
||||
/// Generate all elements for the known system combo box
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static IEnumerable<RedumpSystemComboBoxItem> GenerateElements()
|
||||
{
|
||||
var knownSystems = Enum.GetValues(typeof(RedumpSystem))
|
||||
.OfType<RedumpSystem?>()
|
||||
.Where(s => !s.IsMarker() && s.GetCategory() != SystemCategory.NONE)
|
||||
.ToList();
|
||||
|
||||
Dictionary<SystemCategory, List<RedumpSystem?>> mapping = knownSystems
|
||||
.GroupBy(s => s.GetCategory())
|
||||
.ToDictionary(
|
||||
k => k.Key,
|
||||
v => v
|
||||
.OrderBy(s => s.LongName())
|
||||
.ToList()
|
||||
);
|
||||
|
||||
var systemsValues = new List<RedumpSystemComboBoxItem>
|
||||
{
|
||||
new RedumpSystemComboBoxItem((RedumpSystem?)null),
|
||||
};
|
||||
|
||||
foreach (var group in mapping)
|
||||
{
|
||||
systemsValues.Add(new RedumpSystemComboBoxItem(group.Key));
|
||||
group.Value.ForEach(system => systemsValues.Add(new RedumpSystemComboBoxItem(system)));
|
||||
}
|
||||
|
||||
return systemsValues;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return Equals(obj as RedumpSystemComboBoxItem);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Equals(RedumpSystemComboBoxItem? other)
|
||||
{
|
||||
if (other == null)
|
||||
return false;
|
||||
|
||||
return Value == other.Value;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int GetHashCode() => base.GetHashCode();
|
||||
}
|
||||
}
|
||||
316
MPF.Frontend/Drive.cs
Normal file
316
MPF.Frontend/Drive.cs
Normal file
@@ -0,0 +1,316 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
#if NET462_OR_GREATER || NETCOREAPP
|
||||
using Microsoft.Management.Infrastructure;
|
||||
using Microsoft.Management.Infrastructure.Generic;
|
||||
#endif
|
||||
using SabreTools.RedumpLib.Data;
|
||||
|
||||
namespace MPF.Frontend
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents information for a single drive
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// TODO: Can the Aaru models be used instead of the ones I've created here?
|
||||
/// </remarks>
|
||||
public class Drive
|
||||
{
|
||||
#region Fields
|
||||
|
||||
/// <summary>
|
||||
/// Represents drive type
|
||||
/// </summary>
|
||||
public InternalDriveType? InternalDriveType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Drive partition format
|
||||
/// </summary>
|
||||
public string? DriveFormat { get; private set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Windows drive path
|
||||
/// </summary>
|
||||
public string? Name { get; private set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Represents if Windows has marked the drive as active
|
||||
/// </summary>
|
||||
public bool MarkedActive { get; private set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the total size of the drive
|
||||
/// </summary>
|
||||
public long TotalSize { get; private set; } = default;
|
||||
|
||||
/// <summary>
|
||||
/// Media label as read by Windows
|
||||
/// </summary>
|
||||
/// <remarks>The try/catch is needed because Windows will throw an exception if the drive is not marked as active</remarks>
|
||||
public string? VolumeLabel { get; private set; } = null;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Derived Fields
|
||||
|
||||
/// <summary>
|
||||
/// Read-only access to the drive letter
|
||||
/// </summary>
|
||||
/// <remarks>Should only be used in UI applications</remarks>
|
||||
public char? Letter => Name?[0] ?? '\0';
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Protected constructor
|
||||
/// </summary>
|
||||
protected Drive() { }
|
||||
|
||||
/// <summary>
|
||||
/// Create a new Drive object from a drive type and device path
|
||||
/// </summary>
|
||||
/// <param name="driveType">InternalDriveType value representing the drive type</param>
|
||||
/// <param name="devicePath">Path to the device according to the local machine</param>
|
||||
public static Drive? Create(InternalDriveType? driveType, string devicePath)
|
||||
{
|
||||
// Create a new, empty drive object
|
||||
var drive = new Drive()
|
||||
{
|
||||
InternalDriveType = driveType,
|
||||
};
|
||||
|
||||
// If we have an invalid device path, return null
|
||||
if (string.IsNullOrEmpty(devicePath))
|
||||
return null;
|
||||
|
||||
// Sanitize a Windows-formatted long device path
|
||||
if (devicePath.StartsWith("\\\\.\\"))
|
||||
devicePath = devicePath.Substring("\\\\.\\".Length);
|
||||
|
||||
// Create and validate the drive info object
|
||||
var driveInfo = new DriveInfo(devicePath);
|
||||
if (driveInfo == null || driveInfo == default)
|
||||
return null;
|
||||
|
||||
// Fill in the rest of the data
|
||||
drive.PopulateFromDriveInfo(driveInfo);
|
||||
|
||||
return drive;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Populate all fields from a DriveInfo object
|
||||
/// </summary>
|
||||
/// <param name="driveInfo">DriveInfo object to populate from</param>
|
||||
private void PopulateFromDriveInfo(DriveInfo? driveInfo)
|
||||
{
|
||||
// If we have an invalid DriveInfo, just return
|
||||
if (driveInfo == null || driveInfo == default)
|
||||
return;
|
||||
|
||||
// Populate the data fields
|
||||
Name = driveInfo.Name;
|
||||
MarkedActive = driveInfo.IsReady;
|
||||
if (MarkedActive)
|
||||
{
|
||||
DriveFormat = driveInfo.DriveFormat;
|
||||
TotalSize = driveInfo.TotalSize;
|
||||
VolumeLabel = driveInfo.VolumeLabel;
|
||||
}
|
||||
else
|
||||
{
|
||||
DriveFormat = string.Empty;
|
||||
TotalSize = default;
|
||||
VolumeLabel = string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
#region Public Functionality
|
||||
|
||||
/// <summary>
|
||||
/// Create a list of active drives matched to their volume labels
|
||||
/// </summary>
|
||||
/// <param name="ignoreFixedDrives">True to ignore fixed drives from population, false otherwise</param>
|
||||
/// <returns>Active drives, matched to labels, if possible</returns>
|
||||
public static List<Drive> CreateListOfDrives(bool ignoreFixedDrives)
|
||||
{
|
||||
var drives = GetDriveList(ignoreFixedDrives);
|
||||
drives = [.. drives.OrderBy(i => i == null ? "\0" : i.Name)];
|
||||
return drives;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the current media type from drive letter
|
||||
/// </summary>
|
||||
/// <param name="system">Currently selected system</param>
|
||||
/// <returns>The detected media type, if possible</returns>
|
||||
public MediaType? GetMediaType(RedumpSystem? system)
|
||||
{
|
||||
// Take care of the non-optical stuff first
|
||||
switch (InternalDriveType)
|
||||
{
|
||||
case Frontend.InternalDriveType.Floppy:
|
||||
return MediaType.FloppyDisk;
|
||||
case Frontend.InternalDriveType.HardDisk:
|
||||
return MediaType.HardDisk;
|
||||
case Frontend.InternalDriveType.Removable:
|
||||
return MediaType.FlashDrive;
|
||||
}
|
||||
|
||||
// Some systems should default to certain media types
|
||||
switch (system)
|
||||
{
|
||||
// CD
|
||||
case RedumpSystem.Panasonic3DOInteractiveMultiplayer:
|
||||
case RedumpSystem.PhilipsCDi:
|
||||
case RedumpSystem.SegaDreamcast:
|
||||
case RedumpSystem.SegaSaturn:
|
||||
case RedumpSystem.SonyPlayStation:
|
||||
case RedumpSystem.VideoCD:
|
||||
return MediaType.CDROM;
|
||||
|
||||
// DVD
|
||||
case RedumpSystem.DVDAudio:
|
||||
case RedumpSystem.DVDVideo:
|
||||
case RedumpSystem.MicrosoftXbox:
|
||||
case RedumpSystem.MicrosoftXbox360:
|
||||
return MediaType.DVD;
|
||||
|
||||
// HD-DVD
|
||||
case RedumpSystem.HDDVDVideo:
|
||||
return MediaType.HDDVD;
|
||||
|
||||
// Blu-ray
|
||||
case RedumpSystem.BDVideo:
|
||||
case RedumpSystem.MicrosoftXboxOne:
|
||||
case RedumpSystem.MicrosoftXboxSeriesXS:
|
||||
case RedumpSystem.SonyPlayStation3:
|
||||
case RedumpSystem.SonyPlayStation4:
|
||||
case RedumpSystem.SonyPlayStation5:
|
||||
return MediaType.BluRay;
|
||||
|
||||
// GameCube
|
||||
case RedumpSystem.NintendoGameCube:
|
||||
return MediaType.NintendoGameCubeGameDisc;
|
||||
|
||||
// Wii
|
||||
case RedumpSystem.NintendoWii:
|
||||
return MediaType.NintendoWiiOpticalDisc;
|
||||
|
||||
// WiiU
|
||||
case RedumpSystem.NintendoWiiU:
|
||||
return MediaType.NintendoWiiUOpticalDisc;
|
||||
|
||||
// PSP
|
||||
case RedumpSystem.SonyPlayStationPortable:
|
||||
return MediaType.UMD;
|
||||
}
|
||||
|
||||
// Handle optical media by size and filesystem
|
||||
if (TotalSize >= 0 && TotalSize <= 800_000_000 && (DriveFormat == "CDFS" || DriveFormat == "UDF"))
|
||||
return MediaType.CDROM;
|
||||
else if (TotalSize > 800_000_000 && TotalSize <= 8_540_000_000 && (DriveFormat == "CDFS" || DriveFormat == "UDF"))
|
||||
return MediaType.DVD;
|
||||
else if (TotalSize > 8_540_000_000)
|
||||
return MediaType.BluRay;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Refresh the current drive information based on path
|
||||
/// </summary>
|
||||
public void RefreshDrive()
|
||||
{
|
||||
var driveInfo = DriveInfo.GetDrives().FirstOrDefault(d => d?.Name == Name);
|
||||
PopulateFromDriveInfo(driveInfo);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Helpers
|
||||
|
||||
/// <summary>
|
||||
/// Get all current attached Drives
|
||||
/// </summary>
|
||||
/// <param name="ignoreFixedDrives">True to ignore fixed drives from population, false otherwise</param>
|
||||
/// <returns>List of drives, null on error</returns>
|
||||
/// <remarks>
|
||||
/// https://stackoverflow.com/questions/3060796/how-to-distinguish-between-usb-and-floppy-devices?utm_medium=organic&utm_source=google_rich_qa&utm_campaign=google_rich_qa
|
||||
/// https://msdn.microsoft.com/en-us/library/aa394173(v=vs.85).aspx
|
||||
/// </remarks>
|
||||
private static List<Drive> GetDriveList(bool ignoreFixedDrives)
|
||||
{
|
||||
var desiredDriveTypes = new List<DriveType>() { DriveType.CDRom };
|
||||
if (!ignoreFixedDrives)
|
||||
{
|
||||
desiredDriveTypes.Add(DriveType.Fixed);
|
||||
desiredDriveTypes.Add(DriveType.Removable);
|
||||
}
|
||||
|
||||
// TODO: Reduce reliance on `DriveInfo`
|
||||
// https://github.com/aaru-dps/Aaru/blob/5164a154e2145941472f2ee0aeb2eff3338ecbb3/Aaru.Devices/Windows/ListDevices.cs#L66
|
||||
|
||||
// Create an output drive list
|
||||
var drives = new List<Drive>();
|
||||
|
||||
// Get all standard supported drive types
|
||||
try
|
||||
{
|
||||
drives = DriveInfo.GetDrives()
|
||||
.Where(d => desiredDriveTypes.Contains(d.DriveType))
|
||||
.Select(d => Create(ToInternalDriveType(d.DriveType), d.Name) ?? new Drive())
|
||||
.ToList();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return drives;
|
||||
}
|
||||
|
||||
// Find and update all floppy drives
|
||||
#if NET462_OR_GREATER || NETCOREAPP
|
||||
try
|
||||
{
|
||||
CimSession session = CimSession.Create(null);
|
||||
var collection = session.QueryInstances("root\\CIMV2", "WQL", "SELECT * FROM Win32_LogicalDisk");
|
||||
|
||||
foreach (CimInstance instance in collection)
|
||||
{
|
||||
CimKeyedCollection<CimProperty> properties = instance.CimInstanceProperties;
|
||||
uint? mediaType = properties["MediaType"]?.Value as uint?;
|
||||
if (mediaType != null && ((mediaType > 0 && mediaType < 11) || (mediaType > 12 && mediaType < 22)))
|
||||
{
|
||||
char devId = (properties["Caption"].Value as string ?? string.Empty)[0];
|
||||
drives.ForEach(d => { if (d?.Name != null && d.Name[0] == devId) { d.InternalDriveType = Frontend.InternalDriveType.Floppy; } });
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// No-op
|
||||
}
|
||||
#endif
|
||||
|
||||
return drives;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert drive type to internal version, if possible
|
||||
/// </summary>
|
||||
/// <param name="driveType">DriveType value to check</param>
|
||||
/// <returns>InternalDriveType, if possible, null on error</returns>
|
||||
internal static InternalDriveType? ToInternalDriveType(DriveType driveType)
|
||||
{
|
||||
return driveType switch
|
||||
{
|
||||
DriveType.CDRom => (InternalDriveType?)Frontend.InternalDriveType.Optical,
|
||||
DriveType.Fixed => (InternalDriveType?)Frontend.InternalDriveType.HardDisk,
|
||||
DriveType.Removable => (InternalDriveType?)Frontend.InternalDriveType.Removable,
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
862
MPF.Frontend/DumpEnvironment.cs
Normal file
862
MPF.Frontend/DumpEnvironment.cs
Normal file
@@ -0,0 +1,862 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
#if NET40
|
||||
using System.Threading;
|
||||
#endif
|
||||
using System.Threading.Tasks;
|
||||
using BinaryObjectScanner;
|
||||
using MPF.ExecutionContexts;
|
||||
using MPF.Frontend.Tools;
|
||||
using MPF.Processors;
|
||||
using Newtonsoft.Json;
|
||||
using SabreTools.RedumpLib;
|
||||
using SabreTools.RedumpLib.Data;
|
||||
using Formatting = Newtonsoft.Json.Formatting;
|
||||
|
||||
namespace MPF.Frontend
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the state of all settings to be used during dumping
|
||||
/// </summary>
|
||||
public class DumpEnvironment
|
||||
{
|
||||
#region Output paths
|
||||
|
||||
/// <summary>
|
||||
/// Base output file path to write files to
|
||||
/// </summary>
|
||||
public string OutputPath { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region UI information
|
||||
|
||||
/// <summary>
|
||||
/// Drive object representing the current drive
|
||||
/// </summary>
|
||||
private Drive? _drive;
|
||||
|
||||
/// <summary>
|
||||
/// ExecutionContext object representing how to invoke the internal program
|
||||
/// </summary>
|
||||
private BaseExecutionContext? _executionContext;
|
||||
|
||||
/// <summary>
|
||||
/// Currently selected dumping program
|
||||
/// </summary>
|
||||
private readonly InternalProgram _internalProgram;
|
||||
|
||||
/// <summary>
|
||||
/// Options object representing user-defined options
|
||||
/// </summary>
|
||||
private readonly Frontend.Options _options;
|
||||
|
||||
/// <summary>
|
||||
/// Processor object representing how to process the outputs
|
||||
/// </summary>
|
||||
private BaseProcessor? _processor;
|
||||
|
||||
/// <summary>
|
||||
/// Currently selected system
|
||||
/// </summary>
|
||||
private readonly RedumpSystem? _system;
|
||||
|
||||
/// <summary>
|
||||
/// Currently selected media type
|
||||
/// </summary>
|
||||
private readonly MediaType? _type;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Passthrough Fields
|
||||
|
||||
/// <inheritdoc cref="BaseExecutionContext.InputPath"/>
|
||||
public string? ContextInputPath => _executionContext?.InputPath;
|
||||
|
||||
/// <inheritdoc cref="BaseExecutionContext.OutputPath"/>
|
||||
public string? ContextOutputPath => _executionContext?.OutputPath;
|
||||
|
||||
/// <inheritdoc cref="Drive.MarkedActive/>
|
||||
public bool DriveMarkedActive => _drive?.MarkedActive ?? false;
|
||||
|
||||
/// <inheritdoc cref="Drive.Name/>
|
||||
public string? DriveName => _drive?.Name;
|
||||
|
||||
/// <inheritdoc cref="BaseExecutionContext.Speed"/>
|
||||
public int? Speed
|
||||
{
|
||||
get => _executionContext?.Speed;
|
||||
set
|
||||
{
|
||||
if (_executionContext != null)
|
||||
_executionContext.Speed = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Extensions.LongName(RedumpSystem?)/>
|
||||
public string? SystemName => _system.LongName();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Event Handlers
|
||||
|
||||
/// <summary>
|
||||
/// Generic way of reporting a message
|
||||
/// </summary>
|
||||
public EventHandler<StringEventArgs>? ReportStatus;
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for a full DumpEnvironment object from user information
|
||||
/// </summary>
|
||||
/// <param name="options"></param>
|
||||
/// <param name="outputPath"></param>
|
||||
/// <param name="drive"></param>
|
||||
/// <param name="system"></param>
|
||||
/// <param name="type"></param>
|
||||
/// <param name="internalProgram"></param>
|
||||
/// <param name="parameters"></param>
|
||||
public DumpEnvironment(Frontend.Options options,
|
||||
string? outputPath,
|
||||
Drive? drive,
|
||||
RedumpSystem? system,
|
||||
MediaType? type,
|
||||
InternalProgram? internalProgram,
|
||||
string? parameters)
|
||||
{
|
||||
// Set options object
|
||||
_options = options;
|
||||
|
||||
// Output paths
|
||||
OutputPath = FrontendTool.NormalizeOutputPaths(outputPath, false);
|
||||
|
||||
// UI information
|
||||
_drive = drive;
|
||||
_system = system ?? options.DefaultSystem;
|
||||
_type = type ?? MediaType.NONE;
|
||||
_internalProgram = internalProgram ?? options.InternalProgram;
|
||||
|
||||
// Dumping program
|
||||
SetExecutionContext(parameters);
|
||||
SetProcessor();
|
||||
}
|
||||
|
||||
#region Internal Program Management
|
||||
|
||||
/// <summary>
|
||||
/// Check output path for matching logs from all dumping programs
|
||||
/// </summary>
|
||||
public InternalProgram? CheckForMatchingProgram(string? outputDirectory, string outputFilename)
|
||||
{
|
||||
// If a complete dump exists from a different program
|
||||
InternalProgram? programFound = null;
|
||||
if (programFound == null && _internalProgram != InternalProgram.Aaru)
|
||||
{
|
||||
var processor = new Processors.Aaru(_system, _type);
|
||||
var missingFiles = processor.FoundAllFiles(outputDirectory, outputFilename);
|
||||
if (missingFiles.Count == 0)
|
||||
programFound = InternalProgram.Aaru;
|
||||
}
|
||||
if (programFound == null && _internalProgram != InternalProgram.DiscImageCreator)
|
||||
{
|
||||
var processor = new Processors.DiscImageCreator(_system, _type);
|
||||
var missingFiles = processor.FoundAllFiles(outputDirectory, outputFilename);
|
||||
if (missingFiles.Count == 0)
|
||||
programFound = InternalProgram.DiscImageCreator;
|
||||
}
|
||||
if (programFound == null && _internalProgram != InternalProgram.Redumper)
|
||||
{
|
||||
var processor = new Processors.Redumper(_system, _type);
|
||||
var missingFiles = processor.FoundAllFiles(outputDirectory, outputFilename);
|
||||
if (missingFiles.Count == 0)
|
||||
programFound = InternalProgram.Redumper;
|
||||
}
|
||||
|
||||
return programFound;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the parameters object based on the internal program and parameters string
|
||||
/// </summary>
|
||||
/// <param name="parameters">String representation of the parameters</param>
|
||||
public bool SetExecutionContext(string? parameters)
|
||||
{
|
||||
_executionContext = _internalProgram switch
|
||||
{
|
||||
InternalProgram.Aaru => new ExecutionContexts.Aaru.ExecutionContext(parameters) { ExecutablePath = _options.AaruPath },
|
||||
InternalProgram.DiscImageCreator => new ExecutionContexts.DiscImageCreator.ExecutionContext(parameters) { ExecutablePath = _options.DiscImageCreatorPath },
|
||||
InternalProgram.Redumper => new ExecutionContexts.Redumper.ExecutionContext(parameters) { ExecutablePath = _options.RedumperPath },
|
||||
|
||||
// If no dumping program found, set to null
|
||||
InternalProgram.NONE => null,
|
||||
_ => null,
|
||||
};
|
||||
|
||||
// Set system, type, and drive
|
||||
if (_executionContext != null)
|
||||
{
|
||||
_executionContext.System = _system;
|
||||
_executionContext.Type = _type;
|
||||
|
||||
// Set some parameters, if not already set
|
||||
OutputPath ??= _executionContext.OutputPath!;
|
||||
_drive ??= Drive.Create(InternalDriveType.Optical, _executionContext.InputPath!);
|
||||
}
|
||||
|
||||
return _executionContext != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the processor object based on the internal program
|
||||
/// </summary>
|
||||
public bool SetProcessor()
|
||||
{
|
||||
_processor = _internalProgram switch
|
||||
{
|
||||
InternalProgram.Aaru => new Processors.Aaru(_system, _type),
|
||||
InternalProgram.CleanRip => new CleanRip(_system, _type),
|
||||
InternalProgram.DiscImageCreator => new DiscImageCreator(_system, _type),
|
||||
InternalProgram.PS3CFW => new PS3CFW(_system, _type),
|
||||
InternalProgram.Redumper => new Redumper(_system, _type),
|
||||
InternalProgram.UmdImageCreator => new UmdImageCreator(_system, _type),
|
||||
InternalProgram.XboxBackupCreator => new XboxBackupCreator(_system, _type),
|
||||
|
||||
// If no dumping program found, set to null
|
||||
InternalProgram.NONE => null,
|
||||
_ => null,
|
||||
};
|
||||
|
||||
return _processor != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the full parameter string for either DiscImageCreator or Aaru
|
||||
/// </summary>
|
||||
/// <param name="driveSpeed">Nullable int representing the drive speed</param>
|
||||
/// <returns>String representing the params, null on error</returns>
|
||||
public string? GetFullParameters(int? driveSpeed)
|
||||
{
|
||||
// Populate with the correct params for inputs (if we're not on the default option)
|
||||
if (_system != null && _type != MediaType.NONE)
|
||||
{
|
||||
// If drive letter is invalid, skip this
|
||||
if (_drive == null)
|
||||
return null;
|
||||
|
||||
// Set the proper parameters
|
||||
_executionContext = _internalProgram switch
|
||||
{
|
||||
InternalProgram.Aaru => new ExecutionContexts.Aaru.ExecutionContext(_system, _type, _drive.Name, OutputPath, driveSpeed, _options.Settings),
|
||||
InternalProgram.DiscImageCreator => new ExecutionContexts.DiscImageCreator.ExecutionContext(_system, _type, _drive.Name, OutputPath, driveSpeed, _options.Settings),
|
||||
InternalProgram.Redumper => new ExecutionContexts.Redumper.ExecutionContext(_system, _type, _drive.Name, OutputPath, driveSpeed, _options.Settings),
|
||||
|
||||
// If no dumping program found, set to null
|
||||
InternalProgram.NONE => null,
|
||||
_ => null,
|
||||
};
|
||||
|
||||
// Generate and return the param string
|
||||
return _executionContext?.GenerateParameters();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Passthrough Functionality
|
||||
|
||||
/// <inheritdoc cref="Extensions.DetectedByWindows(RedumpSystem?)"/>
|
||||
public bool DetectedByWindows() => _system.DetectedByWindows();
|
||||
|
||||
/// <summary>
|
||||
/// Determine if the media supports drive speeds
|
||||
/// </summary>
|
||||
/// <param name="type">MediaType value to check</param>
|
||||
/// <returns>True if the media has variable dumping speeds, false otherwise</returns>
|
||||
public bool DoesSupportDriveSpeed()
|
||||
{
|
||||
return _type switch
|
||||
{
|
||||
MediaType.CDROM
|
||||
or MediaType.DVD
|
||||
or MediaType.GDROM
|
||||
or MediaType.HDDVD
|
||||
or MediaType.BluRay
|
||||
or MediaType.NintendoGameCubeGameDisc
|
||||
or MediaType.NintendoWiiOpticalDisc => true,
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="BaseProcessor.FoundAllFiles(string?, string)"/>
|
||||
public bool FoundAllFiles(string? outputDirectory, string outputFilename)
|
||||
{
|
||||
if (_processor == null)
|
||||
return false;
|
||||
|
||||
return _processor.FoundAllFiles(outputDirectory, outputFilename).Count == 0;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="BaseExecutionContext.GetDefaultExtension(MediaType?)"/>
|
||||
public string? GetDefaultExtension(MediaType? mediaType)
|
||||
{
|
||||
if (_executionContext == null)
|
||||
return null;
|
||||
|
||||
return _executionContext.GetDefaultExtension(mediaType);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="BaseExecutionContext.GetMediaType()"/>
|
||||
public MediaType? GetMediaType()
|
||||
{
|
||||
if (_executionContext == null)
|
||||
return null;
|
||||
|
||||
return _executionContext.GetMediaType();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify that, given a system and a media type, they are correct
|
||||
/// </summary>
|
||||
public ResultEventArgs GetSupportStatus()
|
||||
{
|
||||
// No system chosen, update status
|
||||
if (_system == null)
|
||||
return ResultEventArgs.Failure("Please select a valid system");
|
||||
|
||||
// If we're on an unsupported type, update the status accordingly
|
||||
return _type switch
|
||||
{
|
||||
// Fully supported types
|
||||
MediaType.BluRay
|
||||
or MediaType.CDROM
|
||||
or MediaType.DVD
|
||||
or MediaType.FloppyDisk
|
||||
or MediaType.HardDisk
|
||||
or MediaType.CompactFlash
|
||||
or MediaType.SDCard
|
||||
or MediaType.FlashDrive
|
||||
or MediaType.HDDVD => ResultEventArgs.Success($"{_type.LongName()} ready to dump"),
|
||||
|
||||
// Partially supported types
|
||||
MediaType.GDROM
|
||||
or MediaType.NintendoGameCubeGameDisc
|
||||
or MediaType.NintendoWiiOpticalDisc => ResultEventArgs.Success($"{_type.LongName()} partially supported for dumping"),
|
||||
|
||||
// Special case for other supported tools
|
||||
MediaType.UMD => ResultEventArgs.Failure($"{_type.LongName()} supported for submission info parsing"),
|
||||
|
||||
// Specifically unknown type
|
||||
MediaType.NONE => ResultEventArgs.Failure($"Please select a valid media type"),
|
||||
|
||||
// Undumpable but recognized types
|
||||
_ => ResultEventArgs.Failure($"{_type.LongName()} media are not supported for dumping"),
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="BaseExecutionContext.IsDumpingCommand()"/>
|
||||
public bool IsDumpingCommand()
|
||||
{
|
||||
if (_executionContext == null)
|
||||
return false;
|
||||
|
||||
return _executionContext.IsDumpingCommand();
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Drive.RefreshDrive"/>
|
||||
public void RefreshDrive() => _drive?.RefreshDrive();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Dumping
|
||||
|
||||
/// <summary>
|
||||
/// Cancel an in-progress dumping process
|
||||
/// </summary>
|
||||
public void CancelDumping() => _executionContext?.KillInternalProgram();
|
||||
|
||||
/// <summary>
|
||||
/// Execute the initial invocation of the dumping programs
|
||||
/// </summary>
|
||||
/// <param name="progress">Optional result progress callback</param>
|
||||
public async Task<ResultEventArgs> Run(IProgress<ResultEventArgs>? progress = null)
|
||||
{
|
||||
// If we don't have parameters
|
||||
if (_executionContext == null)
|
||||
return ResultEventArgs.Failure("Error! Current configuration is not supported!");
|
||||
|
||||
// Check that we have the basics for dumping
|
||||
ResultEventArgs result = IsValidForDump();
|
||||
if (!result)
|
||||
return result;
|
||||
|
||||
// Execute internal tool
|
||||
progress?.Report(ResultEventArgs.Success($"Executing {_internalProgram}... please wait!"));
|
||||
|
||||
var directoryName = Path.GetDirectoryName(OutputPath);
|
||||
if (!string.IsNullOrEmpty(directoryName))
|
||||
Directory.CreateDirectory(directoryName);
|
||||
|
||||
#if NET40
|
||||
await Task.Factory.StartNew(() => { _executionContext.ExecuteInternalProgram(); return true; });
|
||||
#else
|
||||
await Task.Run(_executionContext.ExecuteInternalProgram);
|
||||
#endif
|
||||
progress?.Report(ResultEventArgs.Success($"{_internalProgram} has finished!"));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify that the current environment has a complete dump and create submission info is possible
|
||||
/// </summary>
|
||||
/// <param name="resultProgress">Optional result progress callback</param>
|
||||
/// <param name="protectionProgress">Optional protection progress callback</param>
|
||||
/// <param name="processUserInfo">Optional user prompt to deal with submission information</param>
|
||||
/// <param name="seedInfo">A seed SubmissionInfo object that contains user data</param>
|
||||
/// <returns>Result instance with the outcome</returns>
|
||||
public async Task<ResultEventArgs> VerifyAndSaveDumpOutput(
|
||||
IProgress<ResultEventArgs>? resultProgress = null,
|
||||
IProgress<ProtectionProgress>? protectionProgress = null,
|
||||
ProcessUserInfoDelegate? processUserInfo = null,
|
||||
SubmissionInfo? seedInfo = null)
|
||||
{
|
||||
if (_processor == null)
|
||||
return ResultEventArgs.Failure("Error! Current configuration is not supported!");
|
||||
|
||||
resultProgress?.Report(ResultEventArgs.Success("Gathering submission information... please wait!"));
|
||||
|
||||
// Get the output directory and filename separately
|
||||
var outputDirectory = Path.GetDirectoryName(OutputPath);
|
||||
var outputFilename = Path.GetFileName(OutputPath);
|
||||
|
||||
// Check to make sure that the output had all the correct files
|
||||
List<string> missingFiles = _processor.FoundAllFiles(outputDirectory, outputFilename);
|
||||
if (missingFiles.Count > 0)
|
||||
{
|
||||
resultProgress?.Report(ResultEventArgs.Failure($"There were files missing from the output:\n{string.Join("\n", [.. missingFiles])}"));
|
||||
return ResultEventArgs.Failure("Error! Please check output directory as dump may be incomplete!");
|
||||
}
|
||||
|
||||
// Extract the information from the output files
|
||||
resultProgress?.Report(ResultEventArgs.Success("Extracting output information from output files..."));
|
||||
var submissionInfo = await SubmissionGenerator.ExtractOutputInformation(
|
||||
OutputPath,
|
||||
_drive,
|
||||
_system,
|
||||
_type,
|
||||
_options,
|
||||
_processor,
|
||||
resultProgress,
|
||||
protectionProgress);
|
||||
resultProgress?.Report(ResultEventArgs.Success("Extracting information complete!"));
|
||||
|
||||
// Inject seed submission info data, if necessary
|
||||
if (seedInfo != null)
|
||||
{
|
||||
resultProgress?.Report(ResultEventArgs.Success("Injecting user-supplied information..."));
|
||||
Builder.InjectSubmissionInformation(submissionInfo, seedInfo);
|
||||
resultProgress?.Report(ResultEventArgs.Success("Information injection complete!"));
|
||||
}
|
||||
|
||||
// Get user-modifiable information if confugured to
|
||||
if (_options.PromptForDiscInformation && processUserInfo != null)
|
||||
{
|
||||
resultProgress?.Report(ResultEventArgs.Success("Waiting for additional disc information..."));
|
||||
|
||||
bool? filledInfo = processUserInfo(ref submissionInfo);
|
||||
|
||||
if (filledInfo == true)
|
||||
resultProgress?.Report(ResultEventArgs.Success("Additional disc information added!"));
|
||||
else
|
||||
resultProgress?.Report(ResultEventArgs.Success("Disc information skipped!"));
|
||||
}
|
||||
|
||||
// Process special fields for site codes
|
||||
resultProgress?.Report(ResultEventArgs.Success("Processing site codes..."));
|
||||
Formatter.ProcessSpecialFields(submissionInfo);
|
||||
resultProgress?.Report(ResultEventArgs.Success("Processing complete!"));
|
||||
|
||||
// Format the information for the text output
|
||||
resultProgress?.Report(ResultEventArgs.Success("Formatting information..."));
|
||||
var formattedValues = Formatter.FormatOutputData(submissionInfo, _options.EnableRedumpCompatibility, out string? formatResult);
|
||||
if (formattedValues == null)
|
||||
resultProgress?.Report(ResultEventArgs.Failure(formatResult));
|
||||
else
|
||||
resultProgress?.Report(ResultEventArgs.Success(formatResult));
|
||||
|
||||
// Get the filename suffix for auto-generated files
|
||||
var filenameSuffix = _options.AddFilenameSuffix ? Path.GetFileNameWithoutExtension(outputFilename) : null;
|
||||
|
||||
// Write the text output
|
||||
resultProgress?.Report(ResultEventArgs.Success("Writing submission information file..."));
|
||||
bool txtSuccess = WriteOutputData(outputDirectory, filenameSuffix, formattedValues, out string txtResult);
|
||||
if (txtSuccess)
|
||||
resultProgress?.Report(ResultEventArgs.Success(txtResult));
|
||||
else
|
||||
resultProgress?.Report(ResultEventArgs.Failure(txtResult));
|
||||
|
||||
// Write the copy protection output
|
||||
if (submissionInfo?.CopyProtection?.FullProtections != null && submissionInfo.CopyProtection.FullProtections.Any())
|
||||
{
|
||||
if (_options.ScanForProtection)
|
||||
{
|
||||
resultProgress?.Report(ResultEventArgs.Success("Writing protection information file..."));
|
||||
bool scanSuccess = WriteProtectionData(outputDirectory, filenameSuffix, submissionInfo, _options.HideDriveLetters);
|
||||
if (scanSuccess)
|
||||
resultProgress?.Report(ResultEventArgs.Success("Writing complete!"));
|
||||
else
|
||||
resultProgress?.Report(ResultEventArgs.Failure("Writing could not complete!"));
|
||||
}
|
||||
}
|
||||
|
||||
// Write the JSON output, if required
|
||||
if (_options.OutputSubmissionJSON)
|
||||
{
|
||||
resultProgress?.Report(ResultEventArgs.Success($"Writing submission information JSON file{(_options.IncludeArtifacts ? " with artifacts" : string.Empty)}..."));
|
||||
bool jsonSuccess = WriteOutputData(outputDirectory, filenameSuffix, submissionInfo, _options.IncludeArtifacts);
|
||||
if (jsonSuccess)
|
||||
resultProgress?.Report(ResultEventArgs.Success("Writing complete!"));
|
||||
else
|
||||
resultProgress?.Report(ResultEventArgs.Failure("Writing could not complete!"));
|
||||
}
|
||||
|
||||
// Compress the logs, if required
|
||||
if (_options.CompressLogFiles)
|
||||
{
|
||||
resultProgress?.Report(ResultEventArgs.Success("Compressing log files..."));
|
||||
if (_processor == null)
|
||||
{
|
||||
resultProgress?.Report(ResultEventArgs.Failure("No processor provided!"));
|
||||
}
|
||||
else
|
||||
{
|
||||
bool compressSuccess = _processor.CompressLogFiles(outputDirectory, filenameSuffix, outputFilename, out string compressResult);
|
||||
if (compressSuccess)
|
||||
resultProgress?.Report(ResultEventArgs.Success(compressResult));
|
||||
else
|
||||
resultProgress?.Report(ResultEventArgs.Failure(compressResult));
|
||||
}
|
||||
}
|
||||
|
||||
// Delete unnecessary files, if required
|
||||
if (_options.DeleteUnnecessaryFiles)
|
||||
{
|
||||
resultProgress?.Report(ResultEventArgs.Success("Deleting unnecessary files..."));
|
||||
if (_processor == null)
|
||||
{
|
||||
resultProgress?.Report(ResultEventArgs.Failure("No processor provided!"));
|
||||
}
|
||||
else
|
||||
{
|
||||
bool deleteSuccess = _processor.DeleteUnnecessaryFiles(outputDirectory, outputFilename, out string deleteResult);
|
||||
if (deleteSuccess)
|
||||
resultProgress?.Report(ResultEventArgs.Success(deleteResult));
|
||||
else
|
||||
resultProgress?.Report(ResultEventArgs.Failure(deleteResult));
|
||||
}
|
||||
}
|
||||
|
||||
// Create PS3 IRD, if required
|
||||
if (_options.CreateIRDAfterDumping && _system == RedumpSystem.SonyPlayStation3 && _type == MediaType.BluRay)
|
||||
{
|
||||
resultProgress?.Report(ResultEventArgs.Success("Creating IRD... please wait!"));
|
||||
bool deleteSuccess = await WriteIRD(OutputPath, submissionInfo?.Extras?.DiscKey, submissionInfo?.Extras?.DiscID, submissionInfo?.Extras?.PIC, submissionInfo?.SizeAndChecksums?.Layerbreak, submissionInfo?.SizeAndChecksums?.CRC32);
|
||||
if (deleteSuccess)
|
||||
resultProgress?.Report(ResultEventArgs.Success("IRD created!"));
|
||||
else
|
||||
resultProgress?.Report(ResultEventArgs.Failure("Failed to create IRD"));
|
||||
}
|
||||
|
||||
resultProgress?.Report(ResultEventArgs.Success("Submission information process complete!"));
|
||||
return ResultEventArgs.Success();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the parameters are valid
|
||||
/// </summary>
|
||||
/// <returns>True if the configuration is valid, false otherwise</returns>
|
||||
internal bool ParametersValid()
|
||||
{
|
||||
// Missing drive means it can never be valid
|
||||
if (_drive == null)
|
||||
return false;
|
||||
|
||||
bool parametersValid = _executionContext?.IsValid() ?? false;
|
||||
bool floppyValid = !(_drive.InternalDriveType == InternalDriveType.Floppy ^ _type == MediaType.FloppyDisk);
|
||||
|
||||
// TODO: HardDisk being in the Removable category is a hack, fix this later
|
||||
bool removableDiskValid = !((_drive.InternalDriveType == InternalDriveType.Removable || _drive.InternalDriveType == InternalDriveType.HardDisk)
|
||||
^ (_type == MediaType.CompactFlash || _type == MediaType.SDCard || _type == MediaType.FlashDrive || _type == MediaType.HardDisk));
|
||||
|
||||
return parametersValid && floppyValid && removableDiskValid;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validate the current environment is ready for a dump
|
||||
/// </summary>
|
||||
/// <returns>Result instance with the outcome</returns>
|
||||
private ResultEventArgs IsValidForDump()
|
||||
{
|
||||
// Validate that everything is good
|
||||
if (_executionContext == null || !ParametersValid())
|
||||
return ResultEventArgs.Failure("Error! Current configuration is not supported!");
|
||||
|
||||
// Fix the output paths, just in case
|
||||
OutputPath = FrontendTool.NormalizeOutputPaths(OutputPath, false);
|
||||
|
||||
// Validate that the output path isn't on the dumping drive
|
||||
if (_drive?.Name != null && OutputPath.StartsWith(_drive.Name))
|
||||
return ResultEventArgs.Failure("Error! Cannot output to same drive that is being dumped!");
|
||||
|
||||
// Validate that the required program exists
|
||||
if (!File.Exists(_executionContext.ExecutablePath))
|
||||
return ResultEventArgs.Failure($"Error! {_executionContext.ExecutablePath} does not exist!");
|
||||
|
||||
// Validate that the dumping drive doesn't contain the executable
|
||||
string fullExecutablePath = Path.GetFullPath(_executionContext.ExecutablePath!);
|
||||
if (_drive?.Name != null && fullExecutablePath.StartsWith(_drive.Name))
|
||||
return ResultEventArgs.Failure("Error! Cannot dump same drive that executable resides on!");
|
||||
|
||||
// Validate that the current configuration is supported
|
||||
return GetSupportStatus();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Information Output
|
||||
|
||||
/// <summary>
|
||||
/// Write the data to the output folder
|
||||
/// </summary>
|
||||
/// <param name="outputDirectory">Output folder to write to</param>
|
||||
/// <param name="filenameSuffix">Optional suffix to append to the filename</param>
|
||||
/// <param name="lines">Preformatted list of lines to write out to the file</param>
|
||||
/// <returns>True on success, false on error</returns>
|
||||
private static bool WriteOutputData(string? outputDirectory, string? filenameSuffix, List<string>? lines, out string status)
|
||||
{
|
||||
// Check to see if the inputs are valid
|
||||
if (lines == null)
|
||||
{
|
||||
status = "No formatted data found to write!";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Now write out to a generic file
|
||||
try
|
||||
{
|
||||
// Get the file path
|
||||
var path = string.Empty;
|
||||
if (string.IsNullOrEmpty(outputDirectory) && string.IsNullOrEmpty(filenameSuffix))
|
||||
path = "!submissionInfo.txt";
|
||||
else if (string.IsNullOrEmpty(outputDirectory) && !string.IsNullOrEmpty(filenameSuffix))
|
||||
path = $"!submissionInfo_{filenameSuffix}.txt";
|
||||
else if (!string.IsNullOrEmpty(outputDirectory) && string.IsNullOrEmpty(filenameSuffix))
|
||||
path = Path.Combine(outputDirectory, "!submissionInfo.txt");
|
||||
else if (!string.IsNullOrEmpty(outputDirectory) && !string.IsNullOrEmpty(filenameSuffix))
|
||||
path = Path.Combine(outputDirectory, $"!submissionInfo_{filenameSuffix}.txt");
|
||||
|
||||
using var sw = new StreamWriter(File.Open(path, FileMode.Create, FileAccess.Write), Encoding.UTF8);
|
||||
foreach (string line in lines)
|
||||
{
|
||||
sw.WriteLine(line);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
status = $"Writing could not complete: {ex}";
|
||||
return false;
|
||||
}
|
||||
|
||||
status = "Writing complete!";
|
||||
return true;
|
||||
}
|
||||
|
||||
// MOVE TO REDUMPLIB
|
||||
/// <summary>
|
||||
/// Write the data to the output folder
|
||||
/// </summary>
|
||||
/// <param name="outputDirectory">Output folder to write to</param>
|
||||
/// <param name="filenameSuffix">Optional suffix to append to the filename</param>
|
||||
/// <param name="info">SubmissionInfo object representing the JSON to write out to the file</param>
|
||||
/// <param name="includedArtifacts">True if artifacts were included, false otherwise</param>
|
||||
/// <returns>True on success, false on error</returns>
|
||||
private static bool WriteOutputData(string? outputDirectory, string? filenameSuffix, SubmissionInfo? info, bool includedArtifacts)
|
||||
{
|
||||
// Check to see if the input is valid
|
||||
if (info == null)
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
// Serialize the JSON and get it writable
|
||||
string json = JsonConvert.SerializeObject(info, Formatting.Indented);
|
||||
byte[] jsonBytes = Encoding.UTF8.GetBytes(json);
|
||||
|
||||
// If we included artifacts, write to a GZip-compressed file
|
||||
if (includedArtifacts)
|
||||
{
|
||||
var path = string.Empty;
|
||||
if (string.IsNullOrEmpty(outputDirectory) && string.IsNullOrEmpty(filenameSuffix))
|
||||
path = "!submissionInfo.json.gz";
|
||||
else if (string.IsNullOrEmpty(outputDirectory) && !string.IsNullOrEmpty(filenameSuffix))
|
||||
path = $"!submissionInfo_{filenameSuffix}.json.gz";
|
||||
else if (!string.IsNullOrEmpty(outputDirectory) && string.IsNullOrEmpty(filenameSuffix))
|
||||
path = Path.Combine(outputDirectory, "!submissionInfo.json.gz");
|
||||
else if (!string.IsNullOrEmpty(outputDirectory) && !string.IsNullOrEmpty(filenameSuffix))
|
||||
path = Path.Combine(outputDirectory, $"!submissionInfo_{filenameSuffix}.json.gz");
|
||||
|
||||
using var fs = File.Create(path);
|
||||
using var gs = new GZipStream(fs, CompressionMode.Compress);
|
||||
gs.Write(jsonBytes, 0, jsonBytes.Length);
|
||||
}
|
||||
|
||||
// Otherwise, write out to a normal JSON
|
||||
else
|
||||
{
|
||||
var path = string.Empty;
|
||||
if (string.IsNullOrEmpty(outputDirectory) && string.IsNullOrEmpty(filenameSuffix))
|
||||
path = "!submissionInfo.json";
|
||||
else if (string.IsNullOrEmpty(outputDirectory) && !string.IsNullOrEmpty(filenameSuffix))
|
||||
path = $"!submissionInfo_{filenameSuffix}.json";
|
||||
else if (!string.IsNullOrEmpty(outputDirectory) && string.IsNullOrEmpty(filenameSuffix))
|
||||
path = Path.Combine(outputDirectory, "!submissionInfo.json");
|
||||
else if (!string.IsNullOrEmpty(outputDirectory) && !string.IsNullOrEmpty(filenameSuffix))
|
||||
path = Path.Combine(outputDirectory, $"!submissionInfo_{filenameSuffix}.json");
|
||||
|
||||
using var fs = File.Create(path);
|
||||
fs.Write(jsonBytes, 0, jsonBytes.Length);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// We don't care what the error is right now
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// MOVE TO REDUMPLIB
|
||||
/// <summary>
|
||||
/// Write the protection data to the output folder
|
||||
/// </summary>
|
||||
/// <param name="outputDirectory">Output folder to write to</param>
|
||||
/// <param name="filenameSuffix">Optional suffix to append to the filename</param>
|
||||
/// <param name="info">SubmissionInfo object containing the protection information</param>
|
||||
/// <param name="hideDriveLetters">True if drive letters are to be removed from output, false otherwise</param>
|
||||
/// <returns>True on success, false on error</returns>
|
||||
private static bool WriteProtectionData(string? outputDirectory, string? filenameSuffix, SubmissionInfo? info, bool hideDriveLetters)
|
||||
{
|
||||
// Check to see if the inputs are valid
|
||||
if (info?.CopyProtection?.FullProtections == null || !info.CopyProtection.FullProtections.Any())
|
||||
return true;
|
||||
|
||||
// Now write out to a generic file
|
||||
try
|
||||
{
|
||||
var path = string.Empty;
|
||||
if (string.IsNullOrEmpty(outputDirectory) && string.IsNullOrEmpty(filenameSuffix))
|
||||
path = "!protectionInfo.txt";
|
||||
else if (string.IsNullOrEmpty(outputDirectory) && !string.IsNullOrEmpty(filenameSuffix))
|
||||
path = $"!protectionInfo{filenameSuffix}.txt";
|
||||
else if (!string.IsNullOrEmpty(outputDirectory) && string.IsNullOrEmpty(filenameSuffix))
|
||||
path = Path.Combine(outputDirectory, "!protectionInfo.txt");
|
||||
else if (!string.IsNullOrEmpty(outputDirectory) && !string.IsNullOrEmpty(filenameSuffix))
|
||||
path = Path.Combine(outputDirectory, $"!protectionInfo{filenameSuffix}.txt");
|
||||
|
||||
using var sw = new StreamWriter(File.Open(path, FileMode.Create, FileAccess.Write), Encoding.UTF8);
|
||||
|
||||
List<string> sortedKeys = [.. info.CopyProtection.FullProtections.Keys.OrderBy(k => k)];
|
||||
foreach (string key in sortedKeys)
|
||||
{
|
||||
string scanPath = key;
|
||||
if (hideDriveLetters)
|
||||
scanPath = Path.DirectorySeparatorChar + key.Substring((Path.GetPathRoot(key) ?? String.Empty).Length);
|
||||
|
||||
List<string>? scanResult = info.CopyProtection.FullProtections[key];
|
||||
|
||||
if (scanResult == null)
|
||||
sw.WriteLine($"{scanPath}: None");
|
||||
else
|
||||
sw.WriteLine($"{scanPath}: {string.Join(", ", [.. scanResult])}");
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// We don't care what the error is right now
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create an IRD and write it to the specified output directory with optional filename suffix
|
||||
/// </summary>
|
||||
/// <param name="outputDirectory">Output folder to write to</param>
|
||||
/// <param name="filenameSuffix">Optional suffix to append to the filename</param>
|
||||
/// <param name="outputFilename">Output filename to use as the base path</param>
|
||||
/// <returns>True on success, false on error</returns>
|
||||
private static async Task<bool> WriteIRD(string isoPath,
|
||||
string? discKeyString,
|
||||
string? discIDString,
|
||||
string? picString,
|
||||
long? layerbreak,
|
||||
string? crc32)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Output IRD file path
|
||||
string irdPath = Path.ChangeExtension(isoPath, ".ird");
|
||||
|
||||
// Parse disc key from submission info (Required)
|
||||
byte[]? discKey = ProcessingTool.ParseHexKey(discKeyString);
|
||||
if (discKey == null)
|
||||
return false;
|
||||
|
||||
// Parse Disc ID from submission info (Optional)
|
||||
byte[]? discID = ProcessingTool.ParseDiscID(discIDString);
|
||||
|
||||
// Parse PIC from submission info (Optional)
|
||||
byte[]? pic = ProcessingTool.ParsePIC(picString);
|
||||
|
||||
// Parse CRC32 strings into ISO hash for Unique ID field (Optional)
|
||||
uint? uid = ProcessingTool.ParseCRC32(crc32);
|
||||
|
||||
// Ensure layerbreak value is valid (Optional)
|
||||
layerbreak = ProcessingTool.ParseLayerbreak(layerbreak);
|
||||
|
||||
// Create Redump-style reproducible IRD
|
||||
#if NET40
|
||||
LibIRD.ReIRD ird = await Task.Factory.StartNew(() =>
|
||||
#else
|
||||
LibIRD.ReIRD ird = await Task.Run(() =>
|
||||
#endif
|
||||
new LibIRD.ReIRD(isoPath, discKey, layerbreak, uid));
|
||||
if (pic != null)
|
||||
ird.PIC = pic;
|
||||
if (discID != null && ird.DiscID[15] != 0x00)
|
||||
ird.DiscID = discID;
|
||||
|
||||
// Write IRD to file
|
||||
ird.Write(irdPath);
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// We don't care what the error is
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
221
MPF.Frontend/EnumExtensions.cs
Normal file
221
MPF.Frontend/EnumExtensions.cs
Normal file
@@ -0,0 +1,221 @@
|
||||
using System;
|
||||
#if NET20 || NET35
|
||||
using System.Collections.Generic;
|
||||
#else
|
||||
using System.Collections.Concurrent;
|
||||
#endif
|
||||
using System.Reflection;
|
||||
using SabreTools.RedumpLib.Data;
|
||||
using RedumperReadMethod = MPF.ExecutionContexts.Redumper.ReadMethod;
|
||||
using RedumperSectorOrder = MPF.ExecutionContexts.Redumper.SectorOrder;
|
||||
|
||||
namespace MPF.Frontend
|
||||
{
|
||||
public static class EnumExtensions
|
||||
{
|
||||
#region Convert to Long Name
|
||||
|
||||
/// <summary>
|
||||
/// Long name method cache
|
||||
/// </summary>
|
||||
#if NET20 || NET35
|
||||
private static readonly Dictionary<Type, MethodInfo?> LongNameMethods = [];
|
||||
#else
|
||||
private static readonly ConcurrentDictionary<Type, MethodInfo?> LongNameMethods = [];
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Get the string representation of a generic enumerable value
|
||||
/// </summary>
|
||||
/// <param name="value">Enum value to convert</param>
|
||||
/// <returns>String representation of that value if possible, empty string on error</returns>
|
||||
public static string GetLongName(Enum value)
|
||||
{
|
||||
try
|
||||
{
|
||||
var sourceType = value.GetType();
|
||||
sourceType = Nullable.GetUnderlyingType(sourceType) ?? sourceType;
|
||||
|
||||
if (!LongNameMethods.TryGetValue(sourceType, out var method))
|
||||
{
|
||||
method = typeof(Extensions).GetMethod("LongName", [typeof(Nullable<>).MakeGenericType(sourceType)]);
|
||||
method ??= typeof(EnumExtensions).GetMethod("LongName", [typeof(Nullable<>).MakeGenericType(sourceType)]);
|
||||
|
||||
#if NET20 || NET35
|
||||
LongNameMethods[sourceType] = method;
|
||||
#else
|
||||
LongNameMethods.TryAdd(sourceType, method);
|
||||
#endif
|
||||
}
|
||||
|
||||
if (method != null)
|
||||
return method.Invoke(null, new[] { value }) as string ?? string.Empty;
|
||||
else
|
||||
return string.Empty;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Converter is not implemented for the given type
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the string representation of the InternalProgram enum values
|
||||
/// </summary>
|
||||
/// <param name="prog">InternalProgram value to convert</param>
|
||||
/// <returns>String representing the value, if possible</returns>
|
||||
public static string LongName(this InternalProgram? prog)
|
||||
{
|
||||
return (prog) switch
|
||||
{
|
||||
#region Dumping support
|
||||
|
||||
InternalProgram.Aaru => "Aaru",
|
||||
InternalProgram.DiscImageCreator => "DiscImageCreator",
|
||||
InternalProgram.Redumper => "Redumper",
|
||||
|
||||
#endregion
|
||||
|
||||
#region Verification support only
|
||||
|
||||
InternalProgram.CleanRip => "CleanRip",
|
||||
InternalProgram.PS3CFW => "PS3 CFW",
|
||||
InternalProgram.UmdImageCreator => "UmdImageCreator",
|
||||
InternalProgram.XboxBackupCreator => "XboxBackupCreator",
|
||||
|
||||
#endregion
|
||||
|
||||
InternalProgram.NONE => "Unknown",
|
||||
_ => "Unknown",
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the string representation of the RedumperReadMethod enum values
|
||||
/// </summary>
|
||||
/// <param name="method">RedumperReadMethod value to convert</param>
|
||||
/// <returns>String representing the value, if possible</returns>
|
||||
public static string LongName(this RedumperReadMethod? method)
|
||||
{
|
||||
return (method) switch
|
||||
{
|
||||
RedumperReadMethod.D8 => "D8",
|
||||
RedumperReadMethod.BE => "BE",
|
||||
RedumperReadMethod.BE_CDDA => "BE_CDDA",
|
||||
|
||||
RedumperReadMethod.NONE => "Default",
|
||||
_ => "Unknown",
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the string representation of the RedumperSectorOrder enum values
|
||||
/// </summary>
|
||||
/// <param name="order">RedumperSectorOrder value to convert</param>
|
||||
/// <returns>String representing the value, if possible</returns>
|
||||
public static string LongName(this RedumperSectorOrder? order)
|
||||
{
|
||||
return (order) switch
|
||||
{
|
||||
RedumperSectorOrder.DATA_C2_SUB => "DATA_C2_SUB",
|
||||
RedumperSectorOrder.DATA_SUB_C2 => "DATA_SUB_C2",
|
||||
RedumperSectorOrder.DATA_SUB => "DATA_SUB",
|
||||
RedumperSectorOrder.DATA_C2 => "DATA_C2",
|
||||
|
||||
RedumperSectorOrder.NONE => "Default",
|
||||
_ => "Unknown",
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Convert from String
|
||||
|
||||
/// <summary>
|
||||
/// Get the RedumperReadMethod enum value for a given string
|
||||
/// </summary>
|
||||
/// <param name="method">String value to convert</param>
|
||||
/// <returns>RedumperReadMethod represented by the string, if possible</returns>
|
||||
public static RedumperReadMethod ToRedumperReadMethod(this string? method)
|
||||
{
|
||||
return (method?.ToLowerInvariant()) switch
|
||||
{
|
||||
"d8" => RedumperReadMethod.D8,
|
||||
"be" => RedumperReadMethod.BE,
|
||||
"be_cdda"
|
||||
or "be cdda"
|
||||
or "be-cdda"
|
||||
or "becdda" => RedumperReadMethod.BE_CDDA,
|
||||
|
||||
_ => RedumperReadMethod.NONE,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the RedumperSectorOrder enum value for a given string
|
||||
/// </summary>
|
||||
/// <param name="order">String value to convert</param>
|
||||
/// <returns>RedumperSectorOrder represented by the string, if possible</returns>
|
||||
public static RedumperSectorOrder ToRedumperSectorOrder(this string? order)
|
||||
{
|
||||
return (order?.ToLowerInvariant()) switch
|
||||
{
|
||||
"data_c2_sub"
|
||||
or "data c2 sub"
|
||||
or "data-c2-sub"
|
||||
or "datac2sub" => RedumperSectorOrder.DATA_C2_SUB,
|
||||
"data_sub_c2"
|
||||
or "data sub c2"
|
||||
or "data-sub-c2"
|
||||
or "datasubc2" => RedumperSectorOrder.DATA_SUB_C2,
|
||||
"data_sub"
|
||||
or "data sub"
|
||||
or "data-sub"
|
||||
or "datasub" => RedumperSectorOrder.DATA_SUB,
|
||||
"data_c2"
|
||||
or "data c2"
|
||||
or "data-c2"
|
||||
or "datac2" => RedumperSectorOrder.DATA_C2,
|
||||
|
||||
_ => RedumperSectorOrder.NONE,
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Functionality Support
|
||||
|
||||
/// <summary>
|
||||
/// Get if a system requires an anti-modchip scan
|
||||
/// </summary>
|
||||
public static bool SupportsAntiModchipScans(this RedumpSystem? system)
|
||||
{
|
||||
return system switch
|
||||
{
|
||||
RedumpSystem.SonyPlayStation => true,
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get if a system requires a copy protection scan
|
||||
/// </summary>
|
||||
public static bool SupportsCopyProtectionScans(this RedumpSystem? system)
|
||||
{
|
||||
return system switch
|
||||
{
|
||||
RedumpSystem.AppleMacintosh => true,
|
||||
RedumpSystem.EnhancedCD => true,
|
||||
RedumpSystem.IBMPCcompatible => true,
|
||||
RedumpSystem.PalmOS => true,
|
||||
RedumpSystem.PocketPC => true,
|
||||
RedumpSystem.RainbowDisc => true,
|
||||
RedumpSystem.SonyElectronicBook => true,
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
43
MPF.Frontend/Enumerations.cs
Normal file
43
MPF.Frontend/Enumerations.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
namespace MPF.Frontend
|
||||
{
|
||||
/// <summary>
|
||||
/// Drive type for dumping
|
||||
/// </summary>
|
||||
public enum InternalDriveType
|
||||
{
|
||||
Optical,
|
||||
Floppy,
|
||||
HardDisk,
|
||||
Removable,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Program that is being used to dump media
|
||||
/// </summary>
|
||||
public enum InternalProgram
|
||||
{
|
||||
NONE = 0,
|
||||
|
||||
// Dumping support
|
||||
Aaru,
|
||||
DiscImageCreator,
|
||||
Redumper,
|
||||
|
||||
// Verification support only
|
||||
CleanRip,
|
||||
PS3CFW,
|
||||
UmdImageCreator,
|
||||
XboxBackupCreator,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Log level for output
|
||||
/// </summary>
|
||||
public enum LogLevel
|
||||
{
|
||||
USER,
|
||||
VERBOSE,
|
||||
ERROR,
|
||||
SECRET,
|
||||
}
|
||||
}
|
||||
62
MPF.Frontend/InterfaceConstants.cs
Normal file
62
MPF.Frontend/InterfaceConstants.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using SabreTools.RedumpLib.Data;
|
||||
|
||||
namespace MPF.Frontend
|
||||
{
|
||||
/// <summary>
|
||||
/// Constant values for UI
|
||||
/// </summary>
|
||||
public static class InterfaceConstants
|
||||
{
|
||||
/// <summary>
|
||||
/// Set of all accepted speed values
|
||||
/// </summary>
|
||||
private static readonly List<int> _speedValues = [1, 2, 3, 4, 6, 8, 12, 16, 20, 24, 32, 40, 44, 48, 52, 56, 72];
|
||||
|
||||
/// <summary>
|
||||
/// Set of accepted speeds for CD and GD media
|
||||
/// </summary>
|
||||
public static IList<int> CD => _speedValues.Where(s => s <= 72).ToList();
|
||||
|
||||
/// <summary>
|
||||
/// Set of accepted speeds for DVD media
|
||||
/// </summary>
|
||||
public static IList<int> DVD => _speedValues.Where(s => s <= 24).ToList();
|
||||
|
||||
/// <summary>
|
||||
/// Set of accepted speeds for HD-DVD media
|
||||
/// </summary>
|
||||
public static IList<int> HDDVD => _speedValues.Where(s => s <= 24).ToList();
|
||||
|
||||
/// <summary>
|
||||
/// Set of accepted speeds for BD media
|
||||
/// </summary>
|
||||
public static IList<int> BD => _speedValues.Where(s => s <= 16).ToList();
|
||||
|
||||
/// <summary>
|
||||
/// Set of accepted speeds for all other media
|
||||
/// </summary>
|
||||
public static IList<int> Unknown => _speedValues.Where(s => s <= 1).ToList();
|
||||
|
||||
/// <summary>
|
||||
/// Get list of all drive speeds for a given MediaType
|
||||
/// </summary>
|
||||
/// <param name="type">MediaType? that represents the current item</param>
|
||||
/// <returns>Read-only list of drive speeds</returns>
|
||||
public static IList<int> GetSpeedsForMediaType(MediaType? type)
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
MediaType.CDROM
|
||||
or MediaType.GDROM => CD,
|
||||
MediaType.DVD
|
||||
or MediaType.NintendoGameCubeGameDisc
|
||||
or MediaType.NintendoWiiOpticalDisc => DVD,
|
||||
MediaType.HDDVD => HDDVD,
|
||||
MediaType.BluRay => BD,
|
||||
_ => Unknown,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
69
MPF.Frontend/MPF.Frontend.csproj
Normal file
69
MPF.Frontend/MPF.Frontend.csproj
Normal file
@@ -0,0 +1,69 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<!-- Assembly Properties -->
|
||||
<TargetFrameworks>net20;net35;net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0</TargetFrameworks>
|
||||
<CheckEolTargetFramework>false</CheckEolTargetFramework>
|
||||
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<VersionPrefix>3.2.3</VersionPrefix>
|
||||
|
||||
<!-- Package Properties -->
|
||||
<Authors>Matt Nadareski;ReignStumble;Jakz</Authors>
|
||||
<Description>Common code for all MPF frontend implementations</Description>
|
||||
<Copyright>Copyright (c) Matt Nadareski 2019-2024</Copyright>
|
||||
<PackageProjectUrl>https://github.com/SabreTools/</PackageProjectUrl>
|
||||
<RepositoryUrl>https://github.com/SabreTools/MPF</RepositoryUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<InternalsVisibleTo Include="MPF.Test" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Support All Frameworks -->
|
||||
<PropertyGroup Condition="$(TargetFramework.StartsWith(`net2`)) OR $(TargetFramework.StartsWith(`net3`)) OR $(TargetFramework.StartsWith(`net4`))">
|
||||
<RuntimeIdentifiers>win-x86;win-x64</RuntimeIdentifiers>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="$(TargetFramework.StartsWith(`netcoreapp`)) OR $(TargetFramework.StartsWith(`net5`))">
|
||||
<RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64</RuntimeIdentifiers>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="$(TargetFramework.StartsWith(`net6`)) OR $(TargetFramework.StartsWith(`net7`)) OR $(TargetFramework.StartsWith(`net8`))">
|
||||
<RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64;osx-arm64</RuntimeIdentifiers>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="$(RuntimeIdentifier.StartsWith(`osx-arm`))">
|
||||
<TargetFrameworks>net6.0;net7.0;net8.0</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MPF.ExecutionContexts\MPF.ExecutionContexts.csproj" />
|
||||
<ProjectReference Include="..\MPF.Processors\MPF.Processors.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Support for old .NET versions -->
|
||||
<ItemGroup Condition="$(TargetFramework.StartsWith(`net2`)) OR $(TargetFramework.StartsWith(`net3`)) OR $(TargetFramework.StartsWith(`net40`))">
|
||||
<PackageReference Include="MinAsyncBridge" Version="0.12.4" />
|
||||
<PackageReference Include="MinTasksExtensionsBridge" Version="0.3.4" />
|
||||
<PackageReference Include="MinThreadingBridge" Version="0.11.4" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="$(TargetFramework.StartsWith(`net452`))">
|
||||
<PackageReference Include="Microsoft.Net.Http" Version="2.2.29" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="!$(TargetFramework.StartsWith(`net2`)) AND !$(TargetFramework.StartsWith(`net3`)) AND !$(TargetFramework.StartsWith(`net40`)) AND !$(TargetFramework.StartsWith(`net452`))">
|
||||
<PackageReference Include="Microsoft.Management.Infrastructure" Version="3.0.0" />
|
||||
<PackageReference Include="System.Net.Http" Version="4.3.4" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BinaryObjectScanner" PrivateAssets="build; analyzers" ExcludeAssets="contentFiles" Version="3.1.16" GeneratePathProperty="true">
|
||||
<IncludeAssets>runtime; compile; build; native; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="LibIRD" Version="0.9.1" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="SabreTools.RedumpLib" Version="1.4.4" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
801
MPF.Frontend/Options.cs
Normal file
801
MPF.Frontend/Options.cs
Normal file
@@ -0,0 +1,801 @@
|
||||
using System.Collections.Generic;
|
||||
using SabreTools.RedumpLib.Data;
|
||||
using AaruSettings = MPF.ExecutionContexts.Aaru.SettingConstants;
|
||||
using DICSettings = MPF.ExecutionContexts.DiscImageCreator.SettingConstants;
|
||||
using RedumperReadMethod = MPF.ExecutionContexts.Redumper.ReadMethod;
|
||||
using RedumperSectorOrder = MPF.ExecutionContexts.Redumper.SectorOrder;
|
||||
using RedumperSettings = MPF.ExecutionContexts.Redumper.SettingConstants;
|
||||
|
||||
namespace MPF.Frontend
|
||||
{
|
||||
public class Options
|
||||
{
|
||||
/// <summary>
|
||||
/// All settings in the form of a dictionary
|
||||
/// </summary>
|
||||
public Dictionary<string, string?> Settings { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicate if the program is being run with a clean configuration
|
||||
/// </summary>
|
||||
public bool FirstRun
|
||||
{
|
||||
get { return GetBooleanSetting(Settings, "FirstRun", true); }
|
||||
set { Settings["FirstRun"] = value.ToString(); }
|
||||
}
|
||||
|
||||
#region Internal Program
|
||||
|
||||
/// <summary>
|
||||
/// Path to Aaru
|
||||
/// </summary>
|
||||
public string? AaruPath
|
||||
{
|
||||
get { return GetStringSetting(Settings, "AaruPath", "Programs\\Aaru\\Aaru.exe"); }
|
||||
set { Settings["AaruPath"] = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Path to DiscImageCreator
|
||||
/// </summary>
|
||||
public string? DiscImageCreatorPath
|
||||
{
|
||||
get { return GetStringSetting(Settings, "DiscImageCreatorPath", "Programs\\Creator\\DiscImageCreator.exe"); }
|
||||
set { Settings["DiscImageCreatorPath"] = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Path to Redumper
|
||||
/// </summary>
|
||||
public string? RedumperPath
|
||||
{
|
||||
get { return GetStringSetting(Settings, "RedumperPath", "Programs\\Redumper\\redumper.exe"); }
|
||||
set { Settings["RedumperPath"] = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Currently selected dumping program
|
||||
/// </summary>
|
||||
public InternalProgram InternalProgram
|
||||
{
|
||||
get
|
||||
{
|
||||
var valueString = GetStringSetting(Settings, "InternalProgram", InternalProgram.Redumper.ToString());
|
||||
var valueEnum = ToInternalProgram(valueString);
|
||||
return valueEnum == InternalProgram.NONE ? InternalProgram.Redumper : valueEnum;
|
||||
}
|
||||
set
|
||||
{
|
||||
Settings["InternalProgram"] = value.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region UI Defaults
|
||||
|
||||
/// <summary>
|
||||
/// Enable dark mode for UI elements
|
||||
/// </summary>
|
||||
public bool EnableDarkMode
|
||||
{
|
||||
get { return GetBooleanSetting(Settings, "EnableDarkMode", false); }
|
||||
set { Settings["EnableDarkMode"] = value.ToString(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enable purple mode for UI elements
|
||||
/// </summary>
|
||||
public bool EnablePurpMode
|
||||
{
|
||||
get { return GetBooleanSetting(Settings, "EnablePurpMode", false); }
|
||||
set { Settings["EnablePurpMode"] = value.ToString(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Custom color setting
|
||||
/// </summary>
|
||||
public string? CustomBackgroundColor
|
||||
{
|
||||
get { return GetStringSetting(Settings, "CustomBackgroundColor", null); }
|
||||
set { Settings["CustomBackgroundColor"] = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Custom color setting
|
||||
/// </summary>
|
||||
public string? CustomTextColor
|
||||
{
|
||||
get { return GetStringSetting(Settings, "CustomTextColor", null); }
|
||||
set { Settings["CustomTextColor"] = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check for updates on startup
|
||||
/// </summary>
|
||||
public bool CheckForUpdatesOnStartup
|
||||
{
|
||||
get { return GetBooleanSetting(Settings, "CheckForUpdatesOnStartup", true); }
|
||||
set { Settings["CheckForUpdatesOnStartup"] = value.ToString(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fast update label - Skips disc checks and updates path only
|
||||
/// </summary>
|
||||
public bool FastUpdateLabel
|
||||
{
|
||||
get { return GetBooleanSetting(Settings, "FastUpdateLabel", false); }
|
||||
set { Settings["FastUpdateLabel"] = value.ToString(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Default output path for dumps
|
||||
/// </summary>
|
||||
public string? DefaultOutputPath
|
||||
{
|
||||
get { return GetStringSetting(Settings, "DefaultOutputPath", "ISO"); }
|
||||
set { Settings["DefaultOutputPath"] = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Default system if none can be detected
|
||||
/// </summary>
|
||||
public RedumpSystem? DefaultSystem
|
||||
{
|
||||
get
|
||||
{
|
||||
var valueString = GetStringSetting(Settings, "DefaultSystem", null);
|
||||
var valueEnum = Extensions.ToRedumpSystem(valueString ?? string.Empty);
|
||||
return valueEnum;
|
||||
}
|
||||
set
|
||||
{
|
||||
Settings["DefaultSystem"] = value.LongName();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Default output path for dumps
|
||||
/// </summary>
|
||||
/// <remarks>This is a hidden setting</remarks>
|
||||
public bool ShowDebugViewMenuItem
|
||||
{
|
||||
get { return GetBooleanSetting(Settings, "ShowDebugViewMenuItem", false); }
|
||||
set { Settings["ShowDebugViewMenuItem"] = value.ToString(); }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Dumping Speeds
|
||||
|
||||
/// <summary>
|
||||
/// Default CD dumping speed
|
||||
/// </summary>
|
||||
public int PreferredDumpSpeedCD
|
||||
{
|
||||
get { return GetInt32Setting(Settings, "PreferredDumpSpeedCD", 24); }
|
||||
set { Settings["PreferredDumpSpeedCD"] = value.ToString(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Default DVD dumping speed
|
||||
/// </summary>
|
||||
public int PreferredDumpSpeedDVD
|
||||
{
|
||||
get { return GetInt32Setting(Settings, "PreferredDumpSpeedDVD", 16); }
|
||||
set { Settings["PreferredDumpSpeedDVD"] = value.ToString(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Default HD-DVD dumping speed
|
||||
/// </summary>
|
||||
public int PreferredDumpSpeedHDDVD
|
||||
{
|
||||
get { return GetInt32Setting(Settings, "PreferredDumpSpeedHDDVD", 8); }
|
||||
set { Settings["PreferredDumpSpeedHDDVD"] = value.ToString(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Default BD dumping speed
|
||||
/// </summary>
|
||||
public int PreferredDumpSpeedBD
|
||||
{
|
||||
get { return GetInt32Setting(Settings, "PreferredDumpSpeedBD", 8); }
|
||||
set { Settings["PreferredDumpSpeedBD"] = value.ToString(); }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Aaru
|
||||
|
||||
/// <summary>
|
||||
/// Enable debug output while dumping by default
|
||||
/// </summary>
|
||||
public bool AaruEnableDebug
|
||||
{
|
||||
get { return GetBooleanSetting(Settings, AaruSettings.EnableDebug, AaruSettings.EnableDebugDefault); }
|
||||
set { Settings[AaruSettings.EnableDebug] = value.ToString(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enable verbose output while dumping by default
|
||||
/// </summary>
|
||||
public bool AaruEnableVerbose
|
||||
{
|
||||
get { return GetBooleanSetting(Settings, AaruSettings.EnableVerbose, AaruSettings.EnableVerboseDefault); }
|
||||
set { Settings[AaruSettings.EnableVerbose] = value.ToString(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enable force dumping of media by default
|
||||
/// </summary>
|
||||
public bool AaruForceDumping
|
||||
{
|
||||
get { return GetBooleanSetting(Settings, AaruSettings.ForceDumping, AaruSettings.ForceDumpingDefault); }
|
||||
set { Settings[AaruSettings.ForceDumping] = value.ToString(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Default number of sector/subchannel rereads
|
||||
/// </summary>
|
||||
public int AaruRereadCount
|
||||
{
|
||||
get { return GetInt32Setting(Settings, AaruSettings.RereadCount, AaruSettings.RereadCountDefault); }
|
||||
set { Settings[AaruSettings.RereadCount] = value.ToString(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Strip personal data information from Aaru metadata by default
|
||||
/// </summary>
|
||||
public bool AaruStripPersonalData
|
||||
{
|
||||
get { return GetBooleanSetting(Settings, AaruSettings.StripPersonalData, AaruSettings.StripPersonalDataDefault); }
|
||||
set { Settings[AaruSettings.StripPersonalData] = value.ToString(); }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region DiscImageCreator
|
||||
|
||||
/// <summary>
|
||||
/// Enable multi-sector read flag by default
|
||||
/// </summary>
|
||||
public bool DICMultiSectorRead
|
||||
{
|
||||
get { return GetBooleanSetting(Settings, DICSettings.MultiSectorRead, DICSettings.MultiSectorReadDefault); }
|
||||
set { Settings[DICSettings.MultiSectorRead] = value.ToString(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Include a default multi-sector read value
|
||||
/// </summary>
|
||||
public int DICMultiSectorReadValue
|
||||
{
|
||||
get { return GetInt32Setting(Settings, DICSettings.MultiSectorReadValue, DICSettings.MultiSectorReadValueDefault); }
|
||||
set { Settings[DICSettings.MultiSectorReadValue] = value.ToString(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enable overly-secure dumping flags by default
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Split this into component parts later. Currently does:
|
||||
/// - Scan sector protection and set subchannel read level to 2 for CD
|
||||
/// - Set scan file protect flag for DVD
|
||||
/// </remarks>
|
||||
public bool DICParanoidMode
|
||||
{
|
||||
get { return GetBooleanSetting(Settings, DICSettings.ParanoidMode, DICSettings.ParanoidModeDefault); }
|
||||
set { Settings[DICSettings.ParanoidMode] = value.ToString(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enable the Quiet flag by default
|
||||
/// </summary>
|
||||
public bool DICQuietMode
|
||||
{
|
||||
get { return GetBooleanSetting(Settings, DICSettings.QuietMode, DICSettings.QuietModeDefault); }
|
||||
set { Settings[DICSettings.QuietMode] = value.ToString(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Default number of C2 rereads
|
||||
/// </summary>
|
||||
public int DICRereadCount
|
||||
{
|
||||
get { return GetInt32Setting(Settings, DICSettings.RereadCount, DICSettings.RereadCountDefault); }
|
||||
set { Settings[DICSettings.RereadCount] = value.ToString(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Default number of DVD/HD-DVD/BD rereads
|
||||
/// </summary>
|
||||
public int DICDVDRereadCount
|
||||
{
|
||||
get { return GetInt32Setting(Settings, DICSettings.DVDRereadCount, DICSettings.DVDRereadCountDefault); }
|
||||
set { Settings[DICSettings.DVDRereadCount] = value.ToString(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use the CMI flag for supported disc types
|
||||
/// </summary>
|
||||
public bool DICUseCMIFlag
|
||||
{
|
||||
get { return GetBooleanSetting(Settings, DICSettings.UseCMIFlag, DICSettings.UseCMIFlagDefault); }
|
||||
set { Settings[DICSettings.UseCMIFlag] = value.ToString(); }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Redumper
|
||||
|
||||
/// <summary>
|
||||
/// Enable debug output while dumping by default
|
||||
/// </summary>
|
||||
public bool RedumperEnableDebug
|
||||
{
|
||||
get { return GetBooleanSetting(Settings, RedumperSettings.EnableDebug, RedumperSettings.EnableDebugDefault); }
|
||||
set { Settings[RedumperSettings.EnableDebug] = value.ToString(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enable Redumper custom lead-in retries for Plextor drives
|
||||
/// </summary>
|
||||
public bool RedumperEnableLeadinRetry
|
||||
{
|
||||
get { return GetBooleanSetting(Settings, RedumperSettings.EnableLeadinRetry, RedumperSettings.EnableLeadinRetryDefault); }
|
||||
set { Settings[RedumperSettings.EnableLeadinRetry] = value.ToString(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enable verbose output while dumping by default
|
||||
/// </summary>
|
||||
public bool RedumperEnableVerbose
|
||||
{
|
||||
get { return GetBooleanSetting(Settings, RedumperSettings.EnableVerbose, RedumperSettings.EnableVerboseDefault); }
|
||||
set { Settings[RedumperSettings.EnableVerbose] = value.ToString(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Default number of redumper Plextor leadin retries
|
||||
/// </summary>
|
||||
public int RedumperLeadinRetryCount
|
||||
{
|
||||
get { return GetInt32Setting(Settings, RedumperSettings.LeadinRetryCount, RedumperSettings.LeadinRetryCountDefault); }
|
||||
set { Settings[RedumperSettings.LeadinRetryCount] = value.ToString(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enable options incompatible with redump submissions
|
||||
/// </summary>
|
||||
public bool RedumperNonRedumpMode
|
||||
{
|
||||
get { return GetBooleanSetting(Settings, "RedumperNonRedumpMode", false); }
|
||||
set { Settings["RedumperNonRedumpMode"] = value.ToString(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enable generic drive type by default with Redumper
|
||||
/// </summary>
|
||||
public bool RedumperUseGenericDriveType
|
||||
{
|
||||
get { return GetBooleanSetting(Settings, RedumperSettings.UseGenericDriveType, RedumperSettings.UseGenericDriveTypeDefault); }
|
||||
set { Settings[RedumperSettings.UseGenericDriveType] = value.ToString(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Currently selected default redumper read method
|
||||
/// </summary>
|
||||
public RedumperReadMethod RedumperReadMethod
|
||||
{
|
||||
get
|
||||
{
|
||||
var valueString = GetStringSetting(Settings, RedumperSettings.ReadMethod, RedumperSettings.ReadMethodDefault);
|
||||
return valueString.ToRedumperReadMethod();
|
||||
}
|
||||
set
|
||||
{
|
||||
Settings[RedumperSettings.ReadMethod] = value.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Currently selected default redumper sector order
|
||||
/// </summary>
|
||||
public RedumperSectorOrder RedumperSectorOrder
|
||||
{
|
||||
get
|
||||
{
|
||||
var valueString = GetStringSetting(Settings, RedumperSettings.SectorOrder, RedumperSettings.SectorOrderDefault);
|
||||
return valueString.ToRedumperSectorOrder();
|
||||
}
|
||||
set
|
||||
{
|
||||
Settings[RedumperSettings.SectorOrder] = value.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Default number of rereads
|
||||
/// </summary>
|
||||
public int RedumperRereadCount
|
||||
{
|
||||
get { return GetInt32Setting(Settings, RedumperSettings.RereadCount, RedumperSettings.RereadCountDefault); }
|
||||
set { Settings[RedumperSettings.RereadCount] = value.ToString(); }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Extra Dumping Options
|
||||
|
||||
/// <summary>
|
||||
/// Scan the disc for protection after dumping
|
||||
/// </summary>
|
||||
public bool ScanForProtection
|
||||
{
|
||||
get { return GetBooleanSetting(Settings, "ScanForProtection", true); }
|
||||
set { Settings["ScanForProtection"] = value.ToString(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add placeholder values in the submission info
|
||||
/// </summary>
|
||||
public bool AddPlaceholders
|
||||
{
|
||||
get { return GetBooleanSetting(Settings, "AddPlaceholders", true); }
|
||||
set { Settings["AddPlaceholders"] = value.ToString(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show the disc information window after dumping
|
||||
/// </summary>
|
||||
public bool PromptForDiscInformation
|
||||
{
|
||||
get { return GetBooleanSetting(Settings, "PromptForDiscInformation", true); }
|
||||
set { Settings["PromptForDiscInformation"] = value.ToString(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pull all information from Redump if signed in
|
||||
/// </summary>
|
||||
public bool PullAllInformation
|
||||
{
|
||||
get { return GetBooleanSetting(Settings, "PullAllInformation", false); }
|
||||
set { Settings["PullAllInformation"] = value.ToString(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enable tabs in all input fields
|
||||
/// </summary>
|
||||
public bool EnableTabsInInputFields
|
||||
{
|
||||
get { return GetBooleanSetting(Settings, "EnableTabsInInputFields", false); }
|
||||
set { Settings["EnableTabsInInputFields"] = value.ToString(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Limit outputs to Redump-supported values only
|
||||
/// </summary>
|
||||
public bool EnableRedumpCompatibility
|
||||
{
|
||||
get { return GetBooleanSetting(Settings, "EnableRedumpCompatibility", true); }
|
||||
set { Settings["EnableRedumpCompatibility"] = value.ToString(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show disc eject reminder before the disc information window is shown
|
||||
/// </summary>
|
||||
public bool ShowDiscEjectReminder
|
||||
{
|
||||
get { return GetBooleanSetting(Settings, "ShowDiscEjectReminder", true); }
|
||||
set { Settings["ShowDiscEjectReminder"] = value.ToString(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ignore fixed drives when populating the list
|
||||
/// </summary>
|
||||
public bool IgnoreFixedDrives
|
||||
{
|
||||
get { return GetBooleanSetting(Settings, "IgnoreFixedDrives", true); }
|
||||
set { Settings["IgnoreFixedDrives"] = value.ToString(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add the dump filename as a suffix to the auto-generated files
|
||||
/// </summary>
|
||||
public bool AddFilenameSuffix
|
||||
{
|
||||
get { return GetBooleanSetting(Settings, "AddFilenameSuffix", false); }
|
||||
set { Settings["AddFilenameSuffix"] = value.ToString(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Output the compressed JSON version of the submission info
|
||||
/// </summary>
|
||||
public bool OutputSubmissionJSON
|
||||
{
|
||||
get { return GetBooleanSetting(Settings, "OutputSubmissionJSON", false); }
|
||||
set { Settings["OutputSubmissionJSON"] = value.ToString(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Include log files in serialized JSON data
|
||||
/// </summary>
|
||||
public bool IncludeArtifacts
|
||||
{
|
||||
get { return GetBooleanSetting(Settings, "IncludeArtifacts", false); }
|
||||
set { Settings["IncludeArtifacts"] = value.ToString(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compress output log files to reduce space
|
||||
/// </summary>
|
||||
public bool CompressLogFiles
|
||||
{
|
||||
get { return GetBooleanSetting(Settings, "CompressLogFiles", true); }
|
||||
set { Settings["CompressLogFiles"] = value.ToString(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete unnecessary files to reduce space
|
||||
/// </summary>
|
||||
public bool DeleteUnnecessaryFiles
|
||||
{
|
||||
get { return GetBooleanSetting(Settings, "DeleteUnnecessaryFiles", false); }
|
||||
set { Settings["DeleteUnnecessaryFiles"] = value.ToString(); }
|
||||
}
|
||||
|
||||
/// <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
|
||||
|
||||
/// <summary>
|
||||
/// Skip detecting media type on disc scan
|
||||
/// </summary>
|
||||
public bool SkipMediaTypeDetection
|
||||
{
|
||||
get { return GetBooleanSetting(Settings, "SkipMediaTypeDetection", false); }
|
||||
set { Settings["SkipMediaTypeDetection"] = value.ToString(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Skip detecting known system on disc scan
|
||||
/// </summary>
|
||||
public bool SkipSystemDetection
|
||||
{
|
||||
get { return GetBooleanSetting(Settings, "SkipSystemDetection", false); }
|
||||
set { Settings["SkipSystemDetection"] = value.ToString(); }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Protection Scanning Options
|
||||
|
||||
/// <summary>
|
||||
/// Scan archive contents during protection scanning
|
||||
/// </summary>
|
||||
public bool ScanArchivesForProtection
|
||||
{
|
||||
get { return GetBooleanSetting(Settings, "ScanArchivesForProtection", true); }
|
||||
set { Settings["ScanArchivesForProtection"] = value.ToString(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scan for executable packers during protection scanning
|
||||
/// </summary>
|
||||
public bool ScanPackersForProtection
|
||||
{
|
||||
get { return GetBooleanSetting(Settings, "ScanPackersForProtection", false); }
|
||||
set { Settings["ScanPackersForProtection"] = value.ToString(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Include debug information with scan results
|
||||
/// </summary>
|
||||
public bool IncludeDebugProtectionInformation
|
||||
{
|
||||
get { return GetBooleanSetting(Settings, "IncludeDebugProtectionInformation", false); }
|
||||
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
|
||||
|
||||
/// <summary>
|
||||
/// Enable verbose and debug logs to be written
|
||||
/// </summary>
|
||||
public bool VerboseLogging
|
||||
{
|
||||
get { return GetBooleanSetting(Settings, "VerboseLogging", true); }
|
||||
set { Settings["VerboseLogging"] = value.ToString(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Have the log panel expanded by default on startup
|
||||
/// </summary>
|
||||
public bool OpenLogWindowAtStartup
|
||||
{
|
||||
get { return GetBooleanSetting(Settings, "OpenLogWindowAtStartup", true); }
|
||||
set { Settings["OpenLogWindowAtStartup"] = value.ToString(); }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Redump Login Information
|
||||
|
||||
public string? RedumpUsername
|
||||
{
|
||||
get { return GetStringSetting(Settings, "RedumpUsername", ""); }
|
||||
set { Settings["RedumpUsername"] = value; }
|
||||
}
|
||||
|
||||
// TODO: Figure out a way to keep this encrypted in some way, BASE64 to start?
|
||||
public string? RedumpPassword
|
||||
{
|
||||
get
|
||||
{
|
||||
return GetStringSetting(Settings, "RedumpPassword", "");
|
||||
}
|
||||
set { Settings["RedumpPassword"] = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determine if a complete set of Redump credentials might exist
|
||||
/// </summary>
|
||||
public bool HasRedumpLogin { get => !string.IsNullOrEmpty(RedumpUsername) && !string.IsNullOrEmpty(RedumpPassword); }
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Constructor taking a dictionary for settings
|
||||
/// </summary>
|
||||
/// <param name="settings"></param>
|
||||
public Options(Dictionary<string, string?>? settings = null)
|
||||
{
|
||||
this.Settings = settings ?? [];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor taking an existing Options object
|
||||
/// </summary>
|
||||
/// <param name="source"></param>
|
||||
public Options(Options? source)
|
||||
{
|
||||
Settings = new Dictionary<string, string?>(source?.Settings ?? []);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Accessor for the internal dictionary
|
||||
/// </summary>
|
||||
public string? this[string key]
|
||||
{
|
||||
get => this.Settings[key];
|
||||
set => this.Settings[key] = value;
|
||||
}
|
||||
|
||||
#region Helpers
|
||||
|
||||
/// <summary>
|
||||
/// Get the InternalProgram enum value for a given string
|
||||
/// </summary>
|
||||
/// <param name="internalProgram">String value to convert</param>
|
||||
/// <returns>InternalProgram represented by the string, if possible</returns>
|
||||
public static InternalProgram ToInternalProgram(string? internalProgram)
|
||||
{
|
||||
return (internalProgram?.ToLowerInvariant()) switch
|
||||
{
|
||||
// Dumping support
|
||||
"aaru"
|
||||
or "chef"
|
||||
or "dichef"
|
||||
or "discimagechef" => InternalProgram.Aaru,
|
||||
"creator"
|
||||
or "dic"
|
||||
or "dicreator"
|
||||
or "discimagecreator" => InternalProgram.DiscImageCreator,
|
||||
"rd"
|
||||
or "redumper" => InternalProgram.Redumper,
|
||||
|
||||
// Verification support only
|
||||
"cleanrip"
|
||||
or "cr" => InternalProgram.CleanRip,
|
||||
"ps3cfw"
|
||||
or "ps3"
|
||||
or "getkey"
|
||||
or "managunz"
|
||||
or "multiman" => InternalProgram.PS3CFW,
|
||||
"uic"
|
||||
or "umd"
|
||||
or "umdcreator"
|
||||
or "umdimagecreator" => InternalProgram.UmdImageCreator,
|
||||
"xbc"
|
||||
or "xbox"
|
||||
or "xbox360"
|
||||
or "xboxcreator"
|
||||
or "xboxbackupcreator" => InternalProgram.XboxBackupCreator,
|
||||
|
||||
_ => InternalProgram.NONE,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a Boolean setting from a settings, dictionary
|
||||
/// </summary>
|
||||
/// <param name="settings">Dictionary representing the settings</param>
|
||||
/// <param name="key">Setting key to get a value for</param>
|
||||
/// <param name="defaultValue">Default value to return if no value is found</param>
|
||||
/// <returns>Setting value if possible, default value otherwise</returns>
|
||||
private static bool GetBooleanSetting(Dictionary<string, string?> settings, string key, bool defaultValue)
|
||||
{
|
||||
if (settings.ContainsKey(key))
|
||||
{
|
||||
if (bool.TryParse(settings[key], out bool value))
|
||||
return value;
|
||||
else
|
||||
return defaultValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get an Int32 setting from a settings, dictionary
|
||||
/// </summary>
|
||||
/// <param name="settings">Dictionary representing the settings</param>
|
||||
/// <param name="key">Setting key to get a value for</param>
|
||||
/// <param name="defaultValue">Default value to return if no value is found</param>
|
||||
/// <returns>Setting value if possible, default value otherwise</returns>
|
||||
private static int GetInt32Setting(Dictionary<string, string?> settings, string key, int defaultValue)
|
||||
{
|
||||
if (settings.ContainsKey(key))
|
||||
{
|
||||
if (int.TryParse(settings[key], out int value))
|
||||
return value;
|
||||
else
|
||||
return defaultValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a String setting from a settings, dictionary
|
||||
/// </summary>
|
||||
/// <param name="settings">Dictionary representing the settings</param>
|
||||
/// <param name="key">Setting key to get a value for</param>
|
||||
/// <param name="defaultValue">Default value to return if no value is found</param>
|
||||
/// <returns>Setting value if possible, default value otherwise</returns>
|
||||
private static string? GetStringSetting(Dictionary<string, string?> settings, string key, string? defaultValue)
|
||||
{
|
||||
if (settings.ContainsKey(key))
|
||||
return settings[key];
|
||||
else
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
11
MPF.Frontend/ProcessUserInfoDelegate.cs
Normal file
11
MPF.Frontend/ProcessUserInfoDelegate.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using SabreTools.RedumpLib.Data;
|
||||
|
||||
namespace MPF.Frontend
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines how user information is processed, if at all
|
||||
/// </summary>
|
||||
/// <param name="info">Submission info that may be overwritten</param>
|
||||
/// <returns>True for successful updating, false or null otherwise</returns>
|
||||
public delegate bool? ProcessUserInfoDelegate(ref SubmissionInfo? info);
|
||||
}
|
||||
102
MPF.Frontend/ProcessingQueue.cs
Normal file
102
MPF.Frontend/ProcessingQueue.cs
Normal file
@@ -0,0 +1,102 @@
|
||||
using System;
|
||||
#if NET20 || NET35
|
||||
using System.Collections.Generic;
|
||||
#else
|
||||
using System.Collections.Concurrent;
|
||||
#endif
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MPF.Frontend
|
||||
{
|
||||
public sealed class ProcessingQueue<T> : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Internal queue to hold data to process
|
||||
/// </summary>
|
||||
#if NET20 || NET35
|
||||
private readonly Queue<T> _internalQueue;
|
||||
#else
|
||||
private readonly ConcurrentQueue<T> _internalQueue;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Custom processing step for dequeued data
|
||||
/// </summary>
|
||||
private readonly Action<T> _customProcessing;
|
||||
|
||||
/// <summary>
|
||||
/// Cancellation method for the processing task
|
||||
/// </summary>
|
||||
private readonly CancellationTokenSource _tokenSource;
|
||||
|
||||
public ProcessingQueue(Action<T> customProcessing)
|
||||
{
|
||||
#if NET20 || NET35
|
||||
_internalQueue = new Queue<T>();
|
||||
#else
|
||||
_internalQueue = new ConcurrentQueue<T>();
|
||||
#endif
|
||||
_customProcessing = customProcessing;
|
||||
_tokenSource = new CancellationTokenSource();
|
||||
#if NET20 || NET35
|
||||
Task.Run(() => ProcessQueue());
|
||||
#elif NET40
|
||||
Task.Factory.StartNew(() => ProcessQueue());
|
||||
#else
|
||||
Task.Run(() => ProcessQueue(), _tokenSource.Token);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dispose the current instance
|
||||
/// </summary>
|
||||
public void Dispose() => _tokenSource.Cancel();
|
||||
|
||||
/// <summary>
|
||||
/// Enqueue a new item for processing
|
||||
/// </summary>
|
||||
/// <param name="item"></param>
|
||||
public void Enqueue(T? item)
|
||||
{
|
||||
// Only accept new data when not cancelled
|
||||
if (item != null && !_tokenSource.IsCancellationRequested)
|
||||
_internalQueue.Enqueue(item);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process
|
||||
/// </summary>
|
||||
private void ProcessQueue()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
// Nothing in the queue means we get to idle
|
||||
#if NET20 || NET35
|
||||
if (_internalQueue.Count == 0)
|
||||
#else
|
||||
if (_internalQueue.IsEmpty)
|
||||
#endif
|
||||
{
|
||||
if (_tokenSource.IsCancellationRequested)
|
||||
break;
|
||||
|
||||
Thread.Sleep(1);
|
||||
continue;
|
||||
}
|
||||
|
||||
#if NET20 || NET35
|
||||
// Get the next item from the queue and invoke the lambda, if possible
|
||||
_customProcessing?.Invoke(_internalQueue.Dequeue());
|
||||
#else
|
||||
// Get the next item from the queue
|
||||
if (!_internalQueue.TryDequeue(out var nextItem))
|
||||
continue;
|
||||
|
||||
// Invoke the lambda, if possible
|
||||
_customProcessing?.Invoke(nextItem);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
59
MPF.Frontend/ResultEventArgs.cs
Normal file
59
MPF.Frontend/ResultEventArgs.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
using System;
|
||||
|
||||
namespace MPF.Frontend
|
||||
{
|
||||
/// <summary>
|
||||
/// Generic success/failure result object, with optional message
|
||||
/// </summary>
|
||||
public class ResultEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Internal representation of success
|
||||
/// </summary>
|
||||
private readonly bool _success;
|
||||
|
||||
/// <summary>
|
||||
/// Optional message for the result
|
||||
/// </summary>
|
||||
public string Message { get; }
|
||||
|
||||
private ResultEventArgs(bool success, string message)
|
||||
{
|
||||
_success = success;
|
||||
Message = message;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a default success result with no message
|
||||
/// </summary>
|
||||
public static ResultEventArgs Success() => new(true, string.Empty);
|
||||
|
||||
/// <summary>
|
||||
/// Create a success result with a custom message
|
||||
/// </summary>
|
||||
/// <param name="message">String to add as a message</param>
|
||||
public static ResultEventArgs Success(string? message) => new(true, message ?? string.Empty);
|
||||
|
||||
/// <summary>
|
||||
/// Create a default failure result with no message
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static ResultEventArgs Failure() => new(false, string.Empty);
|
||||
|
||||
/// <summary>
|
||||
/// Create a failure result with a custom message
|
||||
/// </summary>
|
||||
/// <param name="message">String to add as a message</param>
|
||||
public static ResultEventArgs Failure(string? message) => new(false, message ?? string.Empty);
|
||||
|
||||
/// <summary>
|
||||
/// Results can be compared to boolean values based on the success value
|
||||
/// </summary>
|
||||
public static implicit operator bool(ResultEventArgs result) => result._success;
|
||||
|
||||
/// <summary>
|
||||
/// Results can be compared to boolean values based on the success value
|
||||
/// </summary>
|
||||
public static implicit operator ResultEventArgs(bool bval) => new(bval, string.Empty);
|
||||
}
|
||||
}
|
||||
52
MPF.Frontend/StringEventArgs.cs
Normal file
52
MPF.Frontend/StringEventArgs.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace MPF.Frontend
|
||||
{
|
||||
/// <summary>
|
||||
/// String wrapper for event arguments
|
||||
/// </summary>
|
||||
public class StringEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// String represented by the event arguments
|
||||
/// </summary>
|
||||
private readonly string _value;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for string values
|
||||
/// </summary>
|
||||
public StringEventArgs(string? value)
|
||||
{
|
||||
_value = value ?? string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for StringBuilder values
|
||||
/// </summary>
|
||||
public StringEventArgs(StringBuilder? value)
|
||||
{
|
||||
_value = value?.ToString() ?? string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event arguments are just the value of the string contained within
|
||||
/// </summary>
|
||||
public static implicit operator string(StringEventArgs args) => args._value;
|
||||
|
||||
/// <summary>
|
||||
/// Event arguments are just the value of the string contained within
|
||||
/// </summary>
|
||||
public static implicit operator StringBuilder(StringEventArgs args) => new(args._value);
|
||||
|
||||
/// <summary>
|
||||
/// Event arguments are just the value of the string contained within
|
||||
/// </summary>
|
||||
public static implicit operator StringEventArgs(string? str) => new(str);
|
||||
|
||||
/// <summary>
|
||||
/// Event arguments are just the value of the string contained within
|
||||
/// </summary>
|
||||
public static implicit operator StringEventArgs(StringBuilder? sb) => new(sb);
|
||||
}
|
||||
}
|
||||
655
MPF.Frontend/Tools/FrontendTool.cs
Normal file
655
MPF.Frontend/Tools/FrontendTool.cs
Normal file
@@ -0,0 +1,655 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using SabreTools.RedumpLib.Data;
|
||||
|
||||
namespace MPF.Frontend.Tools
|
||||
{
|
||||
public static class FrontendTool
|
||||
{
|
||||
#region Information Extraction
|
||||
|
||||
/// <summary>
|
||||
/// Get the default speed for a given media type from the supplied options
|
||||
/// </summary>
|
||||
public static int GetDefaultSpeedForMediaType(MediaType? mediaType, Options options)
|
||||
{
|
||||
return mediaType switch
|
||||
{
|
||||
// CD dump speed
|
||||
MediaType.CDROM => options.PreferredDumpSpeedCD,
|
||||
MediaType.GDROM => options.PreferredDumpSpeedCD,
|
||||
|
||||
// DVD dump speed
|
||||
MediaType.DVD => options.PreferredDumpSpeedDVD,
|
||||
MediaType.NintendoGameCubeGameDisc => options.PreferredDumpSpeedDVD,
|
||||
MediaType.NintendoWiiOpticalDisc => options.PreferredDumpSpeedDVD,
|
||||
|
||||
// HD-DVD dump speed
|
||||
MediaType.HDDVD => options.PreferredDumpSpeedHDDVD,
|
||||
|
||||
// BD dump speed
|
||||
MediaType.BluRay => options.PreferredDumpSpeedBD,
|
||||
|
||||
// Default
|
||||
_ => options.PreferredDumpSpeedCD,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the current system from the drive volume label
|
||||
/// </summary>
|
||||
/// <returns>The system based on volume label, null if none detected</returns>
|
||||
public static RedumpSystem? GetRedumpSystemFromVolumeLabel(string? volumeLabel)
|
||||
{
|
||||
// If the volume label is empty, we can't do anything
|
||||
if (string.IsNullOrEmpty(volumeLabel))
|
||||
return null;
|
||||
|
||||
// Audio CD
|
||||
if (volumeLabel!.Equals("Audio CD", StringComparison.OrdinalIgnoreCase))
|
||||
return RedumpSystem.AudioCD;
|
||||
|
||||
// Microsoft Xbox
|
||||
if (volumeLabel.Equals("SEP13011042", StringComparison.OrdinalIgnoreCase))
|
||||
return RedumpSystem.MicrosoftXbox;
|
||||
else if (volumeLabel.Equals("SEP13011042072", StringComparison.OrdinalIgnoreCase))
|
||||
return RedumpSystem.MicrosoftXbox;
|
||||
|
||||
// Microsoft Xbox 360
|
||||
if (volumeLabel.Equals("XBOX360", StringComparison.OrdinalIgnoreCase))
|
||||
return RedumpSystem.MicrosoftXbox360;
|
||||
else if (volumeLabel.Equals("XGD2DVD_NTSC", StringComparison.OrdinalIgnoreCase))
|
||||
return RedumpSystem.MicrosoftXbox360;
|
||||
|
||||
// Microsoft Xbox 360 - Too overly broad even if a lot of discs use this
|
||||
//if (volumeLabel.Equals("CD_ROM", StringComparison.OrdinalIgnoreCase))
|
||||
// return RedumpSystem.MicrosoftXbox360; // Also for Xbox One?
|
||||
//if (volumeLabel.Equals("DVD_ROM", StringComparison.OrdinalIgnoreCase))
|
||||
// return RedumpSystem.MicrosoftXbox360;
|
||||
|
||||
// Sega Mega-CD / Sega-CD
|
||||
if (volumeLabel.Equals("Sega_CD", StringComparison.OrdinalIgnoreCase))
|
||||
return RedumpSystem.SegaMegaCDSegaCD;
|
||||
|
||||
// Sony PlayStation 3
|
||||
if (volumeLabel.Equals("PS3VOLUME", StringComparison.OrdinalIgnoreCase))
|
||||
return RedumpSystem.SonyPlayStation3;
|
||||
|
||||
// Sony PlayStation 4
|
||||
if (volumeLabel.Equals("PS4VOLUME", StringComparison.OrdinalIgnoreCase))
|
||||
return RedumpSystem.SonyPlayStation4;
|
||||
|
||||
// Sony PlayStation 5
|
||||
if (volumeLabel.Equals("PS5VOLUME", StringComparison.OrdinalIgnoreCase))
|
||||
return RedumpSystem.SonyPlayStation5;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Normalization
|
||||
|
||||
/// <summary>
|
||||
/// Adjust a disc title so that it will be processed correctly by Redump
|
||||
/// </summary>
|
||||
/// <param name="title">Existing title to potentially reformat</param>
|
||||
/// <param name="languages">Array of languages to use for assuming articles</param>
|
||||
/// <returns>The reformatted title</returns>
|
||||
public static string? NormalizeDiscTitle(string? title, Language?[]? languages)
|
||||
{
|
||||
// If we have no set languages, then assume English
|
||||
if (languages == null || languages.Length == 0)
|
||||
languages = [Language.English];
|
||||
|
||||
// Loop through all of the given languages
|
||||
foreach (var language in languages)
|
||||
{
|
||||
// If the new title is different, assume it was normalized and return it
|
||||
string? newTitle = NormalizeDiscTitle(title, language);
|
||||
if (newTitle != title)
|
||||
return newTitle;
|
||||
}
|
||||
|
||||
// If we didn't already try English, try it now
|
||||
if (!languages.Contains(Language.English))
|
||||
return NormalizeDiscTitle(title, Language.English);
|
||||
|
||||
// If all fails, then the title didn't need normalization
|
||||
return title;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adjust a disc title so that it will be processed correctly by Redump
|
||||
/// </summary>
|
||||
/// <param name="title">Existing title to potentially reformat</param>
|
||||
/// <param name="language">Language to use for assuming articles</param>
|
||||
/// <returns>The reformatted title</returns>
|
||||
/// <remarks>
|
||||
/// If the language of the title is unknown or if it's multilingual,
|
||||
/// pass in Language.English for standardized coverage.
|
||||
/// </remarks>
|
||||
public static string? NormalizeDiscTitle(string? title, Language? language)
|
||||
{
|
||||
// If we have an invalid title, just return it as-is
|
||||
if (string.IsNullOrEmpty(title))
|
||||
return title;
|
||||
|
||||
// If we have an invalid language, assume Language.English
|
||||
if (language == null)
|
||||
language = Language.English;
|
||||
|
||||
// Get the title split into parts
|
||||
string[] splitTitle = title!.Split(' ').Where(s => !string.IsNullOrEmpty(s)).ToArray();
|
||||
|
||||
// If we only have one part, we can't do anything
|
||||
if (splitTitle.Length <= 1)
|
||||
return title;
|
||||
|
||||
// Determine if we have a definite or indefinite article as the first item
|
||||
string firstItem = splitTitle[0];
|
||||
switch (firstItem.ToLowerInvariant())
|
||||
{
|
||||
// Latin script articles
|
||||
case "'n"
|
||||
when language is Language.Manx:
|
||||
case "a"
|
||||
when language is Language.English
|
||||
|| language is Language.Hungarian
|
||||
|| language is Language.Portuguese
|
||||
|| language is Language.Scots:
|
||||
case "a'"
|
||||
when language is Language.English
|
||||
|| language is Language.Hungarian
|
||||
|| language is Language.Irish
|
||||
|| language is Language.Gaelic: // Scottish Gaelic
|
||||
case "al"
|
||||
when language is Language.Breton:
|
||||
case "am"
|
||||
when language is Language.Gaelic: // Scottish Gaelic
|
||||
case "an"
|
||||
when language is Language.Breton
|
||||
|| language is Language.Cornish
|
||||
|| language is Language.English
|
||||
|| language is Language.Irish
|
||||
|| language is Language.Gaelic: // Scottish Gaelic
|
||||
case "anek"
|
||||
when language is Language.Nepali:
|
||||
case "ar"
|
||||
when language is Language.Breton:
|
||||
case "az"
|
||||
when language is Language.Hungarian:
|
||||
case "ān"
|
||||
when language is Language.Persian:
|
||||
case "as"
|
||||
when language is Language.Portuguese:
|
||||
case "d'"
|
||||
when language is Language.Luxembourgish:
|
||||
case "das"
|
||||
when language is Language.German:
|
||||
case "dat"
|
||||
when language is Language.Luxembourgish:
|
||||
case "de"
|
||||
when language is Language.Dutch:
|
||||
case "déi"
|
||||
when language is Language.Luxembourgish:
|
||||
case "dem"
|
||||
when language is Language.German
|
||||
|| language is Language.Luxembourgish:
|
||||
case "den"
|
||||
when language is Language.Dutch
|
||||
|| language is Language.German
|
||||
|| language is Language.Luxembourgish:
|
||||
case "der"
|
||||
when language is Language.Dutch
|
||||
|| language is Language.German
|
||||
|| language is Language.Luxembourgish:
|
||||
case "des"
|
||||
when language is Language.Dutch
|
||||
|| language is Language.French
|
||||
|| language is Language.German:
|
||||
case "die"
|
||||
when language is Language.Afrikaans
|
||||
|| language is Language.German:
|
||||
case "du"
|
||||
when language is Language.French:
|
||||
case "e"
|
||||
when language is Language.Papiamento:
|
||||
case "een"
|
||||
when language is Language.Dutch:
|
||||
case "egy"
|
||||
when language is Language.Hungarian:
|
||||
case "ei"
|
||||
when language is Language.Norwegian:
|
||||
case "ein"
|
||||
when language is Language.German
|
||||
|| language is Language.Norwegian:
|
||||
case "eine"
|
||||
when language is Language.German:
|
||||
case "einem"
|
||||
when language is Language.German:
|
||||
case "einen"
|
||||
when language is Language.German:
|
||||
case "einer"
|
||||
when language is Language.German:
|
||||
case "eines"
|
||||
when language is Language.German:
|
||||
case "eit"
|
||||
when language is Language.Norwegian:
|
||||
case "ek"
|
||||
when language is Language.Nepali:
|
||||
case "el"
|
||||
when language is Language.Arabic
|
||||
|| language is Language.Catalan
|
||||
|| language is Language.Spanish:
|
||||
case "els"
|
||||
when language is Language.Catalan:
|
||||
case "en"
|
||||
when language is Language.Danish
|
||||
|| language is Language.Luxembourgish
|
||||
|| language is Language.Norwegian
|
||||
|| language is Language.Swedish:
|
||||
case "eng"
|
||||
when language is Language.Luxembourgish:
|
||||
case "engem"
|
||||
when language is Language.Luxembourgish:
|
||||
case "enger"
|
||||
when language is Language.Luxembourgish:
|
||||
case "es"
|
||||
when language is Language.Catalan:
|
||||
case "et"
|
||||
when language is Language.Danish
|
||||
|| language is Language.Norwegian:
|
||||
case "ett"
|
||||
when language is Language.Swedish:
|
||||
case "euta"
|
||||
when language is Language.Nepali:
|
||||
case "euti"
|
||||
when language is Language.Nepali:
|
||||
case "gli"
|
||||
when language is Language.Italian:
|
||||
case "he"
|
||||
when language is Language.Hawaiian
|
||||
|| language is Language.Maori:
|
||||
case "het"
|
||||
when language is Language.Dutch:
|
||||
case "i"
|
||||
when language is Language.Italian
|
||||
|| language is Language.Khasi:
|
||||
case "il"
|
||||
when language is Language.Italian:
|
||||
case "in"
|
||||
when language is Language.Persian:
|
||||
case "ka"
|
||||
when language is Language.Hawaiian
|
||||
|| language is Language.Khasi:
|
||||
case "ke"
|
||||
when language is Language.Hawaiian:
|
||||
case "ki"
|
||||
when language is Language.Khasi:
|
||||
case "kunai"
|
||||
when language is Language.Nepali:
|
||||
case "l'"
|
||||
when language is Language.Catalan
|
||||
|| language is Language.French
|
||||
|| language is Language.Italian:
|
||||
case "la"
|
||||
when language is Language.Catalan
|
||||
|| language is Language.Esperanto
|
||||
|| language is Language.French
|
||||
|| language is Language.Italian
|
||||
|| language is Language.Spanish:
|
||||
case "las"
|
||||
when language is Language.Spanish:
|
||||
case "le"
|
||||
when language is Language.French
|
||||
|| language is Language.Interlingua
|
||||
|| language is Language.Italian:
|
||||
case "les"
|
||||
when language is Language.Catalan
|
||||
|| language is Language.French:
|
||||
case "lo"
|
||||
when language is Language.Catalan
|
||||
|| language is Language.Italian
|
||||
|| language is Language.Spanish:
|
||||
case "los"
|
||||
when language is Language.Catalan
|
||||
|| language is Language.Spanish:
|
||||
case "na"
|
||||
when language is Language.Irish
|
||||
|| language is Language.Gaelic: // Scottish Gaelic
|
||||
case "nam"
|
||||
when language is Language.Gaelic: // Scottish Gaelic
|
||||
case "nan"
|
||||
when language is Language.Gaelic: // Scottish Gaelic
|
||||
case "nā"
|
||||
when language is Language.Hawaiian:
|
||||
case "ngā"
|
||||
when language is Language.Maori:
|
||||
case "niște"
|
||||
when language is Language.Romanian:
|
||||
case "ny"
|
||||
when language is Language.Manx:
|
||||
case "o"
|
||||
when language is Language.Portuguese
|
||||
|| language is Language.Romanian:
|
||||
case "os"
|
||||
when language is Language.Portuguese:
|
||||
case "sa"
|
||||
when language is Language.Catalan:
|
||||
case "sang"
|
||||
when language is Language.Malay:
|
||||
case "se"
|
||||
when language is Language.Finnish:
|
||||
case "ses"
|
||||
when language is Language.Catalan:
|
||||
case "si"
|
||||
when language is Language.Malay:
|
||||
case "te"
|
||||
when language is Language.Maori:
|
||||
case "the"
|
||||
when language is Language.English
|
||||
|| language is Language.Scots:
|
||||
case "u"
|
||||
when language is Language.Khasi:
|
||||
case "ul"
|
||||
when language is Language.Breton:
|
||||
case "um"
|
||||
when language is Language.Portuguese:
|
||||
case "uma"
|
||||
when language is Language.Portuguese:
|
||||
case "umas"
|
||||
when language is Language.Portuguese:
|
||||
case "un"
|
||||
when language is Language.Breton
|
||||
|| language is Language.Catalan
|
||||
|| language is Language.French
|
||||
|| language is Language.Interlingua
|
||||
|| language is Language.Italian
|
||||
|| language is Language.Papiamento
|
||||
|| language is Language.Romanian
|
||||
|| language is Language.Spanish:
|
||||
case "un'"
|
||||
when language is Language.Italian:
|
||||
case "una"
|
||||
when language is Language.Catalan
|
||||
|| language is Language.Italian:
|
||||
case "unas"
|
||||
when language is Language.Spanish:
|
||||
case "une"
|
||||
when language is Language.French:
|
||||
case "uno"
|
||||
when language is Language.Italian:
|
||||
case "unos"
|
||||
when language is Language.Spanish:
|
||||
case "uns"
|
||||
when language is Language.Catalan
|
||||
|| language is Language.Portuguese:
|
||||
case "unei"
|
||||
when language is Language.Romanian:
|
||||
case "unes"
|
||||
when language is Language.Catalan:
|
||||
case "unor"
|
||||
when language is Language.Romanian:
|
||||
case "unui"
|
||||
when language is Language.Romanian:
|
||||
case "ur"
|
||||
when language is Language.Breton:
|
||||
case "y"
|
||||
when language is Language.Manx
|
||||
|| language is Language.Welsh:
|
||||
case "ye"
|
||||
when language is Language.Persian:
|
||||
case "yek"
|
||||
when language is Language.Persian:
|
||||
case "yn"
|
||||
when language is Language.Manx:
|
||||
case "yr"
|
||||
when language is Language.Welsh:
|
||||
|
||||
// Non-latin script articles
|
||||
case "ο"
|
||||
when language is Language.Greek:
|
||||
case "η"
|
||||
when language is Language.Greek:
|
||||
case "το"
|
||||
when language is Language.Greek:
|
||||
case "οι"
|
||||
when language is Language.Greek:
|
||||
case "τα"
|
||||
when language is Language.Greek:
|
||||
case "ένας"
|
||||
when language is Language.Greek:
|
||||
case "μια"
|
||||
when language is Language.Greek:
|
||||
case "ένα"
|
||||
when language is Language.Greek:
|
||||
case "еден"
|
||||
when language is Language.Macedonian:
|
||||
case "една"
|
||||
when language is Language.Macedonian:
|
||||
case "едно"
|
||||
when language is Language.Macedonian:
|
||||
case "едни"
|
||||
when language is Language.Macedonian:
|
||||
case "एउटा"
|
||||
when language is Language.Nepali:
|
||||
case "एउटी"
|
||||
when language is Language.Nepali:
|
||||
case "एक"
|
||||
when language is Language.Nepali:
|
||||
case "अनेक"
|
||||
when language is Language.Nepali:
|
||||
case "कुनै"
|
||||
when language is Language.Nepali:
|
||||
case "דער"
|
||||
when language is Language.Yiddish:
|
||||
case "די"
|
||||
when language is Language.Yiddish:
|
||||
case "דאָס"
|
||||
when language is Language.Yiddish:
|
||||
case "דעם"
|
||||
when language is Language.Yiddish:
|
||||
case "אַ"
|
||||
when language is Language.Yiddish:
|
||||
case "אַן"
|
||||
when language is Language.Yiddish:
|
||||
|
||||
break;
|
||||
|
||||
// Otherwise, just return it as-is
|
||||
default:
|
||||
return title;
|
||||
}
|
||||
|
||||
// Insert the first item if we have a `:` or `-`
|
||||
bool itemInserted = false;
|
||||
var newTitleBuilder = new StringBuilder();
|
||||
for (int i = 1; i < splitTitle.Length; i++)
|
||||
{
|
||||
string segment = splitTitle[i];
|
||||
if (!itemInserted && segment == ":")
|
||||
{
|
||||
itemInserted = true;
|
||||
newTitleBuilder.Append($", {firstItem} :");
|
||||
}
|
||||
else if (!itemInserted && segment == "-")
|
||||
{
|
||||
itemInserted = true;
|
||||
newTitleBuilder.Append($", {firstItem} -");
|
||||
}
|
||||
else if (!itemInserted && segment.EndsWith(":"))
|
||||
{
|
||||
itemInserted = true;
|
||||
newTitleBuilder.Append($" {segment.Substring(0, segment.Length - 1)}, {firstItem}:");
|
||||
}
|
||||
else if (!itemInserted && segment.EndsWith("-"))
|
||||
{
|
||||
itemInserted = true;
|
||||
newTitleBuilder.Append($" {segment.Substring(0, segment.Length - 1)}, {firstItem}-");
|
||||
}
|
||||
else
|
||||
{
|
||||
newTitleBuilder.Append($" {segment}");
|
||||
}
|
||||
}
|
||||
|
||||
// If we didn't insert the item yet, add it to the end
|
||||
string newTitle = newTitleBuilder.ToString().Trim();
|
||||
if (!itemInserted)
|
||||
newTitle = $"{newTitle}, {firstItem}";
|
||||
|
||||
return newTitle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Normalize a split set of paths
|
||||
/// </summary>
|
||||
/// <param name="path">Path value to normalize</param>
|
||||
public static string NormalizeOutputPaths(string? path, bool getFullPath)
|
||||
{
|
||||
// The easy way
|
||||
try
|
||||
{
|
||||
// If we have an invalid path
|
||||
if (string.IsNullOrEmpty(path))
|
||||
return string.Empty;
|
||||
|
||||
// Remove quotes and angle brackets from path
|
||||
path = path!.Replace("\"", string.Empty);
|
||||
path = path!.Replace("<", string.Empty);
|
||||
path = path!.Replace(">", string.Empty);
|
||||
|
||||
// Try getting the combined path and returning that directly
|
||||
string fullPath = getFullPath ? Path.GetFullPath(path) : path;
|
||||
var fullDirectory = Path.GetDirectoryName(fullPath);
|
||||
string fullFile = Path.GetFileName(fullPath);
|
||||
|
||||
// Remove invalid path characters
|
||||
if (fullDirectory != null)
|
||||
{
|
||||
foreach (char c in Path.GetInvalidPathChars())
|
||||
fullDirectory = fullDirectory.Replace(c, '_');
|
||||
}
|
||||
|
||||
// Remove invalid filename characters
|
||||
foreach (char c in Path.GetInvalidFileNameChars())
|
||||
fullFile = fullFile.Replace(c, '_');
|
||||
|
||||
if (string.IsNullOrEmpty(fullDirectory))
|
||||
return fullFile;
|
||||
else
|
||||
return Path.Combine(fullDirectory, fullFile);
|
||||
}
|
||||
catch { }
|
||||
|
||||
return path ?? string.Empty;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Versioning
|
||||
|
||||
/// <summary>
|
||||
/// Check for a new MPF version
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// Bool representing if the values are different.
|
||||
/// String representing the message to display the the user.
|
||||
/// String representing the new release URL.
|
||||
/// </returns>
|
||||
public static void CheckForNewVersion(out bool different, out string message, out string? url)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Get current assembly version
|
||||
var assemblyVersion = Assembly.GetEntryAssembly()?.GetName()?.Version;
|
||||
if (assemblyVersion == null)
|
||||
{
|
||||
different = false;
|
||||
message = "Assembly version could not be determined";
|
||||
url = null;
|
||||
return;
|
||||
}
|
||||
|
||||
string version = $"{assemblyVersion.Major}.{assemblyVersion.Minor}.{assemblyVersion.Build}";
|
||||
|
||||
// Get the latest tag from GitHub
|
||||
_ = GetRemoteVersionAndUrl(out string? tag, out url);
|
||||
different = version != tag && tag != null;
|
||||
|
||||
message = $"Local version: {version}"
|
||||
+ $"{Environment.NewLine}Remote version: {tag}"
|
||||
+ (different
|
||||
? $"{Environment.NewLine}The update URL has been added copied to your clipboard"
|
||||
: $"{Environment.NewLine}You have the newest version!");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
different = false;
|
||||
message = ex.ToString();
|
||||
url = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the current informational version formatted as a string
|
||||
/// </summary>
|
||||
public static string? GetCurrentVersion()
|
||||
{
|
||||
try
|
||||
{
|
||||
var assembly = Assembly.GetEntryAssembly();
|
||||
if (assembly == null)
|
||||
return null;
|
||||
|
||||
var assemblyVersion = Attribute.GetCustomAttribute(assembly, typeof(AssemblyInformationalVersionAttribute)) as AssemblyInformationalVersionAttribute;
|
||||
return assemblyVersion?.InformationalVersion;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return ex.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the latest version of MPF from GitHub and the release URL
|
||||
/// </summary>
|
||||
private static bool GetRemoteVersionAndUrl(out string? tag, out string? url)
|
||||
{
|
||||
tag = null; url = null;
|
||||
#if NET20 || NET35 || NET40
|
||||
// Not supported in .NET Frameworks 2.0, 3.5, or 4.0
|
||||
return false;
|
||||
#else
|
||||
using var hc = new System.Net.Http.HttpClient();
|
||||
#if NET452
|
||||
System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Tls12;
|
||||
#endif
|
||||
|
||||
// TODO: Figure out a better way than having this hardcoded...
|
||||
string releaseUrl = "https://api.github.com/repos/SabreTools/MPF/releases/latest";
|
||||
var message = new System.Net.Http.HttpRequestMessage(System.Net.Http.HttpMethod.Get, releaseUrl);
|
||||
message.Headers.Add("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:64.0) Gecko/20100101 Firefox/64.0");
|
||||
var latestReleaseJsonString = hc.SendAsync(message)?.ConfigureAwait(false).GetAwaiter().GetResult()
|
||||
.Content?.ReadAsStringAsync().ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
if (latestReleaseJsonString == null)
|
||||
return false;
|
||||
|
||||
var latestReleaseJson = Newtonsoft.Json.Linq.JObject.Parse(latestReleaseJsonString);
|
||||
if (latestReleaseJson == null)
|
||||
return false;
|
||||
|
||||
tag = latestReleaseJson["tag_name"]?.ToString();
|
||||
url = latestReleaseJson["html_url"]?.ToString();
|
||||
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
334
MPF.Frontend/Tools/OptionsLoader.cs
Normal file
334
MPF.Frontend/Tools/OptionsLoader.cs
Normal file
@@ -0,0 +1,334 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using Newtonsoft.Json;
|
||||
using SabreTools.RedumpLib.Data;
|
||||
|
||||
namespace MPF.Frontend.Tools
|
||||
{
|
||||
public static class OptionsLoader
|
||||
{
|
||||
/// <summary>
|
||||
/// Full path to the configuration file used by the program
|
||||
/// </summary>
|
||||
#if NET20 || NET35 || NET40 || NET452
|
||||
private static string ConfigurationPath => Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "config.json");
|
||||
#else
|
||||
private static string ConfigurationPath => Path.Combine(AppContext.BaseDirectory, "config.json");
|
||||
#endif
|
||||
|
||||
#region Arguments
|
||||
|
||||
/// <summary>
|
||||
/// Process any standalone arguments for the program
|
||||
/// </summary>
|
||||
/// <returns>True if one of the arguments was processed, false otherwise</returns>
|
||||
public static bool? ProcessStandaloneArguments(string[] args)
|
||||
{
|
||||
// Help options
|
||||
if (args.Length == 0 || args[0] == "-h" || args[0] == "-?")
|
||||
return null;
|
||||
|
||||
// List options
|
||||
if (args[0] == "-lc" || args[0] == "--listcodes")
|
||||
{
|
||||
Console.WriteLine("Supported Site Codes:");
|
||||
foreach (string siteCode in Extensions.ListSiteCodes())
|
||||
{
|
||||
Console.WriteLine(siteCode);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (args[0] == "-lm" || args[0] == "--listmedia")
|
||||
{
|
||||
Console.WriteLine("Supported Media Types:");
|
||||
foreach (string mediaType in Extensions.ListMediaTypes())
|
||||
{
|
||||
Console.WriteLine(mediaType);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (args[0] == "-lp" || args[0] == "--listprograms")
|
||||
{
|
||||
Console.WriteLine("Supported Programs:");
|
||||
foreach (string program in ListPrograms())
|
||||
{
|
||||
Console.WriteLine(program);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (args[0] == "-ls" || args[0] == "--listsystems")
|
||||
{
|
||||
Console.WriteLine("Supported Systems:");
|
||||
foreach (string system in Extensions.ListSystems())
|
||||
{
|
||||
Console.WriteLine(system);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process common arguments for all functionality
|
||||
/// </summary>
|
||||
/// <returns>True if all arguments pass, false otherwise</returns>
|
||||
public static bool ProcessCommonArguments(string[] args, out MediaType mediaType, out RedumpSystem? system, out string? message)
|
||||
{
|
||||
// All other use requires at least 3 arguments
|
||||
if (args.Length < 3)
|
||||
{
|
||||
mediaType = MediaType.NONE;
|
||||
system = null;
|
||||
message = "Invalid number of arguments";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check the MediaType
|
||||
mediaType = ToMediaType(args[0].Trim('"'));
|
||||
if (mediaType == MediaType.NONE)
|
||||
{
|
||||
system = null;
|
||||
message = $"{args[0]} is not a recognized media type";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check the RedumpSystem
|
||||
system = Extensions.ToRedumpSystem(args[1].Trim('"'));
|
||||
if (system == null)
|
||||
{
|
||||
message = $"{args[1]} is not a recognized system";
|
||||
return false;
|
||||
}
|
||||
|
||||
message = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// List all programs with their short usable names
|
||||
/// </summary>
|
||||
private static List<string> ListPrograms()
|
||||
{
|
||||
var programs = new List<string>();
|
||||
|
||||
foreach (var val in Enum.GetValues(typeof(InternalProgram)))
|
||||
{
|
||||
if (((InternalProgram)val!) == InternalProgram.NONE)
|
||||
continue;
|
||||
|
||||
programs.Add($"{((InternalProgram?)val).LongName()}");
|
||||
}
|
||||
|
||||
return programs;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the MediaType enum value for a given string
|
||||
/// </summary>
|
||||
/// <param name="type">String value to convert</param>
|
||||
/// <returns>MediaType represented by the string, if possible</returns>
|
||||
private static MediaType ToMediaType(string type)
|
||||
{
|
||||
return (type.ToLowerInvariant()) switch
|
||||
{
|
||||
#region Punched Media
|
||||
|
||||
"aperture"
|
||||
or "aperturecard"
|
||||
or "aperture card" => MediaType.ApertureCard,
|
||||
"jacquardloom"
|
||||
or "jacquardloomcard"
|
||||
or "jacquard loom card" => MediaType.JacquardLoomCard,
|
||||
"magneticstripe"
|
||||
or "magneticstripecard"
|
||||
or "magnetic stripe card" => MediaType.MagneticStripeCard,
|
||||
"opticalphone"
|
||||
or "opticalphonecard"
|
||||
or "optical phonecard" => MediaType.OpticalPhonecard,
|
||||
"punchcard"
|
||||
or "punchedcard"
|
||||
or "punched card" => MediaType.PunchedCard,
|
||||
"punchtape"
|
||||
or "punchedtape"
|
||||
or "punched tape" => MediaType.PunchedTape,
|
||||
|
||||
#endregion
|
||||
|
||||
#region Tape
|
||||
|
||||
"openreel"
|
||||
or "openreeltape"
|
||||
or "open reel tape" => MediaType.OpenReel,
|
||||
"datacart"
|
||||
or "datacartridge"
|
||||
or "datatapecartridge"
|
||||
or "data tape cartridge" => MediaType.DataCartridge,
|
||||
"cassette"
|
||||
or "cassettetape"
|
||||
or "cassette tape" => MediaType.Cassette,
|
||||
|
||||
#endregion
|
||||
|
||||
#region Disc / Disc
|
||||
|
||||
"bd"
|
||||
or "bdrom"
|
||||
or "bd-rom"
|
||||
or "bluray" => MediaType.BluRay,
|
||||
"cd"
|
||||
or "cdrom"
|
||||
or "cd-rom" => MediaType.CDROM,
|
||||
"dvd"
|
||||
or "dvd5"
|
||||
or "dvd-5"
|
||||
or "dvd9"
|
||||
or "dvd-9"
|
||||
or "dvdrom"
|
||||
or "dvd-rom" => MediaType.DVD,
|
||||
"fd"
|
||||
or "floppy"
|
||||
or "floppydisk"
|
||||
or "floppy disk"
|
||||
or "floppy diskette" => MediaType.FloppyDisk,
|
||||
"floptical" => MediaType.Floptical,
|
||||
"gd"
|
||||
or "gdrom"
|
||||
or "gd-rom" => MediaType.GDROM,
|
||||
"hddvd"
|
||||
or "hd-dvd"
|
||||
or "hddvdrom"
|
||||
or "hd-dvd-rom" => MediaType.HDDVD,
|
||||
"hdd"
|
||||
or "harddisk"
|
||||
or "hard disk" => MediaType.HardDisk,
|
||||
"bernoullidisk"
|
||||
or "iomegabernoullidisk"
|
||||
or "bernoulli disk"
|
||||
or "iomega bernoulli disk" => MediaType.IomegaBernoulliDisk,
|
||||
"jaz"
|
||||
or "iomegajaz"
|
||||
or "iomega jaz" => MediaType.IomegaJaz,
|
||||
"zip"
|
||||
or "zipdisk"
|
||||
or "iomegazip"
|
||||
or "iomega zip" => MediaType.IomegaZip,
|
||||
"ldrom"
|
||||
or "lvrom"
|
||||
or "ld-rom"
|
||||
or "lv-rom"
|
||||
or "laserdisc"
|
||||
or "laservision"
|
||||
or "ld-rom / lv-rom" => MediaType.LaserDisc,
|
||||
"64dd"
|
||||
or "n64dd"
|
||||
or "64dddisk"
|
||||
or "n64dddisk"
|
||||
or "64dd disk"
|
||||
or "n64dd disk" => MediaType.Nintendo64DD,
|
||||
"fds"
|
||||
or "famicom"
|
||||
or "nfds"
|
||||
or "nintendofamicom"
|
||||
or "famicomdisksystem"
|
||||
or "famicom disk system"
|
||||
or "famicom disk system disk" => MediaType.NintendoFamicomDiskSystem,
|
||||
"gc"
|
||||
or "gamecube"
|
||||
or "nintendogamecube"
|
||||
or "nintendo gamecube"
|
||||
or "gamecube disc"
|
||||
or "gamecube game disc" => MediaType.NintendoGameCubeGameDisc,
|
||||
"wii"
|
||||
or "nintendowii"
|
||||
or "nintendo wii"
|
||||
or "nintendo wii disc"
|
||||
or "wii optical disc" => MediaType.NintendoWiiOpticalDisc,
|
||||
"wiiu"
|
||||
or "nintendowiiu"
|
||||
or "nintendo wiiu"
|
||||
or "nintendo wiiu disc"
|
||||
or "wiiu optical disc"
|
||||
or "wii u optical disc" => MediaType.NintendoWiiUOpticalDisc,
|
||||
"umd" => MediaType.UMD,
|
||||
|
||||
#endregion
|
||||
|
||||
// Unsorted Formats
|
||||
"cartridge" => MediaType.Cartridge,
|
||||
"ced"
|
||||
or "rcaced"
|
||||
or "rca ced"
|
||||
or "videodisc"
|
||||
or "rca videodisc" => MediaType.CED,
|
||||
|
||||
_ => MediaType.NONE,
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Configuration
|
||||
|
||||
/// <summary>
|
||||
/// Load the current set of options from the application configuration
|
||||
/// </summary>
|
||||
public static Options LoadFromConfig()
|
||||
{
|
||||
if (!File.Exists(ConfigurationPath))
|
||||
{
|
||||
File.Create(ConfigurationPath).Dispose();
|
||||
return new Options();
|
||||
}
|
||||
|
||||
var serializer = JsonSerializer.Create();
|
||||
var stream = File.Open(ConfigurationPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
var reader = new StreamReader(stream);
|
||||
var settings = serializer.Deserialize(reader, typeof(Dictionary<string, string?>)) as Dictionary<string, string?>;
|
||||
reader.Dispose();
|
||||
return new Options(settings);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save the current set of options to the application configuration
|
||||
/// </summary>
|
||||
public static void SaveToConfig(Options options, bool saveDefault = false)
|
||||
{
|
||||
// If default values should be saved as well
|
||||
if (saveDefault)
|
||||
{
|
||||
PropertyInfo[] properties = typeof(Options).GetProperties();
|
||||
foreach (var property in properties)
|
||||
{
|
||||
// Skip dictionary properties
|
||||
if (property.Name == "Item")
|
||||
continue;
|
||||
|
||||
// Skip non-option properties
|
||||
if (property.Name == "Settings" || property.Name == "HasRedumpLogin")
|
||||
continue;
|
||||
|
||||
var val = property.GetValue(options, null);
|
||||
property.SetValue(options, val, null);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle a very strange edge case
|
||||
if (!File.Exists(ConfigurationPath))
|
||||
File.Create(ConfigurationPath).Dispose();
|
||||
|
||||
var serializer = JsonSerializer.Create();
|
||||
var sw = new StreamWriter(ConfigurationPath) { AutoFlush = true };
|
||||
var writer = new JsonTextWriter(sw) { Formatting = Formatting.Indented };
|
||||
serializer.Serialize(writer, options.Settings, typeof(Dictionary<string, string>));
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
591
MPF.Frontend/Tools/PhysicalTool.cs
Normal file
591
MPF.Frontend/Tools/PhysicalTool.cs
Normal file
@@ -0,0 +1,591 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using SabreTools.IO;
|
||||
|
||||
namespace MPF.Frontend.Tools
|
||||
{
|
||||
public static class PhysicalTool
|
||||
{
|
||||
#region Generic
|
||||
|
||||
/// <summary>s
|
||||
/// Get the last modified date for a file from a physical disc, if possible
|
||||
/// </summary>
|
||||
/// <param name="drive">Drive to extract information from</param>
|
||||
/// <param name="filePath">Relative file path</param>
|
||||
/// <returns>Output last modified date in "yyyy-mm-dd" format if possible, null on error</returns>
|
||||
public static string? GetFileDate(Drive? drive, string? filePath, bool fixTwoDigitYear = false)
|
||||
{
|
||||
// If there's no drive path, we can't do this part
|
||||
if (string.IsNullOrEmpty(drive?.Name))
|
||||
return null;
|
||||
|
||||
// If the folder no longer exists, we can't do this part
|
||||
if (!Directory.Exists(drive!.Name))
|
||||
return null;
|
||||
|
||||
// If the executable name is invalid, we can't do this part
|
||||
if (string.IsNullOrEmpty(filePath))
|
||||
return null;
|
||||
|
||||
// Now that we have the EXE name, try to get the fileinfo for it
|
||||
string exePath = Path.Combine(drive.Name, filePath);
|
||||
if (!File.Exists(exePath))
|
||||
return null;
|
||||
|
||||
// Get the last modified time
|
||||
var fi = new FileInfo(exePath);
|
||||
var lastModified = fi.LastWriteTimeUtc;
|
||||
int year = lastModified.Year;
|
||||
int month = lastModified.Month;
|
||||
int day = lastModified.Day;
|
||||
|
||||
// Fix the Y2K timestamp issue, if required
|
||||
if (fixTwoDigitYear)
|
||||
year = year >= 1900 && year < 1920 ? 2000 + year % 100 : year;
|
||||
|
||||
// Format and return the string
|
||||
var dt = new DateTime(year, month, day);
|
||||
return dt.ToString("yyyy-MM-dd");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region BD-Video
|
||||
|
||||
/// <summary>
|
||||
/// Get if the Bus Encryption Enabled (BEE) flag is set in a path
|
||||
/// </summary>
|
||||
/// <param name="drive">Drive to extract information from</param>
|
||||
/// <returns>Bus encryption enabled status if possible, false otherwise</returns>
|
||||
public static bool GetBusEncryptionEnabled(Drive? drive)
|
||||
{
|
||||
// If there's no drive path, we can't get BEE flag
|
||||
if (string.IsNullOrEmpty(drive?.Name))
|
||||
return false;
|
||||
|
||||
// If the folder no longer exists, we can't get exe name
|
||||
if (!Directory.Exists(drive!.Name))
|
||||
return false;
|
||||
|
||||
// Get the two possible file paths
|
||||
#if NET20 || NET35
|
||||
string content000 = Path.Combine(Path.Combine(drive.Name, "AACS"), "Content000.cer");
|
||||
string content001 = Path.Combine(Path.Combine(drive.Name, "AACS"), "Content001.cer");
|
||||
#else
|
||||
string content000 = Path.Combine(drive.Name, "AACS", "Content000.cer");
|
||||
string content001 = Path.Combine(drive.Name, "AACS", "Content001.cer");
|
||||
#endif
|
||||
|
||||
try
|
||||
{
|
||||
// Check the required files
|
||||
if (File.Exists(content000) && new FileInfo(content000).Length > 1)
|
||||
{
|
||||
using var fs = File.OpenRead(content000);
|
||||
_ = fs.ReadByte(); // Skip the first byte
|
||||
return fs.ReadByte() > 127;
|
||||
}
|
||||
else if (File.Exists(content001) && new FileInfo(content001).Length > 1)
|
||||
{
|
||||
using var fs = File.OpenRead(content001);
|
||||
_ = fs.ReadByte(); // Skip the first byte
|
||||
return fs.ReadByte() > 127;
|
||||
}
|
||||
|
||||
// False if neither file fits the criteria
|
||||
return false;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// We don't care what the error is right now
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region PlayStation
|
||||
|
||||
/// <summary>
|
||||
/// Get the EXE name from a PlayStation disc, if possible
|
||||
/// </summary>
|
||||
/// <param name="drive">Drive to extract information from</param>
|
||||
/// <returns>Executable name on success, null otherwise</returns>
|
||||
public static string? GetPlayStationExecutableName(Drive? drive)
|
||||
{
|
||||
// If there's no drive path, we can't get exe name
|
||||
if (string.IsNullOrEmpty(drive?.Name))
|
||||
return null;
|
||||
|
||||
// If the folder no longer exists, we can't get exe name
|
||||
if (!Directory.Exists(drive!.Name))
|
||||
return null;
|
||||
|
||||
// Get the two paths that we will need to check
|
||||
string psxExePath = Path.Combine(drive.Name, "PSX.EXE");
|
||||
string systemCnfPath = Path.Combine(drive.Name, "SYSTEM.CNF");
|
||||
|
||||
// Read the CNF file as an INI file
|
||||
var systemCnf = new IniFile(systemCnfPath);
|
||||
string? bootValue = string.Empty;
|
||||
|
||||
// PlayStation uses "BOOT" as the key
|
||||
if (systemCnf.ContainsKey("BOOT"))
|
||||
bootValue = systemCnf["BOOT"];
|
||||
|
||||
// PlayStation 2 uses "BOOT2" as the key
|
||||
if (systemCnf.ContainsKey("BOOT2"))
|
||||
bootValue = systemCnf["BOOT2"];
|
||||
|
||||
// If we had any boot value, parse it and get the executable name
|
||||
if (!string.IsNullOrEmpty(bootValue))
|
||||
{
|
||||
var match = Regex.Match(bootValue, @"cdrom.?:\\?(.*)", RegexOptions.Compiled);
|
||||
if (match.Groups.Count > 1)
|
||||
{
|
||||
string? serial = match.Groups[1].Value;
|
||||
|
||||
// Some games may have the EXE in a subfolder
|
||||
serial = Path.GetFileName(serial);
|
||||
|
||||
return serial;
|
||||
}
|
||||
}
|
||||
|
||||
// If the SYSTEM.CNF value can't be found, try PSX.EXE
|
||||
if (File.Exists(psxExePath))
|
||||
return "PSX.EXE";
|
||||
|
||||
// If neither can be found, we return null
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the serial from a PlayStation disc, if possible
|
||||
/// </summary>
|
||||
/// <param name="drive">Drive to extract information from</param>
|
||||
/// <returns>Serial on success, null otherwise</returns>
|
||||
public static string? GetPlayStationSerial(Drive? drive)
|
||||
{
|
||||
// Try to get the executable name
|
||||
string? exeName = GetPlayStationExecutableName(drive);
|
||||
if (string.IsNullOrEmpty(exeName))
|
||||
return null;
|
||||
|
||||
// Handle generic PSX.EXE
|
||||
if (exeName == "PSX.EXE")
|
||||
return null;
|
||||
|
||||
// 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
|
||||
return exeName
|
||||
.Replace('_', '-')
|
||||
.Replace(".", string.Empty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the version from a PlayStation 2 disc, if possible
|
||||
/// </summary>
|
||||
/// <param name="drive">Drive to extract information from</param>
|
||||
/// <returns>Game version if possible, null on error</returns>
|
||||
public static string? GetPlayStation2Version(Drive? drive)
|
||||
{
|
||||
// If there's no drive path, we can't do this part
|
||||
if (string.IsNullOrEmpty(drive?.Name))
|
||||
return null;
|
||||
|
||||
// If the folder no longer exists, we can't do this part
|
||||
if (!Directory.Exists(drive!.Name))
|
||||
return null;
|
||||
|
||||
// Get the SYSTEM.CNF path to check
|
||||
string systemCnfPath = Path.Combine(drive.Name, "SYSTEM.CNF");
|
||||
|
||||
// Try to parse the SYSTEM.CNF file
|
||||
var systemCnf = new IniFile(systemCnfPath);
|
||||
if (systemCnf.ContainsKey("VER"))
|
||||
return systemCnf["VER"];
|
||||
|
||||
// If "VER" can't be found, we can't do much
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the internal serial from a PlayStation 3 disc, if possible
|
||||
/// </summary>
|
||||
/// <param name="drive">Drive to extract information from</param>
|
||||
/// <returns>Internal disc serial if possible, null on error</returns>
|
||||
public static string? GetPlayStation3Serial(Drive? drive)
|
||||
{
|
||||
// If there's no drive path, we can't do this part
|
||||
if (string.IsNullOrEmpty(drive?.Name))
|
||||
return null;
|
||||
|
||||
// If the folder no longer exists, we can't do this part
|
||||
if (!Directory.Exists(drive!.Name))
|
||||
return null;
|
||||
|
||||
// Attempt to use PS3_DISC.SFB
|
||||
string sfbPath = Path.Combine(drive.Name, "PS3_DISC.SFB");
|
||||
if (File.Exists(sfbPath))
|
||||
{
|
||||
try
|
||||
{
|
||||
using var br = new BinaryReader(File.OpenRead(sfbPath));
|
||||
br.BaseStream.Seek(0x220, SeekOrigin.Begin);
|
||||
return new string(br.ReadChars(0x10)).TrimEnd('\0');
|
||||
}
|
||||
catch
|
||||
{
|
||||
// We don't care what the error was
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt to use PARAM.SFO
|
||||
#if NET20 || NET35
|
||||
string sfoPath = Path.Combine(Path.Combine(drive.Name, "PS3_GAME"), "PARAM.SFO");
|
||||
#else
|
||||
string sfoPath = Path.Combine(drive.Name, "PS3_GAME", "PARAM.SFO");
|
||||
#endif
|
||||
if (File.Exists(sfoPath))
|
||||
{
|
||||
try
|
||||
{
|
||||
using var br = new BinaryReader(File.OpenRead(sfoPath));
|
||||
br.BaseStream.Seek(-0x18, SeekOrigin.End);
|
||||
return new string(br.ReadChars(9)).TrimEnd('\0').Insert(4, "-");
|
||||
}
|
||||
catch
|
||||
{
|
||||
// We don't care what the error was
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the version from a PlayStation 3 disc, if possible
|
||||
/// </summary>
|
||||
/// <param name="drive">Drive to extract information from</param>
|
||||
/// <returns>Game version if possible, null on error</returns>
|
||||
public static string? GetPlayStation3Version(Drive? drive)
|
||||
{
|
||||
// If there's no drive path, we can't do this part
|
||||
if (string.IsNullOrEmpty(drive?.Name))
|
||||
return null;
|
||||
|
||||
// If the folder no longer exists, we can't do this part
|
||||
if (!Directory.Exists(drive!.Name))
|
||||
return null;
|
||||
|
||||
// Attempt to use PS3_DISC.SFB
|
||||
string sfbPath = Path.Combine(drive.Name, "PS3_DISC.SFB");
|
||||
if (File.Exists(sfbPath))
|
||||
{
|
||||
try
|
||||
{
|
||||
using var br = new BinaryReader(File.OpenRead(sfbPath));
|
||||
br.BaseStream.Seek(0x230, SeekOrigin.Begin);
|
||||
var discVersion = new string(br.ReadChars(0x10)).TrimEnd('\0');
|
||||
if (!string.IsNullOrEmpty(discVersion))
|
||||
return discVersion;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// We don't care what the error was
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt to use PARAM.SFO
|
||||
#if NET20 || NET35
|
||||
string sfoPath = Path.Combine(Path.Combine(drive.Name, "PS3_GAME"), "PARAM.SFO");
|
||||
#else
|
||||
string sfoPath = Path.Combine(drive.Name, "PS3_GAME", "PARAM.SFO");
|
||||
#endif
|
||||
if (File.Exists(sfoPath))
|
||||
{
|
||||
try
|
||||
{
|
||||
using var br = new BinaryReader(File.OpenRead(sfoPath));
|
||||
br.BaseStream.Seek(-0x08, SeekOrigin.End);
|
||||
return new string(br.ReadChars(5)).TrimEnd('\0');
|
||||
}
|
||||
catch
|
||||
{
|
||||
// We don't care what the error was
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the firmware version from a PlayStation 3 disc, if possible
|
||||
/// </summary>
|
||||
/// <param name="drive">Drive to extract information from</param>
|
||||
/// <returns>Firmware version if possible, null on error</returns>
|
||||
public static string? GetPlayStation3FirmwareVersion(Drive? drive)
|
||||
{
|
||||
// If there's no drive path, we can't do this part
|
||||
if (string.IsNullOrEmpty(drive?.Name))
|
||||
return null;
|
||||
|
||||
// If the folder no longer exists, we can't do this part
|
||||
if (!Directory.Exists(drive!.Name))
|
||||
return null;
|
||||
|
||||
// Attempt to read from /PS3_UPDATE/PS3UPDAT.PUP
|
||||
#if NET20 || NET35
|
||||
string pupPath = Path.Combine(Path.Combine(drive.Name, "PS3_UPDATE"), "PS3UPDAT.PUP");
|
||||
#else
|
||||
string pupPath = Path.Combine(drive.Name, "PS3_UPDATE", "PS3UPDAT.PUP");
|
||||
#endif
|
||||
if (!File.Exists(pupPath))
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
using var br = new BinaryReader(File.OpenRead(pupPath));
|
||||
br.BaseStream.Seek(0x3E, SeekOrigin.Begin);
|
||||
byte[] buf = new byte[2];
|
||||
br.Read(buf, 0, 2);
|
||||
Array.Reverse(buf);
|
||||
short location = BitConverter.ToInt16(buf, 0);
|
||||
br.BaseStream.Seek(location, SeekOrigin.Begin);
|
||||
return new string(br.ReadChars(4));
|
||||
}
|
||||
catch
|
||||
{
|
||||
// We don't care what the error was
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the internal serial from a PlayStation 4 disc, if possible
|
||||
/// </summary>
|
||||
/// <param name="drive">Drive to extract information from</param>
|
||||
/// <returns>Internal disc serial if possible, null on error</returns>
|
||||
public static string? GetPlayStation4Serial(Drive? drive)
|
||||
{
|
||||
// If there's no drive path, we can't do this part
|
||||
if (string.IsNullOrEmpty(drive?.Name))
|
||||
return null;
|
||||
|
||||
// If the folder no longer exists, we can't do this part
|
||||
if (!Directory.Exists(drive!.Name))
|
||||
return null;
|
||||
|
||||
// If we can't find param.sfo, we don't have a PlayStation 4 disc
|
||||
#if NET20 || NET35
|
||||
string paramSfoPath = Path.Combine(Path.Combine(drive.Name, "bd"), "param.sfo");
|
||||
#else
|
||||
string paramSfoPath = Path.Combine(drive.Name, "bd", "param.sfo");
|
||||
#endif
|
||||
if (!File.Exists(paramSfoPath))
|
||||
return null;
|
||||
|
||||
// Let's try reading param.sfo to find the serial at the end of the file
|
||||
try
|
||||
{
|
||||
using var br = new BinaryReader(File.OpenRead(paramSfoPath));
|
||||
br.BaseStream.Seek(-0x14, SeekOrigin.End);
|
||||
return new string(br.ReadChars(9)).Insert(4, "-");
|
||||
}
|
||||
catch
|
||||
{
|
||||
// We don't care what the error was
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the version from a PlayStation 4 disc, if possible
|
||||
/// </summary>
|
||||
/// <param name="drive">Drive to extract information from</param>
|
||||
/// <returns>Game version if possible, null on error</returns>
|
||||
public static string? GetPlayStation4Version(Drive? drive)
|
||||
{
|
||||
// If there's no drive path, we can't do this part
|
||||
if (string.IsNullOrEmpty(drive?.Name))
|
||||
return null;
|
||||
|
||||
// If the folder no longer exists, we can't do this part
|
||||
if (!Directory.Exists(drive!.Name))
|
||||
return null;
|
||||
|
||||
// If we can't find param.sfo, we don't have a PlayStation 4 disc
|
||||
#if NET20 || NET35
|
||||
string paramSfoPath = Path.Combine(Path.Combine(drive.Name, "bd"), "param.sfo");
|
||||
#else
|
||||
string paramSfoPath = Path.Combine(drive.Name, "bd", "param.sfo");
|
||||
#endif
|
||||
if (!File.Exists(paramSfoPath))
|
||||
return null;
|
||||
|
||||
// Let's try reading param.sfo to find the version at the end of the file
|
||||
try
|
||||
{
|
||||
using var br = new BinaryReader(File.OpenRead(paramSfoPath));
|
||||
br.BaseStream.Seek(-0x08, SeekOrigin.End);
|
||||
return new string(br.ReadChars(5));
|
||||
}
|
||||
catch
|
||||
{
|
||||
// We don't care what the error was
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the internal serial from a PlayStation 5 disc, if possible
|
||||
/// </summary>
|
||||
/// <param name="drive">Drive to extract information from</param>
|
||||
/// <returns>Internal disc serial if possible, null on error</returns>
|
||||
public static string? GetPlayStation5Serial(Drive? drive)
|
||||
{
|
||||
// Attempt to get the param.json file
|
||||
var json = GetPlayStation5ParamsJsonFromDrive(drive);
|
||||
if (json == null)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
return json["disc"]?[0]?["masterDataId"]?.Value<string>()?.Insert(4, "-");
|
||||
}
|
||||
catch
|
||||
{
|
||||
// We don't care what the error was
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// <summary>
|
||||
/// Get the version from a PlayStation 5 disc, if possible
|
||||
/// </summary>
|
||||
/// <param name="drive">Drive to extract information from</param>
|
||||
/// <returns>Game version if possible, null on error</returns>
|
||||
public static string? GetPlayStation5Version(Drive? drive)
|
||||
{
|
||||
// Attempt to get the param.json file
|
||||
var json = GetPlayStation5ParamsJsonFromDrive(drive);
|
||||
if (json == null)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
return json["masterVersion"]?.Value<string>();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// We don't care what the error was
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the params.json file from a drive path, if possible
|
||||
/// </summary>
|
||||
/// <param name="drive">Drive to extract information from</param>
|
||||
/// <returns>JObject representing the JSON on success, null on error</returns>
|
||||
private static JObject? GetPlayStation5ParamsJsonFromDrive(Drive? drive)
|
||||
{
|
||||
// If there's no drive path, we can't do this part
|
||||
if (string.IsNullOrEmpty(drive?.Name))
|
||||
return null;
|
||||
|
||||
// If the folder no longer exists, we can't do this part
|
||||
if (!Directory.Exists(drive!.Name))
|
||||
return null;
|
||||
|
||||
// If we can't find param.json, we don't have a PlayStation 5 disc
|
||||
#if NET20 || NET35
|
||||
string paramJsonPath = Path.Combine(Path.Combine(drive.Name, "bd"), "param.json");
|
||||
#else
|
||||
string paramJsonPath = Path.Combine(drive.Name, "bd", "param.json");
|
||||
#endif
|
||||
return GetPlayStation5ParamsJsonFromFile(paramJsonPath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the params.json file from a filename, if possible
|
||||
/// </summary>
|
||||
/// <param name="filename">Filename to check</param>
|
||||
/// <returns>JObject representing the JSON on success, null on error</returns>
|
||||
private static JObject? GetPlayStation5ParamsJsonFromFile(string? filename)
|
||||
{
|
||||
// If the file doesn't exist
|
||||
if (string.IsNullOrEmpty(filename) || !File.Exists(filename))
|
||||
return null;
|
||||
|
||||
// Let's try reading param.json to find the version in the unencrypted JSON
|
||||
try
|
||||
{
|
||||
using var br = new BinaryReader(File.OpenRead(filename));
|
||||
br.BaseStream.Seek(0x800, SeekOrigin.Begin);
|
||||
byte[] jsonBytes = br.ReadBytes((int)(br.BaseStream.Length - 0x800));
|
||||
return JsonConvert.DeserializeObject(Encoding.ASCII.GetString(jsonBytes)) as JObject;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// We don't care what the error was
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Xbox
|
||||
|
||||
/// <summary>
|
||||
/// Get all filenames for Xbox One and Xbox Series X
|
||||
/// </summary>
|
||||
/// <param name="drive">Drive to extract information from</param>
|
||||
/// <returns>Filenames if possible, null on error</returns>
|
||||
public static string? GetXboxFilenames(Drive? drive)
|
||||
{
|
||||
// If there's no drive path, we can't get BEE flag
|
||||
if (string.IsNullOrEmpty(drive?.Name))
|
||||
return null;
|
||||
|
||||
// If the folder no longer exists, we can't get exe name
|
||||
if (!Directory.Exists(drive!.Name))
|
||||
return null;
|
||||
|
||||
// Get the MSXC directory path
|
||||
string msxc = Path.Combine(drive.Name, "MSXC");
|
||||
if (!Directory.Exists(msxc))
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
var files = Directory.GetFiles(msxc, "*", SearchOption.TopDirectoryOnly);
|
||||
var filenames = files.Select(Path.GetFileName).ToArray();
|
||||
return string.Join("\n", filenames);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// We don't care what the error is right now
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
415
MPF.Frontend/Tools/ProtectionTool.cs
Normal file
415
MPF.Frontend/Tools/ProtectionTool.cs
Normal file
@@ -0,0 +1,415 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using BinaryObjectScanner;
|
||||
|
||||
namespace MPF.Frontend.Tools
|
||||
{
|
||||
public static class ProtectionTool
|
||||
{
|
||||
/// <summary>
|
||||
/// Run protection scan on a given path
|
||||
/// </summary>
|
||||
/// <param name="path">Path to scan for protection</param>
|
||||
/// <param name="options">Options object that determines what to scan</param>
|
||||
/// <param name="progress">Optional progress callback</param>
|
||||
/// <returns>Set of all detected copy protections with an optional error string</returns>
|
||||
public static async Task<ProtectionDictionary> RunProtectionScanOnPath(string path,
|
||||
Frontend.Options options,
|
||||
IProgress<ProtectionProgress>? progress = null)
|
||||
{
|
||||
#if NET40
|
||||
var found = await Task.Factory.StartNew(() =>
|
||||
#else
|
||||
var found = await Task.Run(() =>
|
||||
#endif
|
||||
{
|
||||
var scanner = new Scanner(
|
||||
options.ScanArchivesForProtection,
|
||||
scanContents: true, // Hardcoded value to avoid issues
|
||||
scanGameEngines: false, // Hardcoded value to avoid issues
|
||||
options.ScanPackersForProtection,
|
||||
scanPaths: true, // Hardcoded value to avoid issues
|
||||
options.IncludeDebugProtectionInformation,
|
||||
progress);
|
||||
|
||||
return scanner.GetProtections(path);
|
||||
});
|
||||
|
||||
// If nothing was returned, return
|
||||
if (found == null || found.Count == 0)
|
||||
return [];
|
||||
|
||||
// Filter out any empty protections
|
||||
found.ClearEmptyKeys();
|
||||
|
||||
// Return the filtered set of protections
|
||||
return found;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Format found protections to a deduplicated, ordered string
|
||||
/// </summary>
|
||||
/// <param name="protections">Dictionary of file to list of protection mappings</param>
|
||||
/// <returns>Detected protections, if any</returns>
|
||||
public static string? FormatProtections(ProtectionDictionary? protections)
|
||||
{
|
||||
// If the filtered list is empty in some way, return
|
||||
if (protections == null)
|
||||
return "(CHECK WITH PROTECTIONID)";
|
||||
else if (protections.Count == 0)
|
||||
return "None found [OMIT FROM SUBMISSION]";
|
||||
|
||||
// Get an ordered list of distinct found protections
|
||||
var orderedDistinctProtections = protections
|
||||
.SelectMany(kvp => kvp.Value)
|
||||
.Distinct()
|
||||
.OrderBy(p => p);
|
||||
|
||||
// Sanitize and join protections for writing
|
||||
string protectionString = SanitizeFoundProtections(orderedDistinctProtections);
|
||||
if (string.IsNullOrEmpty(protectionString))
|
||||
return "None found [OMIT FROM SUBMISSION]";
|
||||
|
||||
return protectionString;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the existence of an anti-modchip string from a PlayStation disc, if possible
|
||||
/// </summary>
|
||||
/// <param name="path">Path to scan for anti-modchip strings</param>
|
||||
/// <returns>Anti-modchip existence if possible, false on error</returns>
|
||||
public static async Task<bool> GetPlayStationAntiModchipDetected(string? path)
|
||||
{
|
||||
// If there is no valid path
|
||||
if (string.IsNullOrEmpty(path))
|
||||
return false;
|
||||
|
||||
#if NET40
|
||||
return await Task.Factory.StartNew(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var antiModchip = new BinaryObjectScanner.Protection.PSXAntiModchip();
|
||||
foreach (string file in Directory.EnumerateFiles(path, "*", SearchOption.AllDirectories))
|
||||
{
|
||||
try
|
||||
{
|
||||
byte[] fileContent = File.ReadAllBytes(file);
|
||||
var protection = antiModchip.CheckContents(file, fileContent, false);
|
||||
if (!string.IsNullOrEmpty(protection))
|
||||
return true;
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
|
||||
return false;
|
||||
});
|
||||
#else
|
||||
return await Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var antiModchip = new BinaryObjectScanner.Protection.PSXAntiModchip();
|
||||
#if NET20 || NET35
|
||||
foreach (string file in Directory.GetFiles(path, "*", SearchOption.AllDirectories))
|
||||
#else
|
||||
foreach (string file in Directory.EnumerateFiles(path, "*", SearchOption.AllDirectories))
|
||||
#endif
|
||||
{
|
||||
try
|
||||
{
|
||||
byte[] fileContent = File.ReadAllBytes(file);
|
||||
var protection = antiModchip.CheckContents(file, fileContent, false);
|
||||
if (!string.IsNullOrEmpty(protection))
|
||||
return true;
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
|
||||
return false;
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sanitize unnecessary protection duplication from output
|
||||
/// </summary>
|
||||
/// <param name="foundProtections">Enumerable of found protections</param>
|
||||
public static string SanitizeFoundProtections(IEnumerable<string> foundProtections)
|
||||
{
|
||||
// EXCEPTIONS
|
||||
if (foundProtections.Any(p => p.StartsWith("[Exception opening file")))
|
||||
{
|
||||
foundProtections = foundProtections.Where(p => !p.StartsWith("[Exception opening file"));
|
||||
#if NET20 || NET35 || NET40 || NET452 || NET462
|
||||
var tempList = new List<string> { "Exception occurred while scanning [RESCAN NEEDED]" };
|
||||
tempList.AddRange(foundProtections);
|
||||
foundProtections = tempList.OrderBy(p => p);
|
||||
#else
|
||||
foundProtections = foundProtections
|
||||
.Prepend("Exception occurred while scanning [RESCAN NEEDED]")
|
||||
.OrderBy(p => p);
|
||||
#endif
|
||||
}
|
||||
|
||||
// ActiveMARK
|
||||
if (foundProtections.Any(p => p == "ActiveMARK 5") && foundProtections.Any(p => p == "ActiveMARK"))
|
||||
foundProtections = foundProtections.Where(p => p != "ActiveMARK");
|
||||
|
||||
// Cactus Data Shield
|
||||
if (foundProtections.Any(p => Regex.IsMatch(p, @"Cactus Data Shield [0-9]{3} .+", RegexOptions.Compiled)) && foundProtections.Any(p => p == "Cactus Data Shield 200"))
|
||||
foundProtections = foundProtections.Where(p => p != "Cactus Data Shield 200");
|
||||
|
||||
// CD-Check
|
||||
foundProtections = foundProtections.Where(p => p != "Executable-Based CD Check");
|
||||
|
||||
// CD-Cops
|
||||
if (foundProtections.Any(p => p == "CD-Cops") && foundProtections.Any(p => p.StartsWith("CD-Cops") && p.Length > "CD-Cops".Length))
|
||||
foundProtections = foundProtections.Where(p => p != "CD-Cops");
|
||||
|
||||
// CD-Key / Serial
|
||||
foundProtections = foundProtections.Where(p => p != "CD-Key / Serial");
|
||||
|
||||
// Electronic Arts
|
||||
if (foundProtections.Any(p => p == "EA CdKey Registration Module") && foundProtections.Any(p => p.StartsWith("EA CdKey Registration Module") && p.Length > "EA CdKey Registration Module".Length))
|
||||
foundProtections = foundProtections.Where(p => p != "EA CdKey Registration Module");
|
||||
if (foundProtections.Any(p => p == "EA DRM Protection") && foundProtections.Any(p => p.StartsWith("EA DRM Protection") && p.Length > "EA DRM Protection".Length))
|
||||
foundProtections = foundProtections.Where(p => p != "EA DRM Protection");
|
||||
|
||||
// Games for Windows LIVE
|
||||
if (foundProtections.Any(p => p == "Games for Windows LIVE") && foundProtections.Any(p => p.StartsWith("Games for Windows LIVE") && !p.Contains("Zero Day Piracy Protection") && p.Length > "Games for Windows LIVE".Length))
|
||||
foundProtections = foundProtections.Where(p => p != "Games for Windows LIVE");
|
||||
|
||||
// Impulse Reactor
|
||||
if (foundProtections.Any(p => p.StartsWith("Impulse Reactor Core Module")) && foundProtections.Any(p => p == "Impulse Reactor"))
|
||||
foundProtections = foundProtections.Where(p => p != "Impulse Reactor");
|
||||
|
||||
// JoWood X-Prot
|
||||
if (foundProtections.Any(p => p.StartsWith("JoWood X-Prot")))
|
||||
{
|
||||
if (foundProtections.Any(p => Regex.IsMatch(p, @"JoWood X-Prot [0-9]\.[0-9]\.[0-9]\.[0-9]{2}", RegexOptions.Compiled)))
|
||||
{
|
||||
foundProtections = foundProtections.Where(p => p != "JoWood X-Prot")
|
||||
.Where(p => p != "JoWood X-Prot v1.0-v1.3")
|
||||
.Where(p => p != "JoWood X-Prot v1.4+")
|
||||
.Where(p => p != "JoWood X-Prot v2");
|
||||
}
|
||||
else if (foundProtections.Any(p => p == "JoWood X-Prot v2"))
|
||||
{
|
||||
foundProtections = foundProtections.Where(p => p != "JoWood X-Prot")
|
||||
.Where(p => p != "JoWood X-Prot v1.0-v1.3")
|
||||
.Where(p => p != "JoWood X-Prot v1.4+");
|
||||
}
|
||||
else if (foundProtections.Any(p => p == "JoWood X-Prot v1.4+"))
|
||||
{
|
||||
foundProtections = foundProtections.Where(p => p != "JoWood X-Prot")
|
||||
.Where(p => p != "JoWood X-Prot v1.0-v1.3");
|
||||
}
|
||||
else if (foundProtections.Any(p => p == "JoWood X-Prot v1.0-v1.3"))
|
||||
{
|
||||
foundProtections = foundProtections.Where(p => p != "JoWood X-Prot");
|
||||
}
|
||||
}
|
||||
|
||||
// LaserLok
|
||||
// TODO: Figure this one out
|
||||
|
||||
// Online Registration
|
||||
foundProtections = foundProtections.Where(p => !p.StartsWith("Executable-Based Online Registration"));
|
||||
|
||||
// ProtectDISC / VOB ProtectCD/DVD
|
||||
// TODO: Figure this one out
|
||||
|
||||
// SafeCast
|
||||
// TODO: Figure this one out
|
||||
|
||||
// Cactus Data Shield / SafeDisc
|
||||
if (foundProtections.Any(p => p == "Cactus Data Shield 300 (Confirm presence of other CDS-300 files)"))
|
||||
{
|
||||
foundProtections = foundProtections
|
||||
.Where(p => p != "Cactus Data Shield 300 (Confirm presence of other CDS-300 files)");
|
||||
|
||||
if (foundProtections.Any(p => !p.StartsWith("SafeDisc")))
|
||||
{
|
||||
#if NET20 || NET35 || NET40 || NET452 || NET462
|
||||
var tempList = new List<string>();
|
||||
tempList.AddRange(foundProtections);
|
||||
tempList.Add("Cactus Data Shield 300");
|
||||
foundProtections = tempList;
|
||||
#else
|
||||
foundProtections = foundProtections.Append("Cactus Data Shield 300");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// SafeDisc
|
||||
if (foundProtections.Any(p => p.StartsWith("SafeDisc")))
|
||||
{
|
||||
// Confirmed this set of checks works with Redump entries 10430, 11347, 13230, 18614, 28257, 31149, 31824, 52606, 57721, 58455, 58573, 62935, 63941, 64255, 65569, 66005, 70504, 73502, 74520, 78048, 79729, 83468, 98589, and 101261.
|
||||
|
||||
// Best case scenario for SafeDisc 2+: A full SafeDisc version is found in a line starting with "Macrovision Protected Application". All other SafeDisc detections can be safely scrubbed.
|
||||
// TODO: Scrub "Macrovision Protected Application, " from before the SafeDisc version.
|
||||
if (foundProtections.Any(p => Regex.IsMatch(p, @"SafeDisc [0-9]\.[0-9]{2}\.[0-9]{3}", RegexOptions.Compiled) && p.StartsWith("Macrovision Protected Application") && !p.Contains("SRV Tool APP")))
|
||||
{
|
||||
foundProtections = foundProtections.Where(p => !p.StartsWith("Macrovision Protection File"))
|
||||
.Where(p => !p.StartsWith("Macrovision Security Driver"))
|
||||
.Where(p => !p.Contains("SRV Tool APP"))
|
||||
.Where(p => p != "SafeDisc")
|
||||
.Where(p => !p.StartsWith("Macrovision Protected Application [Version Expunged]"))
|
||||
.Where(p => !(Regex.IsMatch(p, @"SafeDisc [0-9]\.[0-9]{2}\.[0-9]{3}-[0-9]\.[0-9]{2}\.[0-9]{3}", RegexOptions.Compiled)))
|
||||
.Where(p => !(Regex.IsMatch(p, @"SafeDisc [0-9]\.[0-9]{2}\.[0-9]{3}\+", RegexOptions.Compiled)))
|
||||
.Where(p => !(Regex.IsMatch(p, @"SafeDisc [0-9]\.[0-9]{2}\.[0-9]{3}\/4\+", RegexOptions.Compiled)))
|
||||
.Where(p => p != "SafeDisc 1/Lite")
|
||||
.Where(p => p != "SafeDisc 2+")
|
||||
.Where(p => p != "SafeDisc 3+ (DVD)");
|
||||
}
|
||||
|
||||
// Next best case for SafeDisc 2+: A full SafeDisc version is found from the "SafeDisc SRV Tool APP".
|
||||
else if (foundProtections.Any(p => Regex.IsMatch(p, @"SafeDisc [0-9]\.[0-9]{2}\.[0-9]{3}", RegexOptions.Compiled) && p.StartsWith("Macrovision Protected Application") && p.Contains("SRV Tool APP")))
|
||||
{
|
||||
foundProtections = foundProtections.Where(p => !p.StartsWith("Macrovision Protection File"))
|
||||
.Where(p => !p.StartsWith("Macrovision Security Driver"))
|
||||
.Where(p => p != "SafeDisc")
|
||||
.Where(p => !p.StartsWith("Macrovision Protected Application [Version Expunged]"))
|
||||
.Where(p => !(Regex.IsMatch(p, @"SafeDisc [0-9]\.[0-9]{2}\.[0-9]{3}-[0-9]\.[0-9]{2}\.[0-9]{3}", RegexOptions.Compiled)))
|
||||
.Where(p => !(Regex.IsMatch(p, @"SafeDisc [0-9]\.[0-9]{2}\.[0-9]{3}\+", RegexOptions.Compiled)))
|
||||
.Where(p => !(Regex.IsMatch(p, @"SafeDisc [0-9]\.[0-9]{2}\.[0-9]{3}\/4\+", RegexOptions.Compiled)))
|
||||
.Where(p => p != "SafeDisc 1/Lite")
|
||||
.Where(p => p != "SafeDisc 2+")
|
||||
.Where(p => p != "SafeDisc 3+ (DVD)");
|
||||
}
|
||||
|
||||
// Covers specific edge cases where older drivers are erroneously placed in discs with a newer version of SafeDisc, and the specific SafeDisc version is expunged.
|
||||
else if (foundProtections.Any(p => Regex.IsMatch(p, @"SafeDisc [1-2]\.[0-9]{2}\.[0-9]{3}-[1-2]\.[0-9]{2}\.[0-9]{3}$", RegexOptions.Compiled) || Regex.IsMatch(p, @"SafeDisc [1-2]\.[0-9]{2}\.[0-9]{3}$", RegexOptions.Compiled)) && foundProtections.Any(p => p == "SafeDisc 3+ (DVD)"))
|
||||
{
|
||||
foundProtections = foundProtections.Where(p => !p.StartsWith("Macrovision Protection File"))
|
||||
.Where(p => !p.StartsWith("Macrovision Protected Application [Version Expunged]"))
|
||||
.Where(p => !p.StartsWith("Macrovision Security Driver"))
|
||||
.Where(p => !(Regex.IsMatch(p, @"SafeDisc [1-2]\.[0-9]{2}\.[0-9]{3}\+", RegexOptions.Compiled)))
|
||||
.Where(p => !(Regex.IsMatch(p, @"SafeDisc [1-2]\.[0-9]{2}\.[0-9]{3}-[1-2]\.[0-9]{2}\.[0-9]{3}", RegexOptions.Compiled)))
|
||||
.Where(p => p != "SafeDisc")
|
||||
.Where(p => p != "SafeDisc 1/Lite")
|
||||
.Where(p => p != "SafeDisc 2+");
|
||||
}
|
||||
|
||||
// Best case for SafeDisc 1.X: A full SafeDisc version is found that isn't part of a version range.
|
||||
else if (foundProtections.Any(p => Regex.IsMatch(p, @"SafeDisc 1\.[0-9]{2}\.[0-9]{3}$", RegexOptions.Compiled) && !(Regex.IsMatch(p, @"SafeDisc 1\.[0-9]{2}\.[0-9]{3}-[0-9]\.[0-9]{2}\.[0-9]{3}", RegexOptions.Compiled))))
|
||||
{
|
||||
foundProtections = foundProtections.Where(p => !p.StartsWith("Macrovision Protection File"))
|
||||
.Where(p => !p.StartsWith("Macrovision Security Driver"))
|
||||
.Where(p => !(Regex.IsMatch(p, @"SafeDisc [0-9]\.[0-9]{2}\.[0-9]{3}-[0-9]\.[0-9]{2}\.[0-9]{3}", RegexOptions.Compiled)))
|
||||
.Where(p => !(Regex.IsMatch(p, @"SafeDisc [0-9]\.[0-9]{2}\.[0-9]{3}\+", RegexOptions.Compiled)))
|
||||
.Where(p => p != "SafeDisc")
|
||||
.Where(p => p != "SafeDisc 1")
|
||||
.Where(p => p != "SafeDisc 1/Lite");
|
||||
}
|
||||
|
||||
// Next best case for SafeDisc 1: A SafeDisc version range is found from "SECDRV.SYS".
|
||||
// TODO: Scrub "Macrovision Security Driver {Version}" from before the SafeDisc version.
|
||||
else if (foundProtections.Any(p => p.StartsWith("Macrovision Security Driver") && Regex.IsMatch(p, @"SafeDisc 1\.[0-9]{2}\.[0-9]{3}-[1-2]\.[0-9]{2}\.[0-9]{3}", RegexOptions.Compiled) || Regex.IsMatch(p, @"SafeDisc 1\.[0-9]{2}\.[0-9]{3}$")))
|
||||
{
|
||||
foundProtections = foundProtections.Where(p => !p.StartsWith("Macrovision Protection File"))
|
||||
.Where(p => !p.StartsWith("Macrovision Protected Application [Version Expunged]"))
|
||||
.Where(p => !(Regex.IsMatch(p, @"SafeDisc [0-9]\.[0-9]{2}\.[0-9]{3}\+", RegexOptions.Compiled)))
|
||||
.Where(p => !(Regex.IsMatch(p, @"SafeDisc 1\.[0-9]{2}\.[0-9]{3}-[0-9]\.[0-9]{2}\.[0-9]{3}", RegexOptions.Compiled)))
|
||||
.Where(p => p != "SafeDisc")
|
||||
.Where(p => p != "SafeDisc 1")
|
||||
.Where(p => p != "SafeDisc 1/Lite");
|
||||
}
|
||||
|
||||
// Next best case for SafeDisc 2+: A SafeDisc version range is found from "SECDRV.SYS".
|
||||
// TODO: Scrub "Macrovision Security Driver {Version}" from before the SafeDisc version.
|
||||
else if (foundProtections.Any(p => p.StartsWith("Macrovision Security Driver")))
|
||||
{
|
||||
foundProtections = foundProtections.Where(p => !p.StartsWith("Macrovision Protection File"))
|
||||
.Where(p => !p.StartsWith("Macrovision Protected Application [Version Expunged]"))
|
||||
.Where(p => !(Regex.IsMatch(p, @"SafeDisc [0-9]\.[0-9]{2}\.[0-9]{3}\+", RegexOptions.Compiled)))
|
||||
.Where(p => !(Regex.IsMatch(p, @"SafeDisc 1\.[0-9]{2}\.[0-9]{3}-[0-9]\.[0-9]{2}\.[0-9]{3}", RegexOptions.Compiled)))
|
||||
.Where(p => p != "SafeDisc")
|
||||
.Where(p => p != "SafeDisc 1")
|
||||
.Where(p => p != "SafeDisc 1/Lite")
|
||||
.Where(p => p != "SafeDisc 2+")
|
||||
.Where(p => p != "SafeDisc 3+ (DVD)");
|
||||
}
|
||||
|
||||
// Only SafeDisc Lite is found.
|
||||
else if (foundProtections.Any(p => p == "SafeDisc Lite"))
|
||||
{
|
||||
foundProtections = foundProtections.Where(p => p != "SafeDisc")
|
||||
.Where(p => !(Regex.IsMatch(p, @"SafeDisc 1\.[0-9]{2}\.[0-9]{3}-1\.[0-9]{2}\.[0-9]{3}\/Lite", RegexOptions.Compiled)));
|
||||
}
|
||||
|
||||
// Only SafeDisc 3+ is found.
|
||||
else if (foundProtections.Any(p => p == "SafeDisc 3+ (DVD)"))
|
||||
{
|
||||
foundProtections = foundProtections.Where(p => p != "SafeDisc")
|
||||
.Where(p => p != "SafeDisc 2+")
|
||||
.Where(p => !p.StartsWith("Macrovision Protection File"))
|
||||
.Where(p => !(Regex.IsMatch(p, @"SafeDisc [0-9]\.[0-9]{2}\.[0-9]{3}\+", RegexOptions.Compiled)));
|
||||
}
|
||||
|
||||
// Only SafeDisc 2+ is found.
|
||||
else if (foundProtections.Any(p => p == "SafeDisc 2+"))
|
||||
{
|
||||
foundProtections = foundProtections.Where(p => p != "SafeDisc")
|
||||
.Where(p => !p.StartsWith("Macrovision Protection File"))
|
||||
.Where(p => !(Regex.IsMatch(p, @"SafeDisc [0-9]\.[0-9]{2}\.[0-9]{3}\+", RegexOptions.Compiled)));
|
||||
}
|
||||
}
|
||||
|
||||
// SecuROM
|
||||
// TODO: Figure this one out
|
||||
|
||||
// SolidShield
|
||||
// TODO: Figure this one out
|
||||
|
||||
// StarForce
|
||||
if (foundProtections.Any(p => p.StartsWith("StarForce")))
|
||||
{
|
||||
if (foundProtections.Any(p => Regex.IsMatch(p, @"StarForce [0-9]+\..+", RegexOptions.Compiled)))
|
||||
{
|
||||
foundProtections = foundProtections.Where(p => p != "StarForce")
|
||||
.Where(p => p != "StarForce 3-5")
|
||||
.Where(p => p != "StarForce 5")
|
||||
.Where(p => p != "StarForce 5 [Protected Module]");
|
||||
}
|
||||
else if (foundProtections.Any(p => p == "StarForce 5 [Protected Module]"))
|
||||
{
|
||||
foundProtections = foundProtections.Where(p => p != "StarForce")
|
||||
.Where(p => p != "StarForce 3-5")
|
||||
.Where(p => p != "StarForce 5");
|
||||
}
|
||||
else if (foundProtections.Any(p => p == "StarForce 5"))
|
||||
{
|
||||
foundProtections = foundProtections.Where(p => p != "StarForce")
|
||||
.Where(p => p != "StarForce 3-5");
|
||||
}
|
||||
else if (foundProtections.Any(p => p == "StarForce 3-5"))
|
||||
{
|
||||
foundProtections = foundProtections.Where(p => p != "StarForce");
|
||||
}
|
||||
}
|
||||
|
||||
// Sysiphus
|
||||
if (foundProtections.Any(p => p == "Sysiphus") && foundProtections.Any(p => p.StartsWith("Sysiphus") && p.Length > "Sysiphus".Length))
|
||||
foundProtections = foundProtections.Where(p => p != "Sysiphus");
|
||||
|
||||
// TAGES
|
||||
// TODO: Figure this one out
|
||||
|
||||
// XCP
|
||||
if (foundProtections.Any(p => p == "XCP") && foundProtections.Any(p => p.StartsWith("XCP") && p.Length > "XCP".Length))
|
||||
foundProtections = foundProtections.Where(p => p != "XCP");
|
||||
|
||||
return string.Join(", ", foundProtections.ToArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
937
MPF.Frontend/Tools/SubmissionGenerator.cs
Normal file
937
MPF.Frontend/Tools/SubmissionGenerator.cs
Normal file
@@ -0,0 +1,937 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BinaryObjectScanner;
|
||||
using MPF.Processors;
|
||||
using SabreTools.RedumpLib;
|
||||
using SabreTools.RedumpLib.Data;
|
||||
using SabreTools.RedumpLib.Web;
|
||||
|
||||
namespace MPF.Frontend.Tools
|
||||
{
|
||||
/// <summary>
|
||||
/// Generator for SubmissionInfo objects
|
||||
/// </summary>
|
||||
internal static class SubmissionGenerator
|
||||
{
|
||||
#region Constants
|
||||
|
||||
private const string RequiredValue = "(REQUIRED)";
|
||||
private const string RequiredIfExistsValue = "(REQUIRED, IF EXISTS)";
|
||||
private const string OptionalValue = "(OPTIONAL)";
|
||||
|
||||
#endregion
|
||||
|
||||
#region Extraction and Filling
|
||||
|
||||
/// <summary>
|
||||
/// Extract all of the possible information from a given input combination
|
||||
/// </summary>
|
||||
/// <param name="outputPath">Output path to write to</param>
|
||||
/// <param name="drive">Drive object representing the current drive</param>
|
||||
/// <param name="system">Currently selected system</param>
|
||||
/// <param name="mediaType">Currently selected media type</param>
|
||||
/// <param name="options">Options object representing user-defined options</param>
|
||||
/// <param name="processor">Processor object representing how to process the outputs</param>
|
||||
/// <param name="resultProgress">Optional result progress callback</param>
|
||||
/// <param name="protectionProgress">Optional protection progress callback</param>
|
||||
/// <returns>SubmissionInfo populated based on outputs, null on error</returns>
|
||||
public static async Task<SubmissionInfo?> ExtractOutputInformation(
|
||||
string outputPath,
|
||||
Drive? drive,
|
||||
RedumpSystem? system,
|
||||
MediaType? mediaType,
|
||||
Frontend.Options options,
|
||||
BaseProcessor processor,
|
||||
IProgress<ResultEventArgs>? resultProgress = null,
|
||||
IProgress<ProtectionProgress>? protectionProgress = null)
|
||||
{
|
||||
// Ensure the current disc combination should exist
|
||||
if (!system.MediaTypes().Contains(mediaType))
|
||||
return null;
|
||||
|
||||
// Split the output path for easier use
|
||||
var outputDirectory = Path.GetDirectoryName(outputPath);
|
||||
string outputFilename = Path.GetFileName(outputPath);
|
||||
|
||||
// Check that all of the relevant files are there
|
||||
List<string> missingFiles = processor.FoundAllFiles(outputDirectory, outputFilename);
|
||||
if (missingFiles.Count > 0)
|
||||
{
|
||||
resultProgress?.Report(ResultEventArgs.Failure($"There were files missing from the output:\n{string.Join("\n", [.. missingFiles])}"));
|
||||
resultProgress?.Report(ResultEventArgs.Failure($"This may indicate an issue with the hardware or media, including unsupported devices.\nPlease see dumping program documentation for more details."));
|
||||
return null;
|
||||
}
|
||||
|
||||
// Sanitize the output filename to strip off any potential extension
|
||||
outputFilename = Path.GetFileNameWithoutExtension(outputFilename);
|
||||
|
||||
// Create the SubmissionInfo object with all user-inputted values by default
|
||||
string combinedBase;
|
||||
if (string.IsNullOrEmpty(outputDirectory))
|
||||
combinedBase = outputFilename;
|
||||
else
|
||||
combinedBase = Path.Combine(outputDirectory, outputFilename);
|
||||
|
||||
// Create the default submission info
|
||||
SubmissionInfo info = CreateDefaultSubmissionInfo(processor, system, mediaType, options.AddPlaceholders);
|
||||
|
||||
// Get specific tool output handling
|
||||
processor?.GenerateSubmissionInfo(info, combinedBase, options.EnableRedumpCompatibility);
|
||||
if (options.IncludeArtifacts)
|
||||
info.Artifacts = processor?.GenerateArtifacts(combinedBase);
|
||||
|
||||
// Get a list of matching IDs for each line in the DAT
|
||||
if (!string.IsNullOrEmpty(info.TracksAndWriteOffsets!.ClrMameProData) && options.HasRedumpLogin)
|
||||
#if NET40
|
||||
_ = FillFromRedump(options, info, resultProgress);
|
||||
#else
|
||||
_ = await FillFromRedump(options, info, resultProgress);
|
||||
#endif
|
||||
|
||||
// If we have both ClrMamePro and Size and Checksums data, remove the ClrMamePro
|
||||
if (!string.IsNullOrEmpty(info.SizeAndChecksums?.CRC32))
|
||||
info.TracksAndWriteOffsets.ClrMameProData = null;
|
||||
|
||||
// Add the volume label to comments, if possible or necessary
|
||||
string? volLabels = FormatVolumeLabels(drive?.VolumeLabel, processor?.VolumeLabels);
|
||||
if (volLabels != null)
|
||||
info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.VolumeLabel] = volLabels;
|
||||
|
||||
// Extract info based generically on MediaType
|
||||
ProcessMediaType(info, mediaType, options.AddPlaceholders);
|
||||
|
||||
// Extract info based specifically on RedumpSystem
|
||||
ProcessSystem(info, system, drive, options.AddPlaceholders, processor is DiscImageCreator, combinedBase);
|
||||
|
||||
// Run anti-modchip check, if necessary
|
||||
if (drive != null && system.SupportsAntiModchipScans() && info.CopyProtection!.AntiModchip == YesNo.NULL)
|
||||
{
|
||||
resultProgress?.Report(ResultEventArgs.Success("Checking for anti-modchip strings... this might take a while!"));
|
||||
info.CopyProtection.AntiModchip = await ProtectionTool.GetPlayStationAntiModchipDetected(drive?.Name) ? YesNo.Yes : YesNo.No;
|
||||
resultProgress?.Report(ResultEventArgs.Success("Anti-modchip string scan complete!"));
|
||||
}
|
||||
|
||||
// Run copy protection, if possible or necessary
|
||||
if (system.SupportsCopyProtectionScans())
|
||||
{
|
||||
resultProgress?.Report(ResultEventArgs.Success("Running copy protection scan... this might take a while!"));
|
||||
|
||||
ProtectionDictionary? protections = null;
|
||||
try
|
||||
{
|
||||
if (options.ScanForProtection && drive?.Name != null)
|
||||
protections = await ProtectionTool.RunProtectionScanOnPath(drive.Name, options, protectionProgress);
|
||||
|
||||
var protectionString = ProtectionTool.FormatProtections(protections);
|
||||
|
||||
info.CopyProtection!.Protection += protectionString;
|
||||
info.CopyProtection.FullProtections = ReformatProtectionDictionary(protections);
|
||||
resultProgress?.Report(ResultEventArgs.Success("Copy protection scan complete!"));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
resultProgress?.Report(ResultEventArgs.Failure(ex.ToString()));
|
||||
}
|
||||
}
|
||||
|
||||
// Set fields that may have automatic filling otherwise
|
||||
info.CommonDiscInfo!.Category ??= DiscCategory.Games;
|
||||
info.VersionAndEditions!.Version ??= options.AddPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
|
||||
// Comments and contents have odd handling
|
||||
if (string.IsNullOrEmpty(info.CommonDiscInfo.Comments))
|
||||
info.CommonDiscInfo.Comments = options.AddPlaceholders ? OptionalValue : string.Empty;
|
||||
if (string.IsNullOrEmpty(info.CommonDiscInfo.Contents))
|
||||
info.CommonDiscInfo.Contents = options.AddPlaceholders ? OptionalValue : string.Empty;
|
||||
|
||||
// Normalize the disc type with all current information
|
||||
Validator.NormalizeDiscType(info);
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fill in a SubmissionInfo object from Redump, if possible
|
||||
/// </summary>
|
||||
/// <param name="options">Options object representing user-defined options</param>
|
||||
/// <param name="info">Existing SubmissionInfo object to fill</param>
|
||||
/// <param name="resultProgress">Optional result progress callback</param>
|
||||
public async static Task<bool> FillFromRedump(Frontend.Options options, SubmissionInfo info, IProgress<ResultEventArgs>? resultProgress = null)
|
||||
{
|
||||
// If no username is provided
|
||||
if (string.IsNullOrEmpty(options.RedumpUsername) || string.IsNullOrEmpty(options.RedumpPassword))
|
||||
return false;
|
||||
|
||||
// Set the current dumper based on username
|
||||
info.DumpersAndStatus ??= new DumpersAndStatusSection();
|
||||
info.DumpersAndStatus.Dumpers = [options.RedumpUsername!];
|
||||
info.PartiallyMatchedIDs = [];
|
||||
|
||||
// Login to Redump
|
||||
var wc = new RedumpClient();
|
||||
bool? loggedIn = await wc.Login(options.RedumpUsername ?? string.Empty, options.RedumpPassword ?? string.Empty);
|
||||
if (loggedIn == null)
|
||||
{
|
||||
resultProgress?.Report(ResultEventArgs.Failure("There was an unknown error connecting to Redump"));
|
||||
return false;
|
||||
}
|
||||
else if (loggedIn == false)
|
||||
{
|
||||
// Don't log the as a failure or error
|
||||
return false;
|
||||
}
|
||||
|
||||
// Setup the checks
|
||||
bool allFound = true;
|
||||
List<int[]> foundIdSets = [];
|
||||
|
||||
// Loop through all of the hashdata to find matching IDs
|
||||
resultProgress?.Report(ResultEventArgs.Success("Finding disc matches on Redump..."));
|
||||
var splitData = info.TracksAndWriteOffsets?.ClrMameProData?.TrimEnd('\n')?.Split('\n');
|
||||
int trackCount = splitData?.Length ?? 0;
|
||||
foreach (string hashData in splitData ?? [])
|
||||
{
|
||||
// Catch any errant blank lines
|
||||
if (string.IsNullOrEmpty(hashData))
|
||||
{
|
||||
trackCount--;
|
||||
resultProgress?.Report(ResultEventArgs.Success("Blank line found, skipping!"));
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the line ends in a known extra track names, skip them for checking
|
||||
if (hashData.Contains("(Track 0).bin")
|
||||
|| hashData.Contains("(Track 0.2).bin")
|
||||
|| hashData.Contains("(Track 00).bin")
|
||||
|| hashData.Contains("(Track 00.2).bin")
|
||||
|| hashData.Contains("(Track A).bin")
|
||||
|| hashData.Contains("(Track A.2).bin")
|
||||
|| hashData.Contains("(Track AA).bin")
|
||||
|| hashData.Contains("(Track AA.2).bin"))
|
||||
{
|
||||
trackCount--;
|
||||
resultProgress?.Report(ResultEventArgs.Success("Extra track found, skipping!"));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get the SHA-1 hash
|
||||
if (!ProcessingTool.GetISOHashValues(hashData, out _, out _, out _, out string? sha1))
|
||||
{
|
||||
resultProgress?.Report(ResultEventArgs.Failure($"Line could not be parsed: {hashData}"));
|
||||
continue;
|
||||
}
|
||||
|
||||
var foundIds = await Validator.ValidateSingleTrack(wc, info, sha1);
|
||||
if (foundIds != null && foundIds.Count == 1)
|
||||
resultProgress?.Report(ResultEventArgs.Success($"Single match found for {sha1}"));
|
||||
else if (foundIds != null && foundIds.Count != 1)
|
||||
resultProgress?.Report(ResultEventArgs.Success($"Multiple matches found for {sha1}"));
|
||||
else
|
||||
resultProgress?.Report(ResultEventArgs.Failure($"No matches found for {sha1}"));
|
||||
|
||||
// Add the found IDs to the map
|
||||
foundIdSets.Add(foundIds?.ToArray() ?? []);
|
||||
|
||||
// Ensure that all tracks are found
|
||||
allFound &= (foundIds != null && foundIds.Count == 1);
|
||||
}
|
||||
|
||||
// If all tracks were found, check if there are any fully-matched IDs
|
||||
List<int>? fullyMatchedIDs = null;
|
||||
if (allFound)
|
||||
{
|
||||
fullyMatchedIDs = null;
|
||||
foreach (var set in foundIdSets)
|
||||
{
|
||||
// First track is always all IDs
|
||||
if (fullyMatchedIDs == null)
|
||||
{
|
||||
fullyMatchedIDs = [.. set];
|
||||
continue;
|
||||
}
|
||||
|
||||
// Try to intersect with all known IDs
|
||||
fullyMatchedIDs = fullyMatchedIDs.Intersect(set).ToList();
|
||||
if (!fullyMatchedIDs.Any())
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If we don't have any matches but we have a universal hash
|
||||
if (!info.PartiallyMatchedIDs.Any() && info.CommonDiscInfo?.CommentsSpecialFields?.ContainsKey(SiteCode.UniversalHash) == true)
|
||||
{
|
||||
string sha1 = info.CommonDiscInfo.CommentsSpecialFields[SiteCode.UniversalHash];
|
||||
var foundIds = await Validator.ValidateUniversalHash(wc, info);
|
||||
if (foundIds != null && foundIds.Count == 1)
|
||||
resultProgress?.Report(ResultEventArgs.Success($"Single match found for universal hash {sha1}"));
|
||||
else if (foundIds != null && foundIds.Count != 1)
|
||||
resultProgress?.Report(ResultEventArgs.Success($"Multiple matches found for universal hash {sha1}"));
|
||||
else
|
||||
resultProgress?.Report(ResultEventArgs.Failure($"No matches found for universal hash {sha1}"));
|
||||
|
||||
// Ensure that the hash is found
|
||||
allFound = (foundIds != null && foundIds.Count == 1);
|
||||
|
||||
// If we found a match, then the disc is a match
|
||||
if ((foundIds != null && foundIds.Count == 1) && foundIds != null)
|
||||
fullyMatchedIDs = foundIds;
|
||||
else
|
||||
fullyMatchedIDs = [];
|
||||
}
|
||||
|
||||
// Make sure we only have unique IDs
|
||||
info.PartiallyMatchedIDs = [.. info.PartiallyMatchedIDs.Distinct().OrderBy(id => id)];
|
||||
|
||||
resultProgress?.Report(ResultEventArgs.Success("Match finding complete! " + (fullyMatchedIDs != null && fullyMatchedIDs.Count > 0
|
||||
? "Fully Matched IDs: " + string.Join(",", fullyMatchedIDs.Select(i => i.ToString()).ToArray())
|
||||
: "No matches found")));
|
||||
|
||||
// Exit early if one failed or there are no matched IDs
|
||||
if (!allFound || fullyMatchedIDs == null || fullyMatchedIDs.Count == 0)
|
||||
return false;
|
||||
|
||||
// Find the first matched ID where the track count matches, we can grab a bunch of info from it
|
||||
int totalMatchedIDsCount = fullyMatchedIDs.Count;
|
||||
for (int i = 0; i < totalMatchedIDsCount; i++)
|
||||
{
|
||||
// Skip if the track count doesn't match
|
||||
#if NET40
|
||||
var validateTask = Validator.ValidateTrackCount(wc, fullyMatchedIDs[i], trackCount);
|
||||
validateTask.Wait();
|
||||
if (!validateTask.Result)
|
||||
#else
|
||||
if (!await Validator.ValidateTrackCount(wc, fullyMatchedIDs[i], trackCount))
|
||||
#endif
|
||||
continue;
|
||||
|
||||
// Fill in the fields from the existing ID
|
||||
resultProgress?.Report(ResultEventArgs.Success($"Filling fields from existing ID {fullyMatchedIDs[i]}..."));
|
||||
#if NET40
|
||||
var fillTask = Task.Factory.StartNew(() => Builder.FillFromId(wc, info, fullyMatchedIDs[i], options.PullAllInformation));
|
||||
fillTask.Wait();
|
||||
_ = fillTask.Result;
|
||||
#else
|
||||
_ = await Builder.FillFromId(wc, info, fullyMatchedIDs[i], options.PullAllInformation);
|
||||
#endif
|
||||
resultProgress?.Report(ResultEventArgs.Success("Information filling complete!"));
|
||||
|
||||
// Set the fully matched ID to the current
|
||||
info.FullyMatchedID = fullyMatchedIDs[i];
|
||||
break;
|
||||
}
|
||||
|
||||
// Clear out fully matched IDs from the partial list
|
||||
if (info.FullyMatchedID.HasValue)
|
||||
{
|
||||
if (info.PartiallyMatchedIDs.Count == 1)
|
||||
info.PartiallyMatchedIDs = null;
|
||||
else
|
||||
info.PartiallyMatchedIDs.Remove(info.FullyMatchedID.Value);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Helper Functions
|
||||
|
||||
/// <summary>
|
||||
/// Creates a default SubmissionInfo object based on the current system and media type
|
||||
/// </summary>
|
||||
private static SubmissionInfo CreateDefaultSubmissionInfo(BaseProcessor processor, RedumpSystem? system, MediaType? mediaType, bool addPlaceholders)
|
||||
{
|
||||
// Create the template object
|
||||
var info = new SubmissionInfo()
|
||||
{
|
||||
CommonDiscInfo = new CommonDiscInfoSection()
|
||||
{
|
||||
System = system,
|
||||
Media = mediaType.ToDiscType(),
|
||||
Title = addPlaceholders ? RequiredValue : string.Empty,
|
||||
ForeignTitleNonLatin = addPlaceholders ? OptionalValue : string.Empty,
|
||||
DiscNumberLetter = addPlaceholders ? OptionalValue : string.Empty,
|
||||
DiscTitle = addPlaceholders ? OptionalValue : string.Empty,
|
||||
Category = null,
|
||||
Region = null,
|
||||
Languages = null,
|
||||
Serial = addPlaceholders ? RequiredIfExistsValue : string.Empty,
|
||||
Barcode = addPlaceholders ? OptionalValue : string.Empty,
|
||||
Contents = string.Empty,
|
||||
},
|
||||
VersionAndEditions = new VersionAndEditionsSection()
|
||||
{
|
||||
OtherEditions = addPlaceholders ? "(VERIFY THIS) Original" : string.Empty,
|
||||
},
|
||||
DumpingInfo = new DumpingInfoSection()
|
||||
{
|
||||
FrontendVersion = FrontendTool.GetCurrentVersion(),
|
||||
DumpingProgram = GetDumpingProgramFromProcessor(processor),
|
||||
},
|
||||
};
|
||||
|
||||
// Ensure that required sections exist
|
||||
info = Builder.EnsureAllSections(info);
|
||||
return info;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the dumping program name from the processor
|
||||
/// </summary>
|
||||
/// <param name="processor"></param>
|
||||
/// <returns></returns>
|
||||
private static string? GetDumpingProgramFromProcessor(BaseProcessor processor)
|
||||
{
|
||||
// Map to the internal program
|
||||
InternalProgram? internalProgram = processor switch
|
||||
{
|
||||
Processors.Aaru => InternalProgram.Aaru,
|
||||
CleanRip => InternalProgram.CleanRip,
|
||||
DiscImageCreator => InternalProgram.DiscImageCreator,
|
||||
PS3CFW => InternalProgram.PS3CFW,
|
||||
Redumper => InternalProgram.Redumper,
|
||||
UmdImageCreator => InternalProgram.UmdImageCreator,
|
||||
XboxBackupCreator => InternalProgram.XboxBackupCreator,
|
||||
_ => null,
|
||||
};
|
||||
|
||||
// Use the internal program to map to the name
|
||||
return internalProgram.LongName();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formats a list of volume labels and their corresponding filesystems
|
||||
/// </summary>
|
||||
/// <param name="labels">Dictionary of volume labels and their filesystems</param>
|
||||
/// <returns>Formatted string of volume labels and their filesystems</returns>
|
||||
private static string? FormatVolumeLabels(string? driveLabel, Dictionary<string, List<string>>? labels)
|
||||
{
|
||||
// Must have at least one label to format
|
||||
if (driveLabel == null && (labels == null || labels.Count == 0))
|
||||
return null;
|
||||
|
||||
// If no labels given, use drive label
|
||||
if (labels == null || labels.Count == 0)
|
||||
{
|
||||
// Ignore common volume labels
|
||||
if (FrontendTool.GetRedumpSystemFromVolumeLabel(driveLabel) != null)
|
||||
return null;
|
||||
|
||||
return driveLabel;
|
||||
}
|
||||
|
||||
// If only one label, don't mention fs
|
||||
string firstLabel = labels.First().Key;
|
||||
if (labels.Count == 1 && (firstLabel == driveLabel || driveLabel == null))
|
||||
{
|
||||
// Ignore common volume labels
|
||||
if (FrontendTool.GetRedumpSystemFromVolumeLabel(firstLabel) != null)
|
||||
return null;
|
||||
|
||||
return firstLabel;
|
||||
}
|
||||
|
||||
// Otherwise, state filesystem for each label
|
||||
List<string> volLabels = [];
|
||||
|
||||
// Begin formatted output with the label from Windows, if it is unique and not a common volume label
|
||||
if (driveLabel != null && !labels.TryGetValue(driveLabel, out List<string>? value) && FrontendTool.GetRedumpSystemFromVolumeLabel(driveLabel) == null)
|
||||
volLabels.Add(driveLabel);
|
||||
|
||||
// Add remaining labels with their corresponding filesystems
|
||||
foreach (KeyValuePair<string, List<string>> label in labels)
|
||||
{
|
||||
// Ignore common volume labels
|
||||
if (FrontendTool.GetRedumpSystemFromVolumeLabel(label.Key) == null)
|
||||
volLabels.Add($"{label.Key} ({string.Join(", ", [.. label.Value])})");
|
||||
}
|
||||
|
||||
// Ensure that no labels are empty
|
||||
volLabels = volLabels.Where(l => !string.IsNullOrEmpty(l?.Trim())).ToList();
|
||||
|
||||
// Print each label separated by a comma and a space
|
||||
if (volLabels.Count == 0)
|
||||
return null;
|
||||
|
||||
return string.Join(", ", [.. volLabels]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes default data based on media type
|
||||
/// </summary>
|
||||
private static bool ProcessMediaType(SubmissionInfo info, MediaType? mediaType, bool addPlaceholders)
|
||||
{
|
||||
switch (mediaType)
|
||||
{
|
||||
case MediaType.CDROM:
|
||||
case MediaType.GDROM:
|
||||
info.CommonDiscInfo!.Layer0MasteringRing = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer0MasteringSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer0ToolstampMasteringCode = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer0MouldSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer1MouldSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer0AdditionalMould = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
break;
|
||||
|
||||
case MediaType.DVD:
|
||||
case MediaType.HDDVD:
|
||||
case MediaType.BluRay:
|
||||
|
||||
// If we have a single-layer disc
|
||||
if (info.SizeAndChecksums!.Layerbreak == default)
|
||||
{
|
||||
info.CommonDiscInfo!.Layer0MasteringRing = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer0MasteringSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer0ToolstampMasteringCode = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer0MouldSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer1MouldSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer0AdditionalMould = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
}
|
||||
// If we have a dual-layer disc
|
||||
else if (info.SizeAndChecksums!.Layerbreak2 == default)
|
||||
{
|
||||
info.CommonDiscInfo!.Layer0MasteringRing = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer0MasteringSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer0ToolstampMasteringCode = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer0MouldSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer0AdditionalMould = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
|
||||
info.CommonDiscInfo.Layer1MasteringRing = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer1MasteringSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer1ToolstampMasteringCode = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer1MouldSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
}
|
||||
// If we have a triple-layer disc
|
||||
else if (info.SizeAndChecksums!.Layerbreak3 == default)
|
||||
{
|
||||
info.CommonDiscInfo!.Layer0MasteringRing = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer0MasteringSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer0ToolstampMasteringCode = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer0MouldSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer0AdditionalMould = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
|
||||
info.CommonDiscInfo.Layer1MasteringRing = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer1MasteringSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer1ToolstampMasteringCode = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer1MouldSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
|
||||
info.CommonDiscInfo.Layer2MasteringRing = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer2MasteringSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer2ToolstampMasteringCode = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
}
|
||||
// If we have a quad-layer disc
|
||||
else
|
||||
{
|
||||
info.CommonDiscInfo!.Layer0MasteringRing = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer0MasteringSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer0ToolstampMasteringCode = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer0MouldSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer0AdditionalMould = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
|
||||
info.CommonDiscInfo.Layer1MasteringRing = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer1MasteringSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer1ToolstampMasteringCode = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer1MouldSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
|
||||
info.CommonDiscInfo.Layer2MasteringRing = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer2MasteringSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer2ToolstampMasteringCode = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
|
||||
info.CommonDiscInfo.Layer3MasteringRing = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer3MasteringSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer3ToolstampMasteringCode = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case MediaType.NintendoGameCubeGameDisc:
|
||||
info.CommonDiscInfo!.Layer0MasteringRing = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer0MasteringSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer0ToolstampMasteringCode = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer0MouldSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer1MouldSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer0AdditionalMould = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.Extras!.BCA ??= addPlaceholders ? RequiredValue : string.Empty;
|
||||
break;
|
||||
|
||||
case MediaType.NintendoWiiOpticalDisc:
|
||||
|
||||
// If we have a single-layer disc
|
||||
if (info.SizeAndChecksums!.Layerbreak == default)
|
||||
{
|
||||
info.CommonDiscInfo!.Layer0MasteringRing = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer0MasteringSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer0ToolstampMasteringCode = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer0MouldSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer1MouldSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer0AdditionalMould = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
}
|
||||
// If we have a dual-layer disc
|
||||
else
|
||||
{
|
||||
info.CommonDiscInfo!.Layer0MasteringRing = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer0MasteringSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer0ToolstampMasteringCode = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer0MouldSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer0AdditionalMould = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
|
||||
info.CommonDiscInfo.Layer1MasteringRing = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer1MasteringSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer1ToolstampMasteringCode = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer1MouldSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
}
|
||||
|
||||
info.Extras!.DiscKey = addPlaceholders ? RequiredValue : string.Empty;
|
||||
info.Extras.BCA ??= addPlaceholders ? RequiredValue : string.Empty;
|
||||
|
||||
break;
|
||||
|
||||
case MediaType.UMD:
|
||||
// Both single- and dual-layer discs have two "layers" for the ring
|
||||
info.CommonDiscInfo!.Layer0MasteringRing = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer0MasteringSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer0ToolstampMasteringCode = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer0MouldSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
|
||||
info.CommonDiscInfo.Layer1MasteringRing = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer1MasteringSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer1ToolstampMasteringCode = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
|
||||
info.SizeAndChecksums!.CRC32 ??= (addPlaceholders ? RequiredValue + " [Not automatically generated for UMD]" : string.Empty);
|
||||
info.SizeAndChecksums.MD5 ??= (addPlaceholders ? RequiredValue + " [Not automatically generated for UMD]" : string.Empty);
|
||||
info.SizeAndChecksums.SHA1 ??= (addPlaceholders ? RequiredValue + " [Not automatically generated for UMD]" : string.Empty);
|
||||
info.TracksAndWriteOffsets!.ClrMameProData = null;
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes default data based on system type
|
||||
/// </summary>
|
||||
private static bool ProcessSystem(SubmissionInfo info, RedumpSystem? system, Drive? drive, bool addPlaceholders, bool isDiscImageCreator, string basePath)
|
||||
{
|
||||
// Extract info based specifically on RedumpSystem
|
||||
switch (system)
|
||||
{
|
||||
case RedumpSystem.AcornArchimedes:
|
||||
info.CommonDiscInfo!.Region ??= Region.UnitedKingdom;
|
||||
break;
|
||||
|
||||
case RedumpSystem.AudioCD:
|
||||
case RedumpSystem.DVDAudio:
|
||||
case RedumpSystem.EnhancedCD:
|
||||
case RedumpSystem.SuperAudioCD:
|
||||
info.CommonDiscInfo!.Category ??= DiscCategory.Audio;
|
||||
break;
|
||||
|
||||
case RedumpSystem.BandaiPlaydiaQuickInteractiveSystem:
|
||||
info.CommonDiscInfo!.EXEDateBuildDate ??= addPlaceholders ? RequiredValue : string.Empty;
|
||||
info.CommonDiscInfo.Region ??= info.CommonDiscInfo.Region ?? Region.Japan;
|
||||
break;
|
||||
|
||||
case RedumpSystem.BDVideo:
|
||||
info.CommonDiscInfo!.Category ??= DiscCategory.Video;
|
||||
bool bee = PhysicalTool.GetBusEncryptionEnabled(drive);
|
||||
if (bee && string.IsNullOrEmpty(info.CopyProtection!.Protection))
|
||||
info.CopyProtection.Protection = "Bus encryption enabled flag set";
|
||||
else if (bee)
|
||||
info.CopyProtection!.Protection += "\nBus encryption enabled flag set";
|
||||
else
|
||||
info.CopyProtection!.Protection ??= addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
|
||||
break;
|
||||
|
||||
case RedumpSystem.DVDVideo:
|
||||
case RedumpSystem.HDDVDVideo:
|
||||
info.CommonDiscInfo!.Category ??= DiscCategory.Video;
|
||||
info.CopyProtection!.Protection ??= addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
break;
|
||||
|
||||
case RedumpSystem.CommodoreAmigaCD:
|
||||
info.CommonDiscInfo!.EXEDateBuildDate ??= addPlaceholders ? RequiredValue : string.Empty;
|
||||
break;
|
||||
|
||||
case RedumpSystem.CommodoreAmigaCD32:
|
||||
info.CommonDiscInfo!.EXEDateBuildDate ??= addPlaceholders ? RequiredValue : string.Empty;
|
||||
info.CommonDiscInfo.Region ??= Region.Europe;
|
||||
break;
|
||||
|
||||
case RedumpSystem.CommodoreAmigaCDTV:
|
||||
info.CommonDiscInfo!.EXEDateBuildDate ??= addPlaceholders ? RequiredValue : string.Empty;
|
||||
info.CommonDiscInfo.Region ??= Region.Europe;
|
||||
break;
|
||||
|
||||
case RedumpSystem.FujitsuFMTownsseries:
|
||||
info.CommonDiscInfo!.EXEDateBuildDate ??= addPlaceholders ? RequiredValue : string.Empty;
|
||||
info.CommonDiscInfo.Region ??= Region.Japan;
|
||||
break;
|
||||
|
||||
case RedumpSystem.FujitsuFMTownsMarty:
|
||||
info.CommonDiscInfo!.Region ??= Region.Japan;
|
||||
break;
|
||||
|
||||
case RedumpSystem.HasbroVideoNow:
|
||||
case RedumpSystem.HasbroVideoNowColor:
|
||||
case RedumpSystem.HasbroVideoNowJr:
|
||||
case RedumpSystem.VideoCD:
|
||||
info.CommonDiscInfo!.Category ??= DiscCategory.Video;
|
||||
break;
|
||||
|
||||
case RedumpSystem.HasbroVideoNowXP:
|
||||
case RedumpSystem.PhotoCD:
|
||||
case RedumpSystem.SonyElectronicBook:
|
||||
info.CommonDiscInfo!.Category ??= DiscCategory.Multimedia;
|
||||
break;
|
||||
|
||||
case RedumpSystem.IncredibleTechnologiesEagle:
|
||||
info.CommonDiscInfo!.EXEDateBuildDate ??= addPlaceholders ? RequiredValue : string.Empty;
|
||||
break;
|
||||
|
||||
case RedumpSystem.KonamieAmusement:
|
||||
info.CommonDiscInfo!.EXEDateBuildDate ??= addPlaceholders ? RequiredValue : string.Empty;
|
||||
break;
|
||||
|
||||
case RedumpSystem.KonamiFireBeat:
|
||||
info.CommonDiscInfo!.EXEDateBuildDate ??= addPlaceholders ? RequiredValue : string.Empty;
|
||||
break;
|
||||
|
||||
case RedumpSystem.KonamiPython2:
|
||||
string? kp2Exe = PhysicalTool.GetPlayStationExecutableName(drive);
|
||||
|
||||
// TODO: Remove this hack when DIC supports build date output
|
||||
if (isDiscImageCreator)
|
||||
info.CommonDiscInfo!.EXEDateBuildDate = DiscImageCreator.GetPlayStationEXEDate($"{basePath}_volDesc.txt", kp2Exe);
|
||||
|
||||
SetCommentFieldIfNotExists(info, SiteCode.InternalSerialName, drive, PhysicalTool.GetPlayStationSerial);
|
||||
info.CommonDiscInfo!.EXEDateBuildDate ??= PhysicalTool.GetFileDate(drive, kp2Exe, fixTwoDigitYear: true);
|
||||
|
||||
if (CommentFieldExists(info, SiteCode.InternalSerialName, out kp2Exe))
|
||||
info.CommonDiscInfo!.Region = ProcessingTool.GetPlayStationRegion(kp2Exe);
|
||||
|
||||
SetVersionIfNotExists(info, drive, PhysicalTool.GetPlayStation2Version);
|
||||
break;
|
||||
|
||||
case RedumpSystem.KonamiSystemGV:
|
||||
info.CommonDiscInfo!.EXEDateBuildDate ??= addPlaceholders ? RequiredValue : string.Empty;
|
||||
break;
|
||||
|
||||
case RedumpSystem.KonamiSystem573:
|
||||
info.CommonDiscInfo!.EXEDateBuildDate ??= addPlaceholders ? RequiredValue : string.Empty;
|
||||
break;
|
||||
|
||||
case RedumpSystem.KonamiTwinkle:
|
||||
info.CommonDiscInfo!.EXEDateBuildDate ??= addPlaceholders ? RequiredValue : string.Empty;
|
||||
break;
|
||||
|
||||
case RedumpSystem.MattelHyperScan:
|
||||
info.CommonDiscInfo!.EXEDateBuildDate ??= addPlaceholders ? RequiredValue : string.Empty;
|
||||
break;
|
||||
|
||||
case RedumpSystem.MicrosoftXboxOne:
|
||||
info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.Filename] = PhysicalTool.GetXboxFilenames(drive) ?? string.Empty;
|
||||
break;
|
||||
|
||||
case RedumpSystem.MicrosoftXboxSeriesXS:
|
||||
info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.Filename] = PhysicalTool.GetXboxFilenames(drive) ?? string.Empty;
|
||||
break;
|
||||
|
||||
case RedumpSystem.NamcoSegaNintendoTriforce:
|
||||
info.CommonDiscInfo!.EXEDateBuildDate ??= addPlaceholders ? RequiredValue : string.Empty;
|
||||
break;
|
||||
|
||||
case RedumpSystem.NavisoftNaviken21:
|
||||
info.CommonDiscInfo!.EXEDateBuildDate = addPlaceholders ? RequiredValue : string.Empty;
|
||||
info.CommonDiscInfo.Region ??= Region.Japan;
|
||||
break;
|
||||
|
||||
case RedumpSystem.NECPC88series:
|
||||
info.CommonDiscInfo!.Region ??= Region.Japan;
|
||||
break;
|
||||
|
||||
case RedumpSystem.NECPC98series:
|
||||
info.CommonDiscInfo!.EXEDateBuildDate = addPlaceholders ? RequiredValue : string.Empty;
|
||||
info.CommonDiscInfo!.Region ??= Region.Japan;
|
||||
break;
|
||||
|
||||
case RedumpSystem.NECPCFXPCFXGA:
|
||||
info.CommonDiscInfo!.Region ??= Region.Japan;
|
||||
break;
|
||||
|
||||
case RedumpSystem.SegaChihiro:
|
||||
info.CommonDiscInfo!.EXEDateBuildDate ??= addPlaceholders ? RequiredValue : string.Empty;
|
||||
break;
|
||||
|
||||
case RedumpSystem.SegaDreamcast:
|
||||
info.CommonDiscInfo!.EXEDateBuildDate ??= addPlaceholders ? RequiredValue : string.Empty;
|
||||
break;
|
||||
|
||||
case RedumpSystem.SegaNaomi:
|
||||
info.CommonDiscInfo!.EXEDateBuildDate ??= addPlaceholders ? RequiredValue : string.Empty;
|
||||
break;
|
||||
|
||||
case RedumpSystem.SegaNaomi2:
|
||||
info.CommonDiscInfo!.EXEDateBuildDate ??= addPlaceholders ? RequiredValue : string.Empty;
|
||||
break;
|
||||
|
||||
case RedumpSystem.SegaTitanVideo:
|
||||
info.CommonDiscInfo!.EXEDateBuildDate ??= addPlaceholders ? RequiredValue : string.Empty;
|
||||
break;
|
||||
|
||||
case RedumpSystem.SharpX68000:
|
||||
info.CommonDiscInfo!.Region ??= Region.Japan;
|
||||
break;
|
||||
|
||||
case RedumpSystem.SNKNeoGeoCD:
|
||||
info.CommonDiscInfo!.EXEDateBuildDate ??= addPlaceholders ? RequiredValue : string.Empty;
|
||||
break;
|
||||
|
||||
case RedumpSystem.SonyPlayStation:
|
||||
string? ps1Exe = PhysicalTool.GetPlayStationExecutableName(drive);
|
||||
|
||||
// TODO: Remove this hack when DIC supports build date output
|
||||
if (isDiscImageCreator)
|
||||
info.CommonDiscInfo!.EXEDateBuildDate = DiscImageCreator.GetPlayStationEXEDate($"{basePath}_volDesc.txt", ps1Exe, psx: true);
|
||||
|
||||
SetCommentFieldIfNotExists(info, SiteCode.InternalSerialName, drive, PhysicalTool.GetPlayStationSerial);
|
||||
info.CommonDiscInfo!.EXEDateBuildDate ??= PhysicalTool.GetFileDate(drive, ps1Exe, fixTwoDigitYear: true);
|
||||
|
||||
if (CommentFieldExists(info, SiteCode.InternalSerialName, out ps1Exe))
|
||||
info.CommonDiscInfo!.Region = ProcessingTool.GetPlayStationRegion(ps1Exe);
|
||||
|
||||
break;
|
||||
|
||||
case RedumpSystem.SonyPlayStation2:
|
||||
info.CommonDiscInfo!.LanguageSelection ??= [];
|
||||
string? ps2Exe = PhysicalTool.GetPlayStationExecutableName(drive);
|
||||
|
||||
// TODO: Remove this hack when DIC supports build date output
|
||||
if (isDiscImageCreator)
|
||||
info.CommonDiscInfo!.EXEDateBuildDate = DiscImageCreator.GetPlayStationEXEDate($"{basePath}_volDesc.txt", ps2Exe);
|
||||
|
||||
SetCommentFieldIfNotExists(info, SiteCode.InternalSerialName, drive, PhysicalTool.GetPlayStationSerial);
|
||||
info.CommonDiscInfo!.EXEDateBuildDate ??= PhysicalTool.GetFileDate(drive, ps2Exe, fixTwoDigitYear: true);
|
||||
|
||||
if (CommentFieldExists(info, SiteCode.InternalSerialName, out ps2Exe))
|
||||
info.CommonDiscInfo.Region = ProcessingTool.GetPlayStationRegion(ps2Exe);
|
||||
|
||||
SetVersionIfNotExists(info, drive, PhysicalTool.GetPlayStation2Version);
|
||||
break;
|
||||
|
||||
case RedumpSystem.SonyPlayStation3:
|
||||
info.Extras!.DiscKey ??= addPlaceholders ? RequiredValue : string.Empty;
|
||||
info.Extras.DiscID ??= addPlaceholders ? RequiredValue : string.Empty;
|
||||
|
||||
SetCommentFieldIfNotExists(info, SiteCode.InternalSerialName, drive, PhysicalTool.GetPlayStation3Serial);
|
||||
SetVersionIfNotExists(info, drive, PhysicalTool.GetPlayStation3Version);
|
||||
SetCommentFieldIfNotExists(info, SiteCode.Patches, drive, FormatPlayStation3FirmwareVersion);
|
||||
break;
|
||||
|
||||
case RedumpSystem.SonyPlayStation4:
|
||||
SetCommentFieldIfNotExists(info, SiteCode.InternalSerialName, drive, PhysicalTool.GetPlayStation4Serial);
|
||||
SetVersionIfNotExists(info, drive, PhysicalTool.GetPlayStation4Version);
|
||||
break;
|
||||
|
||||
case RedumpSystem.SonyPlayStation5:
|
||||
SetCommentFieldIfNotExists(info, SiteCode.InternalSerialName, drive, PhysicalTool.GetPlayStation5Serial);
|
||||
SetVersionIfNotExists(info, drive, PhysicalTool.GetPlayStation5Version);
|
||||
break;
|
||||
|
||||
case RedumpSystem.TomyKissSite:
|
||||
info.CommonDiscInfo!.Category ??= DiscCategory.Video;
|
||||
info.CommonDiscInfo!.Region ??= Region.Japan;
|
||||
break;
|
||||
|
||||
case RedumpSystem.ZAPiTGamesGameWaveFamilyEntertainmentSystem:
|
||||
info.CopyProtection!.Protection ??= addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a preformatted string for the PS3 firmware version, if possible
|
||||
/// </summary>
|
||||
private static string? FormatPlayStation3FirmwareVersion(Drive? drive)
|
||||
{
|
||||
string? firmwareVersion = PhysicalTool.GetPlayStation3FirmwareVersion(drive);
|
||||
if (string.IsNullOrEmpty(firmwareVersion))
|
||||
return string.Empty;
|
||||
|
||||
return $"PS3 Firmware {firmwareVersion}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determine if a comment field exists based on key
|
||||
/// </summary>
|
||||
private static bool CommentFieldExists(SubmissionInfo info, SiteCode key, out string? value)
|
||||
{
|
||||
// Ensure the comments fields exist
|
||||
if (info.CommonDiscInfo!.CommentsSpecialFields == null)
|
||||
info.CommonDiscInfo.CommentsSpecialFields = [];
|
||||
|
||||
// Check if the field exists
|
||||
if (!info.CommonDiscInfo.CommentsSpecialFields.TryGetValue(key, out value))
|
||||
return false;
|
||||
if (string.IsNullOrEmpty(value))
|
||||
return false;
|
||||
|
||||
// The value is valid
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set a comment field if it doesn't already have a value
|
||||
/// </summary>
|
||||
private static void SetCommentFieldIfNotExists(SubmissionInfo info, SiteCode key, Drive? drive, Func<Drive?, string?> valueFunc)
|
||||
{
|
||||
// If the field has a valid value, skip
|
||||
if (CommentFieldExists(info, key, out _))
|
||||
return;
|
||||
|
||||
// Set the value
|
||||
info.CommonDiscInfo!.CommentsSpecialFields![key] = valueFunc(drive) ?? string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the version if it doesn't already have a value
|
||||
/// </summary>
|
||||
private static void SetVersionIfNotExists(SubmissionInfo info, Drive? drive, Func<Drive?, string?> valueFunc)
|
||||
{
|
||||
// If the version already exists, skip
|
||||
if (!string.IsNullOrEmpty(info.VersionAndEditions!.Version))
|
||||
return;
|
||||
|
||||
// Set the version
|
||||
info.VersionAndEditions.Version = valueFunc(drive) ?? string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reformat a protection dictionary for submission info
|
||||
/// </summary>
|
||||
/// <param name="oldDict">ProtectionDictionary to format</param>
|
||||
/// <returns>Reformatted dictionary on success, empty on error</returns>
|
||||
private static Dictionary<string, List<string>?> ReformatProtectionDictionary(ProtectionDictionary? oldDict)
|
||||
{
|
||||
// Null or empty protections return empty
|
||||
if (oldDict == null || oldDict.Count == 0)
|
||||
return [];
|
||||
|
||||
// Reformat each set into a List
|
||||
var newDict = new Dictionary<string, List<string>?>();
|
||||
foreach (string key in oldDict.Keys)
|
||||
{
|
||||
newDict[key] = [.. oldDict[key]];
|
||||
}
|
||||
|
||||
return newDict;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
481
MPF.Frontend/ViewModels/CheckDumpViewModel.cs
Normal file
481
MPF.Frontend/ViewModels/CheckDumpViewModel.cs
Normal file
@@ -0,0 +1,481 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BinaryObjectScanner;
|
||||
using MPF.Frontend.ComboBoxItems;
|
||||
using MPF.Frontend.Tools;
|
||||
using SabreTools.RedumpLib.Data;
|
||||
|
||||
namespace MPF.Frontend.ViewModels
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
public class CheckDumpViewModel : INotifyPropertyChanged
|
||||
{
|
||||
#region Fields
|
||||
|
||||
/// <summary>
|
||||
/// Access to the current options
|
||||
/// </summary>
|
||||
public Frontend.Options Options
|
||||
{
|
||||
get => _options;
|
||||
}
|
||||
private readonly Frontend.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(ProcessUserInfoDelegate 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<ResultEventArgs>();
|
||||
var protectionProgress = new Progress<ProtectionProgress>();
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
1070
MPF.Frontend/ViewModels/CreateIRDViewModel.cs
Normal file
1070
MPF.Frontend/ViewModels/CreateIRDViewModel.cs
Normal file
File diff suppressed because it is too large
Load Diff
246
MPF.Frontend/ViewModels/DiscInformationViewModel.cs
Normal file
246
MPF.Frontend/ViewModels/DiscInformationViewModel.cs
Normal file
@@ -0,0 +1,246 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using MPF.Frontend.ComboBoxItems;
|
||||
using MPF.Frontend.Tools;
|
||||
using SabreTools.RedumpLib.Data;
|
||||
|
||||
namespace MPF.Frontend.ViewModels
|
||||
{
|
||||
public class DiscInformationViewModel
|
||||
{
|
||||
#region Fields
|
||||
|
||||
/// <summary>
|
||||
/// Application-level Options object
|
||||
/// </summary>
|
||||
public Options Options { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// SubmissionInfo object to fill and save
|
||||
/// </summary>
|
||||
public SubmissionInfo SubmissionInfo { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Lists
|
||||
|
||||
/// <summary>
|
||||
/// List of available disc categories
|
||||
/// </summary>
|
||||
public List<Element<DiscCategory>> Categories { get; private set; } = Element<DiscCategory>.GenerateElements().ToList();
|
||||
|
||||
/// <summary>
|
||||
/// List of available regions
|
||||
/// </summary>
|
||||
public List<Element<Region>> Regions { get; private set; } = Element<Region>.GenerateElements().ToList();
|
||||
|
||||
/// <summary>
|
||||
/// List of Redump-supported Regions
|
||||
/// </summary>
|
||||
private static readonly List<Region> RedumpRegions = new()
|
||||
{
|
||||
Region.Argentina,
|
||||
Region.Asia,
|
||||
Region.AsiaEurope,
|
||||
Region.AsiaUSA,
|
||||
Region.Australia,
|
||||
Region.AustraliaGermany,
|
||||
Region.AustraliaNewZealand,
|
||||
Region.Austria,
|
||||
Region.AustriaSwitzerland,
|
||||
Region.Belarus,
|
||||
Region.Belgium,
|
||||
Region.BelgiumNetherlands,
|
||||
Region.Brazil,
|
||||
Region.Bulgaria,
|
||||
Region.Canada,
|
||||
Region.China,
|
||||
Region.Croatia,
|
||||
Region.Czechia,
|
||||
Region.Denmark,
|
||||
Region.Estonia,
|
||||
Region.Europe,
|
||||
Region.EuropeAsia,
|
||||
Region.EuropeAustralia,
|
||||
Region.EuropeCanada,
|
||||
Region.EuropeGermany,
|
||||
Region.Export,
|
||||
Region.Finland,
|
||||
Region.France,
|
||||
Region.FranceSpain,
|
||||
Region.Germany,
|
||||
Region.GreaterChina,
|
||||
Region.Greece,
|
||||
Region.Hungary,
|
||||
Region.Iceland,
|
||||
Region.India,
|
||||
Region.Ireland,
|
||||
Region.Israel,
|
||||
Region.Italy,
|
||||
Region.Japan,
|
||||
Region.JapanAsia,
|
||||
Region.JapanEurope,
|
||||
Region.JapanKorea,
|
||||
Region.JapanUSA,
|
||||
Region.SouthKorea,
|
||||
Region.LatinAmerica,
|
||||
Region.Lithuania,
|
||||
Region.Netherlands,
|
||||
Region.NewZealand,
|
||||
Region.Norway,
|
||||
Region.Poland,
|
||||
Region.Portugal,
|
||||
Region.Romania,
|
||||
Region.RussianFederation,
|
||||
Region.Scandinavia,
|
||||
Region.Serbia,
|
||||
Region.Singapore,
|
||||
Region.Slovakia,
|
||||
Region.SouthAfrica,
|
||||
Region.Spain,
|
||||
Region.SpainPortugal,
|
||||
Region.Sweden,
|
||||
Region.Switzerland,
|
||||
Region.Taiwan,
|
||||
Region.Thailand,
|
||||
Region.Turkey,
|
||||
Region.UnitedArabEmirates,
|
||||
Region.UnitedKingdom,
|
||||
Region.UKAustralia,
|
||||
Region.Ukraine,
|
||||
Region.UnitedStatesOfAmerica,
|
||||
Region.USAAsia,
|
||||
Region.USAAustralia,
|
||||
Region.USABrazil,
|
||||
Region.USACanada,
|
||||
Region.USAEurope,
|
||||
Region.USAGermany,
|
||||
Region.USAJapan,
|
||||
Region.USAKorea,
|
||||
Region.World,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// List of available languages
|
||||
/// </summary>
|
||||
public List<Element<Language>> Languages { get; private set; } = Element<Language>.GenerateElements().ToList();
|
||||
|
||||
/// <summary>
|
||||
/// List of Redump-supported Languages
|
||||
/// </summary>
|
||||
private static readonly List<Language> RedumpLanguages = new()
|
||||
{
|
||||
Language.Afrikaans,
|
||||
Language.Albanian,
|
||||
Language.Arabic,
|
||||
Language.Armenian,
|
||||
Language.Basque,
|
||||
Language.Belarusian,
|
||||
Language.Bulgarian,
|
||||
Language.Catalan,
|
||||
Language.Chinese,
|
||||
Language.Croatian,
|
||||
Language.Czech,
|
||||
Language.Danish,
|
||||
Language.Dutch,
|
||||
Language.English,
|
||||
Language.Estonian,
|
||||
Language.Finnish,
|
||||
Language.French,
|
||||
Language.Gaelic,
|
||||
Language.German,
|
||||
Language.Greek,
|
||||
Language.Hebrew,
|
||||
Language.Hindi,
|
||||
Language.Hungarian,
|
||||
Language.Icelandic,
|
||||
Language.Indonesian,
|
||||
Language.Italian,
|
||||
Language.Japanese,
|
||||
Language.Korean,
|
||||
Language.Latin,
|
||||
Language.Latvian,
|
||||
Language.Lithuanian,
|
||||
Language.Macedonian,
|
||||
Language.Norwegian,
|
||||
Language.Polish,
|
||||
Language.Portuguese,
|
||||
Language.Panjabi,
|
||||
Language.Romanian,
|
||||
Language.Russian,
|
||||
Language.Serbian,
|
||||
Language.Slovak,
|
||||
Language.Slovenian,
|
||||
Language.Spanish,
|
||||
Language.Swedish,
|
||||
Language.Tamil,
|
||||
Language.Thai,
|
||||
Language.Turkish,
|
||||
Language.Ukrainian,
|
||||
Language.Vietnamese,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// List of available languages
|
||||
/// </summary>
|
||||
public List<Element<LanguageSelection>> LanguageSelections { get; private set; } = Element<LanguageSelection>.GenerateElements().ToList();
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
public DiscInformationViewModel(Options options, SubmissionInfo? submissionInfo)
|
||||
{
|
||||
Options = options;
|
||||
SubmissionInfo = submissionInfo?.Clone() as SubmissionInfo ?? new SubmissionInfo();
|
||||
}
|
||||
|
||||
#region Helpers
|
||||
|
||||
/// <summary>
|
||||
/// Load the current contents of the base SubmissionInfo to the UI
|
||||
/// </summary>
|
||||
/// TODO: Convert selected list item to binding
|
||||
public void Load()
|
||||
{
|
||||
if (SubmissionInfo.CommonDiscInfo?.Languages != null)
|
||||
Languages.ForEach(l => l.IsChecked = SubmissionInfo.CommonDiscInfo.Languages.Contains(l));
|
||||
if (SubmissionInfo.CommonDiscInfo?.LanguageSelection != null)
|
||||
LanguageSelections.ForEach(ls => ls.IsChecked = SubmissionInfo.CommonDiscInfo.LanguageSelection.Contains(ls));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save the current contents of the UI to the base SubmissionInfo
|
||||
/// </summary>
|
||||
/// TODO: Convert selected list item to binding
|
||||
public void Save()
|
||||
{
|
||||
if (SubmissionInfo.CommonDiscInfo == null)
|
||||
SubmissionInfo.CommonDiscInfo = new CommonDiscInfoSection();
|
||||
SubmissionInfo.CommonDiscInfo.Languages = Languages.Where(l => l.IsChecked).Select(l => l?.Value).ToArray();
|
||||
if (!SubmissionInfo.CommonDiscInfo.Languages.Any())
|
||||
SubmissionInfo.CommonDiscInfo.Languages = [null];
|
||||
SubmissionInfo.CommonDiscInfo.LanguageSelection = LanguageSelections.Where(ls => ls.IsChecked).Select(ls => ls?.Value).ToArray();
|
||||
SubmissionInfo.CommonDiscInfo.Title = FrontendTool.NormalizeDiscTitle(SubmissionInfo.CommonDiscInfo.Title, SubmissionInfo.CommonDiscInfo.Languages);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Repopulate the list of Languages based on Redump support
|
||||
/// </summary>
|
||||
public void SetRedumpLanguages()
|
||||
{
|
||||
this.Languages = RedumpLanguages.Select(l => new Element<Language>(l)).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Repopulate the list of Regions based on Redump support
|
||||
/// </summary>
|
||||
public void SetRedumpRegions()
|
||||
{
|
||||
this.Regions = RedumpRegions.Select(r => new Element<Region>(r)).ToList();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
2322
MPF.Frontend/ViewModels/MainViewModel.cs
Normal file
2322
MPF.Frontend/ViewModels/MainViewModel.cs
Normal file
File diff suppressed because it is too large
Load Diff
160
MPF.Frontend/ViewModels/OptionsViewModel.cs
Normal file
160
MPF.Frontend/ViewModels/OptionsViewModel.cs
Normal file
@@ -0,0 +1,160 @@
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using MPF.Frontend.ComboBoxItems;
|
||||
using SabreTools.RedumpLib.Web;
|
||||
using RedumperReadMethod = MPF.ExecutionContexts.Redumper.ReadMethod;
|
||||
using RedumperSectorOrder = MPF.ExecutionContexts.Redumper.SectorOrder;
|
||||
|
||||
namespace MPF.Frontend.ViewModels
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
public class OptionsViewModel : INotifyPropertyChanged
|
||||
{
|
||||
#region Fields
|
||||
|
||||
/// <summary>
|
||||
/// Title for the window
|
||||
/// </summary>
|
||||
public string? Title
|
||||
{
|
||||
get => _title;
|
||||
set
|
||||
{
|
||||
_title = value;
|
||||
TriggerPropertyChanged(nameof(Title));
|
||||
}
|
||||
}
|
||||
private string? _title;
|
||||
|
||||
/// <summary>
|
||||
/// Current set of options
|
||||
/// </summary>
|
||||
public Options Options { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Flag for if settings were saved or not
|
||||
/// </summary>
|
||||
public bool SavedSettings { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Lists
|
||||
|
||||
/// <summary>
|
||||
/// List of available internal programs
|
||||
/// </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>
|
||||
public static List<RedumpSystemComboBoxItem> Systems => RedumpSystemComboBoxItem.GenerateElements().ToList();
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for pure view model
|
||||
/// </summary>
|
||||
public OptionsViewModel()
|
||||
{
|
||||
Options = new Options();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for in-code
|
||||
/// </summary>
|
||||
public OptionsViewModel(Options baseOptions)
|
||||
{
|
||||
Options = new Options(baseOptions);
|
||||
}
|
||||
|
||||
#region Population
|
||||
|
||||
/// <summary>
|
||||
/// Get a complete list of supported internal programs
|
||||
/// </summary>
|
||||
private static List<Element<InternalProgram>> PopulateInternalPrograms()
|
||||
{
|
||||
var internalPrograms = new List<InternalProgram> { InternalProgram.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
|
||||
|
||||
/// <summary>
|
||||
/// Get the human-readable result for a Redump login result
|
||||
/// </summary>
|
||||
public static string GetRedumpLoginResult(bool? success)
|
||||
{
|
||||
return success switch
|
||||
{
|
||||
true => "Redump username and password accepted!",
|
||||
false => "Redump username and password denied!",
|
||||
null => "An error occurred validating your credentials!",
|
||||
};
|
||||
}
|
||||
|
||||
/// <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
|
||||
|
||||
/// <summary>
|
||||
/// Trigger a property changed event
|
||||
/// </summary>
|
||||
private void TriggerPropertyChanged(string propertyName)
|
||||
{
|
||||
// If the property change event is initialized
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
1265
MPF.Processors/Aaru.cs
Normal file
1265
MPF.Processors/Aaru.cs
Normal file
File diff suppressed because it is too large
Load Diff
648
MPF.Processors/BaseProcessor.cs
Normal file
648
MPF.Processors/BaseProcessor.cs
Normal file
@@ -0,0 +1,648 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
#if NET452_OR_GREATER || NETCOREAPP
|
||||
using System.IO.Compression;
|
||||
#endif
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using SabreTools.RedumpLib.Data;
|
||||
|
||||
namespace MPF.Processors
|
||||
{
|
||||
public abstract class BaseProcessor
|
||||
{
|
||||
/// <summary>
|
||||
/// All found volume labels and their corresponding file systems
|
||||
/// </summary>
|
||||
public Dictionary<string, List<string>>? VolumeLabels;
|
||||
|
||||
#region Metadata
|
||||
|
||||
/// <summary>
|
||||
/// Currently represented system
|
||||
/// </summary>
|
||||
public RedumpSystem? System { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Currently represented media type
|
||||
/// </summary>
|
||||
public MediaType? Type { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Generate processor for a system and media type combination
|
||||
/// </summary>
|
||||
/// <param name="system">RedumpSystem value to use</param>
|
||||
/// <param name="type">MediaType value to use</param>
|
||||
public BaseProcessor(RedumpSystem? system, MediaType? type)
|
||||
{
|
||||
System = system;
|
||||
Type = type;
|
||||
}
|
||||
|
||||
#region Abstract Methods
|
||||
|
||||
/// <summary>
|
||||
/// Generate a SubmissionInfo for the output files
|
||||
/// </summary>
|
||||
/// <param name="submissionInfo">Base submission info to fill in specifics for</param>
|
||||
/// <param name="basePath">Base filename and path to use for checking</param>
|
||||
/// <param name="redumpCompat">Determines if outputs are processed according to Redump specifications</param>
|
||||
public abstract void GenerateSubmissionInfo(SubmissionInfo submissionInfo, string basePath, bool redumpCompat);
|
||||
|
||||
// <summary>
|
||||
/// Generate a list of all output files generated
|
||||
/// </summary>
|
||||
/// <param name="baseDirectory">Base filename and path to use for checking</param>
|
||||
/// <param name="baseFilename">Base filename and path to use for checking</param>
|
||||
/// <returns>List of all output files, empty otherwise</returns>
|
||||
internal abstract List<OutputFile> GetOutputFiles(string? baseDirectory, string baseFilename);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Output Files
|
||||
|
||||
/// <summary>
|
||||
/// Compress log files to save space
|
||||
/// </summary>
|
||||
/// <param name="outputDirectory">Output folder to write to</param>
|
||||
/// <param name="filenameSuffix">Output filename to use as the base path</param>
|
||||
/// <param name="outputFilename">Output filename to use as the base path</param>
|
||||
/// <param name="processor">Processor object representing how to process the outputs</param>
|
||||
/// <returns>True if the process succeeded, false otherwise</returns>
|
||||
public bool CompressLogFiles(string? outputDirectory, string? filenameSuffix, string outputFilename, out string status)
|
||||
{
|
||||
#if NET20 || NET35 || NET40
|
||||
status = "Log compression is not available for this framework version";
|
||||
return false;
|
||||
#else
|
||||
// Prepare the necessary paths
|
||||
outputFilename = Path.GetFileNameWithoutExtension(outputFilename);
|
||||
string combinedBase;
|
||||
if (string.IsNullOrEmpty(outputDirectory))
|
||||
combinedBase = outputFilename;
|
||||
else
|
||||
combinedBase = Path.Combine(outputDirectory, outputFilename);
|
||||
|
||||
// Generate the archive filename
|
||||
string archiveName = $"{combinedBase}_logs.zip";
|
||||
|
||||
// Get the lists of zippable files
|
||||
var zippableFiles = GetZippableFilePaths(combinedBase);
|
||||
var generatedFiles = GetGeneratedFilePaths(outputDirectory, filenameSuffix);
|
||||
|
||||
// Don't create an archive if there are no paths
|
||||
if (!zippableFiles.Any() && !generatedFiles.Any())
|
||||
{
|
||||
status = "No files to compress!";
|
||||
return true;
|
||||
}
|
||||
|
||||
// If the file already exists, we want to delete the old one
|
||||
try
|
||||
{
|
||||
if (File.Exists(archiveName))
|
||||
File.Delete(archiveName);
|
||||
}
|
||||
catch
|
||||
{
|
||||
status = "Could not delete old archive!";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Add the log files to the archive and delete the uncompressed file after
|
||||
ZipArchive? zf = null;
|
||||
try
|
||||
{
|
||||
zf = ZipFile.Open(archiveName, ZipArchiveMode.Create);
|
||||
|
||||
_ = AddToArchive(zf, zippableFiles, outputDirectory, true);
|
||||
_ = AddToArchive(zf, generatedFiles, outputDirectory, false);
|
||||
|
||||
status = "Compression complete!";
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
status = $"Compression could not complete: {ex}";
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
zf?.Dispose();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compress log files to save space
|
||||
/// </summary>
|
||||
/// <param name="outputDirectory">Output folder to write to</param>
|
||||
/// <param name="outputFilename">Output filename to use as the base path</param>
|
||||
/// <param name="processor">Processor object representing how to process the outputs</param>
|
||||
/// <returns>True if the process succeeded, false otherwise</returns>
|
||||
public bool DeleteUnnecessaryFiles(string? outputDirectory, string outputFilename, out string status)
|
||||
{
|
||||
// Prepare the necessary paths
|
||||
outputFilename = Path.GetFileNameWithoutExtension(outputFilename);
|
||||
string combinedBase;
|
||||
if (string.IsNullOrEmpty(outputDirectory))
|
||||
combinedBase = outputFilename;
|
||||
else
|
||||
combinedBase = Path.Combine(outputDirectory, outputFilename);
|
||||
|
||||
// Get the list of deleteable files from the parameters object
|
||||
var files = GetDeleteableFilePaths(combinedBase);
|
||||
|
||||
if (!files.Any())
|
||||
{
|
||||
status = "No files to delete!";
|
||||
return true;
|
||||
}
|
||||
|
||||
// Attempt to delete all of the files
|
||||
foreach (string file in files)
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Delete(file);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
status = "Deletion complete!";
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that all required output files have been created
|
||||
/// </summary>
|
||||
/// <param name="outputDirectory">Output folder to write to</param>
|
||||
/// <param name="outputFilename">Output filename to use as the base path</param>
|
||||
/// <param name="processor">Processor object representing how to process the outputs</param>
|
||||
/// <returns>A list representing missing files, empty if none</returns>
|
||||
public List<string> FoundAllFiles(string? outputDirectory, string outputFilename)
|
||||
{
|
||||
// Sanitize the output filename to strip off any potential extension
|
||||
outputFilename = Path.GetFileNameWithoutExtension(outputFilename);
|
||||
|
||||
// Then get the base path for all checking
|
||||
string basePath;
|
||||
if (string.IsNullOrEmpty(outputDirectory))
|
||||
basePath = outputFilename;
|
||||
else
|
||||
basePath = Path.Combine(outputDirectory, outputFilename);
|
||||
|
||||
// Finally, let the parameters say if all files exist
|
||||
return CheckRequiredFiles(basePath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate artifacts and return them as a dictionary
|
||||
/// </summary>
|
||||
/// <param name="basePath">Base filename and path to use for checking</param>
|
||||
/// <returns>Dictiionary of artifact keys to Base64-encoded values, if possible</param>
|
||||
public Dictionary<string, string> GenerateArtifacts(string basePath)
|
||||
{
|
||||
// Split the base path for matching
|
||||
string baseDirectory = Path.GetDirectoryName(basePath) ?? string.Empty;
|
||||
string baseFilename = Path.GetFileNameWithoutExtension(basePath);
|
||||
|
||||
// Get the list of output files
|
||||
var outputFiles = GetOutputFiles(baseDirectory, baseFilename);
|
||||
if (outputFiles.Count == 0)
|
||||
return [];
|
||||
|
||||
// Create the artifacts dictionary
|
||||
var artifacts = new Dictionary<string, string>();
|
||||
|
||||
// Only try to create artifacts for files that exist
|
||||
foreach (var outputFile in outputFiles)
|
||||
{
|
||||
// Skip non-artifact files
|
||||
if (!outputFile.IsArtifact || outputFile.ArtifactKey == null)
|
||||
continue;
|
||||
|
||||
// Skip non-existent files
|
||||
foreach (string filename in outputFile.Filenames)
|
||||
{
|
||||
string possibleFile = Path.Combine(baseDirectory, filename);
|
||||
if (!File.Exists(possibleFile))
|
||||
continue;
|
||||
|
||||
// Get binary artifacts as a byte array
|
||||
if (outputFile.IsBinaryArtifact)
|
||||
{
|
||||
byte[] data = File.ReadAllBytes(possibleFile);
|
||||
string str = Convert.ToBase64String(data);
|
||||
artifacts.Add(outputFile.ArtifactKey, str);
|
||||
}
|
||||
else
|
||||
{
|
||||
string? data = ProcessingTool.GetFullFile(possibleFile);
|
||||
string str = ProcessingTool.GetBase64(data) ?? string.Empty;
|
||||
artifacts.Add(outputFile.ArtifactKey, str);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return artifacts;
|
||||
}
|
||||
|
||||
#if NET452_OR_GREATER || NETCOREAPP
|
||||
/// <summary>
|
||||
/// Try to add a set of files to an existing archive
|
||||
/// </summary>
|
||||
/// <param name="archive">Archive to add the file to</param>
|
||||
/// <param name="files">Full path to a set of existing files</param>
|
||||
/// <param name="outputDirectory">Directory that the existing files live in</param>
|
||||
/// <param name="delete">Indicates if the files should be deleted after adding</param>
|
||||
/// <returns>True if all files were added successfully, false otherwise</returns>
|
||||
private static bool AddToArchive(ZipArchive archive, List<string> files, string? outputDirectory, bool delete)
|
||||
{
|
||||
// An empty list means success
|
||||
if (files.Count == 0)
|
||||
return true;
|
||||
|
||||
// Loop through and add all files
|
||||
bool allAdded = true;
|
||||
foreach (string file in files)
|
||||
{
|
||||
allAdded &= AddToArchive(archive, file, outputDirectory, delete);
|
||||
}
|
||||
|
||||
return allAdded;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to add a file to an existing archive
|
||||
/// </summary>
|
||||
/// <param name="archive">Archive to add the file to</param>
|
||||
/// <param name="file">Full path to an existing file</param>
|
||||
/// <param name="outputDirectory">Directory that the existing file lives in</param>
|
||||
/// <param name="delete">Indicates if the file should be deleted after adding</param>
|
||||
/// <returns>True if the file was added successfully, false otherwise</returns>
|
||||
private static bool AddToArchive(ZipArchive archive, string file, string? outputDirectory, bool delete)
|
||||
{
|
||||
// Check if the file exists
|
||||
if (!File.Exists(file))
|
||||
return false;
|
||||
|
||||
// Get the entry name from the file
|
||||
string entryName = file;
|
||||
if (!string.IsNullOrEmpty(outputDirectory))
|
||||
entryName = entryName.Substring(outputDirectory!.Length);
|
||||
|
||||
// Ensure the entry is formatted correctly
|
||||
entryName = entryName.TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
|
||||
|
||||
// Create and add the entry
|
||||
try
|
||||
{
|
||||
#if NETFRAMEWORK || NETCOREAPP3_1 || NET5_0
|
||||
archive.CreateEntryFromFile(file, entryName, CompressionLevel.Optimal);
|
||||
#else
|
||||
archive.CreateEntryFromFile(file, entryName, CompressionLevel.SmallestSize);
|
||||
#endif
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Try to delete the file if requested
|
||||
if (delete)
|
||||
{
|
||||
try { File.Delete(file); } catch { }
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Validate if all required output files exist
|
||||
/// </summary>
|
||||
/// <param name="basePath">Base filename and path to use for checking</param>
|
||||
/// <returns>A list representing missing files, empty if none</returns>
|
||||
private List<string> CheckRequiredFiles(string basePath)
|
||||
{
|
||||
// Split the base path for matching
|
||||
string baseDirectory = Path.GetDirectoryName(basePath) ?? string.Empty;
|
||||
string baseFilename = Path.GetFileNameWithoutExtension(basePath);
|
||||
|
||||
// Get the list of output files
|
||||
var outputFiles = GetOutputFiles(baseDirectory, baseFilename);
|
||||
if (outputFiles.Count == 0)
|
||||
return ["Media and system combination not supported"];
|
||||
|
||||
// Check for the log file
|
||||
bool logArchiveExists = false;
|
||||
#if NET452_OR_GREATER || NETCOREAPP
|
||||
ZipArchive? logArchive = null;
|
||||
#endif
|
||||
if (File.Exists($"{basePath}_logs.zip"))
|
||||
{
|
||||
logArchiveExists = true;
|
||||
#if NET452_OR_GREATER || NETCOREAPP
|
||||
try
|
||||
{
|
||||
// Try to open the archive
|
||||
logArchive = ZipFile.OpenRead($"{basePath}_logs.zip");
|
||||
}
|
||||
catch
|
||||
{
|
||||
logArchiveExists = false;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// Get a list of all missing required files
|
||||
var missingFiles = new List<string>();
|
||||
foreach (var outputFile in outputFiles)
|
||||
{
|
||||
// Only check required files
|
||||
if (!outputFile.IsRequired)
|
||||
continue;
|
||||
|
||||
// Use the built-in existence function
|
||||
if (outputFile.Exists(baseDirectory))
|
||||
continue;
|
||||
|
||||
// If the log archive doesn't exist
|
||||
if (!logArchiveExists)
|
||||
{
|
||||
missingFiles.Add(outputFile.Filenames[0]);
|
||||
continue;
|
||||
}
|
||||
|
||||
#if NET20 || NET35 || NET40
|
||||
// Assume the zipfile has the file in it
|
||||
continue;
|
||||
#else
|
||||
// Check the log archive
|
||||
if (outputFile.Exists(logArchive))
|
||||
continue;
|
||||
|
||||
// Add the file to the missing list
|
||||
missingFiles.Add(outputFile.Filenames[0]);
|
||||
#endif
|
||||
}
|
||||
|
||||
return missingFiles;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a list of all deleteable filenames
|
||||
/// </summary>
|
||||
/// <param name="basePath">Base filename and path to use for checking</param>
|
||||
/// <returns>List of all deleteable filenames, empty otherwise</returns>
|
||||
private List<string> GetDeleteableFilenames(string basePath)
|
||||
{
|
||||
// Split the base path for matching
|
||||
string baseDirectory = Path.GetDirectoryName(basePath) ?? string.Empty;
|
||||
string baseFilename = Path.GetFileNameWithoutExtension(basePath);
|
||||
|
||||
// Get the list of output files
|
||||
var outputFiles = GetOutputFiles(baseDirectory, baseFilename);
|
||||
if (outputFiles.Count == 0)
|
||||
return [];
|
||||
|
||||
// Filter down to deleteable files
|
||||
var deleteableFiles = outputFiles.Where(of => of.IsDeleteable);
|
||||
return deleteableFiles.SelectMany(of => of.Filenames).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a list of all deleteable file paths
|
||||
/// </summary>
|
||||
/// <param name="basePath">Base filename and path to use for checking</param>
|
||||
/// <returns>List of all deleteable file paths, empty otherwise</returns>
|
||||
private List<string> GetDeleteableFilePaths(string basePath)
|
||||
{
|
||||
// Split the base path for matching
|
||||
string baseDirectory = Path.GetDirectoryName(basePath) ?? string.Empty;
|
||||
|
||||
// Get the list of deleteable files
|
||||
var deleteableFilenames = GetDeleteableFilenames(basePath);
|
||||
if (deleteableFilenames.Count == 0)
|
||||
return [];
|
||||
|
||||
// Return only files that exist
|
||||
var deleteableFiles = new List<string>();
|
||||
foreach (var filename in deleteableFilenames)
|
||||
{
|
||||
// Skip non-existent files
|
||||
string possiblePath = Path.Combine(baseDirectory, filename);
|
||||
if (!File.Exists(possiblePath))
|
||||
continue;
|
||||
|
||||
deleteableFiles.Add(possiblePath);
|
||||
}
|
||||
|
||||
return deleteableFiles;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a list of all MPF-generated filenames
|
||||
/// </summary>
|
||||
/// <param name="filenameSuffix">Optional suffix to append to the filename</param>
|
||||
/// <returns>List of all MPF-generated filenames, empty otherwise</returns>
|
||||
private static List<string> GetGeneratedFilenames(string? filenameSuffix)
|
||||
{
|
||||
// Set the base file path names
|
||||
const string submissionInfoBase = "!submissionInfo";
|
||||
const string protectionInfoBase = "!protectionInfo";
|
||||
|
||||
// Ensure the filename suffix is formatted correctly
|
||||
filenameSuffix = string.IsNullOrEmpty(filenameSuffix) ? string.Empty : $"_{filenameSuffix}";
|
||||
|
||||
// Define the output filenames
|
||||
return [
|
||||
$"{protectionInfoBase}{filenameSuffix}.txt",
|
||||
$"{submissionInfoBase}{filenameSuffix}.json",
|
||||
$"{submissionInfoBase}{filenameSuffix}.json.gz",
|
||||
$"{submissionInfoBase}{filenameSuffix}.txt",
|
||||
];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a list of all MPF-specific log files generated
|
||||
/// </summary>
|
||||
/// <param name="basePath">Base directory to use for checking</param>
|
||||
/// <param name="filenameSuffix">Optional suffix to append to the filename</param>
|
||||
/// <returns>List of all log file paths, empty otherwise</returns>
|
||||
private static List<string> GetGeneratedFilePaths(string? baseDirectory, string? filenameSuffix)
|
||||
{
|
||||
// Get the list of generated files
|
||||
var generatedFilenames = GetGeneratedFilenames(filenameSuffix);
|
||||
if (generatedFilenames.Count == 0)
|
||||
return [];
|
||||
|
||||
// Ensure the output directory
|
||||
baseDirectory ??= string.Empty;
|
||||
|
||||
// Return only files that exist
|
||||
var generatedFiles = new List<string>();
|
||||
foreach (var filename in generatedFilenames)
|
||||
{
|
||||
// Skip non-existent files
|
||||
string possiblePath = Path.Combine(baseDirectory, filename);
|
||||
if (!File.Exists(possiblePath))
|
||||
continue;
|
||||
|
||||
generatedFiles.Add(possiblePath);
|
||||
}
|
||||
|
||||
return generatedFiles;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a list of all zippable filenames
|
||||
/// </summary>
|
||||
/// <param name="basePath">Base filename and path to use for checking</param>
|
||||
/// <returns>List of all zippable filenames, empty otherwise</returns>
|
||||
private List<string> GetZippableFilenames(string basePath)
|
||||
{
|
||||
// Split the base path for matching
|
||||
string baseDirectory = Path.GetDirectoryName(basePath) ?? string.Empty;
|
||||
string baseFilename = Path.GetFileNameWithoutExtension(basePath);
|
||||
|
||||
// Get the list of output files
|
||||
var outputFiles = GetOutputFiles(baseDirectory, baseFilename);
|
||||
if (outputFiles.Count == 0)
|
||||
return [];
|
||||
|
||||
// Filter down to zippable files
|
||||
var zippableFiles = outputFiles.Where(of => of.IsZippable);
|
||||
return zippableFiles.SelectMany(of => of.Filenames).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a list of all zippable file paths
|
||||
/// </summary>
|
||||
/// <param name="basePath">Base filename and path to use for checking</param>
|
||||
/// <returns>List of all zippable file paths, empty otherwise</returns>
|
||||
private List<string> GetZippableFilePaths(string basePath)
|
||||
{
|
||||
// Split the base path for matching
|
||||
string baseDirectory = Path.GetDirectoryName(basePath) ?? string.Empty;
|
||||
|
||||
// Get the list of zippable files
|
||||
var zippableFilenames = GetZippableFilenames(basePath);
|
||||
if (zippableFilenames.Count == 0)
|
||||
return [];
|
||||
|
||||
// Return only files that exist
|
||||
var zippableFiles = new List<string>();
|
||||
foreach (var filename in zippableFilenames)
|
||||
{
|
||||
// Skip non-existent files
|
||||
string possiblePath = Path.Combine(baseDirectory, filename);
|
||||
if (!File.Exists(possiblePath))
|
||||
continue;
|
||||
|
||||
zippableFiles.Add(possiblePath);
|
||||
}
|
||||
|
||||
return zippableFiles;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Shared Methods
|
||||
|
||||
/// <summary>
|
||||
/// Get the hex contents of the PIC file
|
||||
/// </summary>
|
||||
/// <param name="picPath">Path to the PIC.bin file associated with the dump</param>
|
||||
/// <param name="trimLength">Number of characters to trim the PIC to, if -1, ignored</param>
|
||||
/// <returns>PIC data as a hex string if possible, null on error</returns>
|
||||
/// <remarks>https://stackoverflow.com/questions/9932096/add-separator-to-string-at-every-n-characters</remarks>
|
||||
protected static string? GetPIC(string picPath, int trimLength = -1)
|
||||
{
|
||||
// If the file doesn't exist, we can't get the info
|
||||
if (!File.Exists(picPath))
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
var hex = ProcessingTool.GetFullFile(picPath, true);
|
||||
if (hex == null)
|
||||
return null;
|
||||
|
||||
if (trimLength > -1)
|
||||
hex = hex.Substring(0, trimLength);
|
||||
|
||||
// TODO: Check for non-zero values in discarded PIC
|
||||
|
||||
return Regex.Replace(hex, ".{32}", "$0\n", RegexOptions.Compiled);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// We don't care what the error was right now
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a isobuster-formatted PVD from a 2048 byte-per-sector image, if possible
|
||||
/// </summary>
|
||||
/// <param name="isoPath">Path to ISO file</param>
|
||||
/// <param name="pvd">Formatted PVD string, otherwise null</param>
|
||||
/// <returns>True if PVD was successfully parsed, otherwise false</returns>
|
||||
protected static bool GetPVD(string isoPath, out string? pvd)
|
||||
{
|
||||
pvd = null;
|
||||
try
|
||||
{
|
||||
// Get PVD bytes from ISO file
|
||||
var buf = new byte[96];
|
||||
using (FileStream iso = File.OpenRead(isoPath))
|
||||
{
|
||||
// TODO: Don't hardcode 0x8320
|
||||
iso.Seek(0x8320, SeekOrigin.Begin);
|
||||
|
||||
int offset = 0;
|
||||
while (offset < 96)
|
||||
{
|
||||
int read = iso.Read(buf, offset, buf.Length - offset);
|
||||
if (read == 0)
|
||||
throw new EndOfStreamException();
|
||||
offset += read;
|
||||
}
|
||||
}
|
||||
|
||||
// Format PVD to isobuster standard
|
||||
char[] pvdCharArray = new char[96];
|
||||
for (int i = 0; i < 96; i++)
|
||||
{
|
||||
if (buf[i] >= 0x20 && buf[i] <= 0x7E)
|
||||
pvdCharArray[i] = (char)buf[i];
|
||||
else
|
||||
pvdCharArray[i] = '.';
|
||||
}
|
||||
string pvdASCII = new string(pvdCharArray, 0, 96);
|
||||
pvd = string.Empty;
|
||||
for (int i = 0; i < 96; i += 16)
|
||||
{
|
||||
pvd += $"{(0x0320 + i):X4} : {buf[i]:X2} {buf[i + 1]:X2} {buf[i + 2]:X2} {buf[i + 3]:X2} {buf[i + 4]:X2} {buf[i + 5]:X2} {buf[i + 6]:X2} {buf[i + 7]:X2} " +
|
||||
$"{buf[i + 8]:X2} {buf[i + 9]:X2} {buf[i + 10]:X2} {buf[i + 11]:X2} {buf[i + 12]:X2} {buf[i + 13]:X2} {buf[i + 14]:X2} {buf[i + 15]:X2} {pvdASCII.Substring(i, 16)}\n";
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// We don't care what the error is
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
302
MPF.Processors/CleanRip.cs
Normal file
302
MPF.Processors/CleanRip.cs
Normal file
@@ -0,0 +1,302 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
using SabreTools.Hashing;
|
||||
using SabreTools.Models.Logiqx;
|
||||
using SabreTools.RedumpLib;
|
||||
using SabreTools.RedumpLib.Data;
|
||||
|
||||
namespace MPF.Processors
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents processing CleanRip outputs
|
||||
/// </summary>
|
||||
public sealed class CleanRip : BaseProcessor
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public CleanRip(RedumpSystem? system, MediaType? type) : base(system, type) { }
|
||||
|
||||
#region BaseProcessor Implementations
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void GenerateSubmissionInfo(SubmissionInfo info, string basePath, bool redumpCompat)
|
||||
{
|
||||
// Ensure that required sections exist
|
||||
info = Builder.EnsureAllSections(info);
|
||||
|
||||
// TODO: Determine if there's a CleanRip version anywhere
|
||||
info.DumpingInfo!.DumpingDate = ProcessingTool.GetFileModifiedDate(basePath + "-dumpinfo.txt")?.ToString("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
// Get the Datafile information
|
||||
var datafile = GenerateCleanripDatafile(basePath + ".iso", basePath + "-dumpinfo.txt");
|
||||
info.TracksAndWriteOffsets!.ClrMameProData = ProcessingTool.GenerateDatfile(datafile);
|
||||
|
||||
// Get the individual hash data, as per internal
|
||||
if (ProcessingTool.GetISOHashValues(datafile, out long size, out var crc32, out var md5, out var sha1))
|
||||
{
|
||||
info.SizeAndChecksums!.Size = size;
|
||||
info.SizeAndChecksums.CRC32 = crc32;
|
||||
info.SizeAndChecksums.MD5 = md5;
|
||||
info.SizeAndChecksums.SHA1 = sha1;
|
||||
|
||||
// Dual-layer discs have the same size and layerbreak
|
||||
if (size == 8511160320)
|
||||
info.SizeAndChecksums.Layerbreak = 2084960;
|
||||
}
|
||||
|
||||
// Extract info based generically on MediaType
|
||||
switch (Type)
|
||||
{
|
||||
case MediaType.DVD: // Only added here to help users; not strictly correct
|
||||
case MediaType.NintendoGameCubeGameDisc:
|
||||
case MediaType.NintendoWiiOpticalDisc:
|
||||
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, 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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
internal override List<OutputFile> GetOutputFiles(string? baseDirectory, string baseFilename)
|
||||
{
|
||||
switch (Type)
|
||||
{
|
||||
case MediaType.DVD: // Only added here to help users; not strictly correct
|
||||
case MediaType.NintendoGameCubeGameDisc:
|
||||
case MediaType.NintendoWiiOpticalDisc:
|
||||
return [
|
||||
new($"{baseFilename}.bca", OutputFileFlags.Required
|
||||
| OutputFileFlags.Binary
|
||||
| OutputFileFlags.Zippable,
|
||||
"bca"),
|
||||
new($"{baseFilename}.iso", OutputFileFlags.Required),
|
||||
|
||||
new($"{baseFilename}-dumpinfo.txt", OutputFileFlags.Required
|
||||
| OutputFileFlags.Artifact
|
||||
| OutputFileFlags.Zippable,
|
||||
"dumpinfo"),
|
||||
];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Information Extraction Methods
|
||||
|
||||
/// <summary>
|
||||
/// Get a formatted datfile from the cleanrip output, if possible
|
||||
/// </summary>
|
||||
/// <param name="iso">Path to ISO file</param>
|
||||
/// <param name="dumpinfo">Path to discinfo file</param>
|
||||
/// <returns></returns>
|
||||
private static Datafile? GenerateCleanripDatafile(string iso, string dumpinfo)
|
||||
{
|
||||
// If the files don't exist, we can't get info from it
|
||||
if (!File.Exists(iso) || !File.Exists(dumpinfo))
|
||||
return null;
|
||||
|
||||
long size = new FileInfo(iso).Length;
|
||||
string crc = string.Empty;
|
||||
string md5 = string.Empty;
|
||||
string sha1 = string.Empty;
|
||||
|
||||
try
|
||||
{
|
||||
// Make sure this file is a dumpinfo
|
||||
using var sr = File.OpenText(dumpinfo);
|
||||
if (sr.ReadLine()?.Contains("--File Generated by CleanRip") != true)
|
||||
return null;
|
||||
|
||||
// Read all lines and gather dat information
|
||||
while (!sr.EndOfStream)
|
||||
{
|
||||
var line = sr.ReadLine()?.Trim();
|
||||
if (string.IsNullOrEmpty(line))
|
||||
continue;
|
||||
else if (line!.StartsWith("CRC32"))
|
||||
crc = line.Substring(7).ToLowerInvariant();
|
||||
else if (line.StartsWith("MD5"))
|
||||
md5 = line.Substring(5);
|
||||
else if (line.StartsWith("SHA-1"))
|
||||
sha1 = line.Substring(7);
|
||||
}
|
||||
|
||||
// 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
|
||||
{
|
||||
Game = [new Game() { Rom = [new Rom { Name = Path.GetFileName(iso), Size = size.ToString(), CRC = crc, MD5 = md5, SHA1 = sha1 }] }]
|
||||
};
|
||||
}
|
||||
catch
|
||||
{
|
||||
// We don't care what the exception is right now
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the hex contents of the BCA file
|
||||
/// </summary>
|
||||
/// <param name="bcaPath">Path to the BCA file associated with the dump</param>
|
||||
/// <returns>BCA data as a hex string if possible, null on error</returns>
|
||||
/// <remarks>https://stackoverflow.com/questions/9932096/add-separator-to-string-at-every-n-characters</remarks>
|
||||
private static string? GetBCA(string bcaPath)
|
||||
{
|
||||
// If the file doesn't exist, we can't get the info
|
||||
if (!File.Exists(bcaPath))
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
var hex = ProcessingTool.GetFullFile(bcaPath, true);
|
||||
if (hex == null)
|
||||
return null;
|
||||
|
||||
// First separate into lines then into blocks of 4 hex digits
|
||||
string bca = Regex.Replace(hex, ".{32}", "$0\n");
|
||||
return Regex.Replace(bca, "[0-9a-fA-F]{4}", "$0 ");
|
||||
}
|
||||
catch
|
||||
{
|
||||
// We don't care what the error was right now
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the extracted GC and Wii version
|
||||
/// </summary>
|
||||
/// <param name="dumpinfo">Path to discinfo file</param>
|
||||
/// <param name="region">Output region, if possible</param>
|
||||
/// <param name="version">Output internal version of the game</param>
|
||||
/// <param name="name">Output internal name of the game</param>
|
||||
/// <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, out string? serial)
|
||||
{
|
||||
region = null; version = null; name = null; serial = null;
|
||||
|
||||
// If the file doesn't exist, we can't get info from it
|
||||
if (!File.Exists(dumpinfo))
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
// Make sure this file is a dumpinfo
|
||||
using var sr = File.OpenText(dumpinfo);
|
||||
if (sr.ReadLine()?.Contains("--File Generated by CleanRip") != true)
|
||||
return false;
|
||||
|
||||
// Read all lines and gather dat information
|
||||
while (!sr.EndOfStream)
|
||||
{
|
||||
var line = sr.ReadLine()?.Trim();
|
||||
if (string.IsNullOrEmpty(line))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else if (line!.StartsWith("Version"))
|
||||
{
|
||||
version = line.Substring("Version: ".Length);
|
||||
}
|
||||
else if (line.StartsWith("Internal Name"))
|
||||
{
|
||||
name = line.Substring("Internal Name: ".Length);
|
||||
}
|
||||
else if (line.StartsWith("Filename"))
|
||||
{
|
||||
serial = line.Substring("Filename: ".Length);
|
||||
if (serial.EndsWith("-disc2"))
|
||||
serial = serial.Replace("-disc2", string.Empty);
|
||||
|
||||
// char gameType = serial[0];
|
||||
// string gameid = serial[1] + serial[2];
|
||||
// string version = serial[4] + serial[5]
|
||||
|
||||
switch (serial[3])
|
||||
{
|
||||
case 'A':
|
||||
region = Region.World;
|
||||
break;
|
||||
case 'D':
|
||||
region = Region.Germany;
|
||||
break;
|
||||
case 'E':
|
||||
region = Region.UnitedStatesOfAmerica;
|
||||
break;
|
||||
case 'F':
|
||||
region = Region.France;
|
||||
break;
|
||||
case 'I':
|
||||
region = Region.Italy;
|
||||
break;
|
||||
case 'J':
|
||||
region = Region.Japan;
|
||||
break;
|
||||
case 'K':
|
||||
region = Region.SouthKorea;
|
||||
break;
|
||||
case 'L':
|
||||
region = Region.Europe; // Japanese import to Europe
|
||||
break;
|
||||
case 'M':
|
||||
region = Region.Europe; // American import to Europe
|
||||
break;
|
||||
case 'N':
|
||||
region = Region.UnitedStatesOfAmerica; // Japanese import to USA
|
||||
break;
|
||||
case 'P':
|
||||
region = Region.Europe;
|
||||
break;
|
||||
case 'R':
|
||||
region = Region.RussianFederation;
|
||||
break;
|
||||
case 'S':
|
||||
region = Region.Spain;
|
||||
break;
|
||||
case 'Q':
|
||||
region = Region.SouthKorea; // Korea with Japanese language
|
||||
break;
|
||||
case 'T':
|
||||
region = Region.SouthKorea; // Korea with English language
|
||||
break;
|
||||
case 'X':
|
||||
region = null; // Not a real region code
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// We don't care what the exception is right now
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
92
MPF.Processors/CustomOutputFile.cs
Normal file
92
MPF.Processors/CustomOutputFile.cs
Normal file
@@ -0,0 +1,92 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
#if NET452_OR_GREATER || NETCOREAPP
|
||||
using System.IO.Compression;
|
||||
#endif
|
||||
|
||||
namespace MPF.Processors
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a single output file with custom detection rules
|
||||
/// </summary>
|
||||
internal class CustomOutputFile : OutputFile
|
||||
{
|
||||
/// <summary>
|
||||
/// Optional func for determining if a file exists
|
||||
/// </summary>
|
||||
private readonly Func<string, bool> _existsFunc;
|
||||
|
||||
/// <summary>
|
||||
/// Create an OutputFile with a single filename
|
||||
/// </summary>
|
||||
public CustomOutputFile(string filename, OutputFileFlags flags, Func<string, bool> existsFunc)
|
||||
: base([filename], flags)
|
||||
{
|
||||
_existsFunc = existsFunc;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create an OutputFile with a single filename
|
||||
/// </summary>
|
||||
public CustomOutputFile(string filename, OutputFileFlags flags, string artifactKey, Func<string, bool> existsFunc)
|
||||
: base([filename], flags, artifactKey)
|
||||
{
|
||||
_existsFunc = existsFunc;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create an OutputFile with set of filenames
|
||||
/// </summary>
|
||||
public CustomOutputFile(string[] filenames, OutputFileFlags flags, Func<string, bool> existsFunc)
|
||||
: base(filenames, flags)
|
||||
{
|
||||
_existsFunc = existsFunc;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create an OutputFile with set of filenames
|
||||
/// </summary>
|
||||
public CustomOutputFile(string[] filenames, OutputFileFlags flags, string artifactKey, Func<string, bool> existsFunc)
|
||||
: base(filenames, flags, artifactKey)
|
||||
{
|
||||
_existsFunc = existsFunc;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Exists(string baseDirectory)
|
||||
{
|
||||
// Ensure the directory exists
|
||||
if (!Directory.Exists(baseDirectory))
|
||||
return false;
|
||||
|
||||
foreach (string filename in Filenames)
|
||||
{
|
||||
// Check for invalid filenames
|
||||
if (string.IsNullOrEmpty(filename))
|
||||
continue;
|
||||
|
||||
try
|
||||
{
|
||||
string possibleFile = Path.Combine(baseDirectory, filename);
|
||||
if (_existsFunc(possibleFile))
|
||||
return true;
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#if NET452_OR_GREATER || NETCOREAPP
|
||||
/// <summary>
|
||||
/// Indicates if an output file exists in an archive
|
||||
/// </summary>
|
||||
/// <param name="archive">Zip archive to check in</param>
|
||||
public override bool Exists(ZipArchive? archive)
|
||||
{
|
||||
// Files aren't extracted so this check can't be done
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
2150
MPF.Processors/DiscImageCreator.cs
Normal file
2150
MPF.Processors/DiscImageCreator.cs
Normal file
File diff suppressed because it is too large
Load Diff
7391
MPF.Processors/External/cicm.cs
vendored
Normal file
7391
MPF.Processors/External/cicm.cs
vendored
Normal file
File diff suppressed because it is too large
Load Diff
63
MPF.Processors/MPF.Processors.csproj
Normal file
63
MPF.Processors/MPF.Processors.csproj
Normal file
@@ -0,0 +1,63 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<!-- Assembly Properties -->
|
||||
<TargetFrameworks>net20;net35;net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0</TargetFrameworks>
|
||||
<CheckEolTargetFramework>false</CheckEolTargetFramework>
|
||||
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<VersionPrefix>3.2.3</VersionPrefix>
|
||||
<WarningsNotAsErrors>NU5104</WarningsNotAsErrors>
|
||||
|
||||
<!-- Package Properties -->
|
||||
<Authors>Matt Nadareski;ReignStumble;Jakz</Authors>
|
||||
<Description>Common code for all MPF implementations</Description>
|
||||
<Copyright>Copyright (c) Matt Nadareski 2019-2024</Copyright>
|
||||
<PackageProjectUrl>https://github.com/SabreTools/</PackageProjectUrl>
|
||||
<RepositoryUrl>https://github.com/SabreTools/MPF</RepositoryUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<InternalsVisibleTo Include="MPF.Test" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Support All Frameworks -->
|
||||
<PropertyGroup Condition="$(TargetFramework.StartsWith(`net2`)) OR $(TargetFramework.StartsWith(`net3`)) OR $(TargetFramework.StartsWith(`net4`))">
|
||||
<RuntimeIdentifiers>win-x86;win-x64</RuntimeIdentifiers>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="$(TargetFramework.StartsWith(`netcoreapp`)) OR $(TargetFramework.StartsWith(`net5`))">
|
||||
<RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64</RuntimeIdentifiers>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="$(TargetFramework.StartsWith(`net6`)) OR $(TargetFramework.StartsWith(`net7`)) OR $(TargetFramework.StartsWith(`net8`))">
|
||||
<RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64;osx-arm64</RuntimeIdentifiers>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="$(RuntimeIdentifier.StartsWith(`osx-arm`))">
|
||||
<TargetFrameworks>net6.0;net7.0;net8.0</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Support for old .NET versions -->
|
||||
<ItemGroup Condition="$(TargetFramework.StartsWith(`net2`)) OR $(TargetFramework.StartsWith(`net3`)) OR $(TargetFramework.StartsWith(`net40`))">
|
||||
<PackageReference Include="MinAsyncBridge" Version="0.12.4" />
|
||||
<PackageReference Include="MinTasksExtensionsBridge" Version="0.3.4" />
|
||||
<PackageReference Include="MinThreadingBridge" Version="0.11.4" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="!$(TargetFramework.StartsWith(`net2`)) AND !$(TargetFramework.StartsWith(`net3`)) AND !$(TargetFramework.StartsWith(`net40`))">
|
||||
<PackageReference Include="System.IO.Compression" Version="4.3.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="!$(TargetFramework.StartsWith(`net2`)) AND !$(TargetFramework.StartsWith(`net3`)) AND !$(TargetFramework.StartsWith(`net40`)) AND !$(TargetFramework.StartsWith(`net452`))">
|
||||
<PackageReference Include="System.IO.Compression.ZipFile" Version="4.3.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="psxt001z.Library" Version="0.21.0-rc1" />
|
||||
<PackageReference Include="SabreTools.Hashing" Version="1.2.2" />
|
||||
<PackageReference Include="SabreTools.Models" Version="1.4.11" />
|
||||
<PackageReference Include="SabreTools.RedumpLib" Version="1.4.4" />
|
||||
<PackageReference Include="SabreTools.Serialization" Version="1.6.9" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
250
MPF.Processors/OutputFile.cs
Normal file
250
MPF.Processors/OutputFile.cs
Normal file
@@ -0,0 +1,250 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
#if NET452_OR_GREATER || NETCOREAPP
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
#endif
|
||||
|
||||
namespace MPF.Processors
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents attributes about an <see cref="OutputFile"/>
|
||||
/// </summary>
|
||||
[Flags]
|
||||
internal enum OutputFileFlags : ushort
|
||||
{
|
||||
/// <summary>
|
||||
/// Default state
|
||||
/// </summary>
|
||||
None = 0x0000,
|
||||
|
||||
/// <summary>
|
||||
/// File is required to exist
|
||||
/// </summary>
|
||||
Required = 0x0001,
|
||||
|
||||
/// <summary>
|
||||
/// File is included as an artifact
|
||||
/// </summary>
|
||||
Artifact = 0x0002,
|
||||
|
||||
/// <summary>
|
||||
/// File is included as a binary artifact
|
||||
/// </summary>
|
||||
Binary = 0x0004,
|
||||
|
||||
/// <summary>
|
||||
/// File can be deleted after processing
|
||||
/// </summary>
|
||||
Deleteable = 0x0008,
|
||||
|
||||
/// <summary>
|
||||
/// File can be zipped after processing
|
||||
/// </summary>
|
||||
Zippable = 0x0010,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a single output file
|
||||
/// </summary>
|
||||
internal class OutputFile
|
||||
{
|
||||
/// <summary>
|
||||
/// Set of all filename variants
|
||||
/// </summary>
|
||||
public string[] Filenames { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Key used when creating an artifact
|
||||
/// </summary>
|
||||
public string? ArtifactKey { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if the file is required
|
||||
/// </summary>
|
||||
public bool IsRequired
|
||||
{
|
||||
get
|
||||
{
|
||||
#if NET20 || NET35
|
||||
return (_flags & OutputFileFlags.Required) != 0;
|
||||
#else
|
||||
return _flags.HasFlag(OutputFileFlags.Required);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if the file is an artifact
|
||||
/// </summary>
|
||||
public bool IsArtifact
|
||||
{
|
||||
get
|
||||
{
|
||||
#if NET20 || NET35
|
||||
return (_flags & OutputFileFlags.Artifact) != 0
|
||||
|| (_flags & OutputFileFlags.Binary) != 0;
|
||||
#else
|
||||
return _flags.HasFlag(OutputFileFlags.Artifact)
|
||||
|| _flags.HasFlag(OutputFileFlags.Binary);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if the file is a binary artifact
|
||||
/// </summary>
|
||||
public bool IsBinaryArtifact
|
||||
{
|
||||
get
|
||||
{
|
||||
#if NET20 || NET35
|
||||
return (_flags & OutputFileFlags.Binary) != 0;
|
||||
#else
|
||||
return _flags.HasFlag(OutputFileFlags.Binary);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if the file is deleteable after processing
|
||||
/// </summary>
|
||||
public bool IsDeleteable
|
||||
{
|
||||
get
|
||||
{
|
||||
#if NET20 || NET35
|
||||
return (_flags & OutputFileFlags.Deleteable) != 0;
|
||||
#else
|
||||
return _flags.HasFlag(OutputFileFlags.Deleteable);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if the file is zippable after processing
|
||||
/// </summary>
|
||||
public bool IsZippable
|
||||
{
|
||||
get
|
||||
{
|
||||
#if NET20 || NET35
|
||||
return (_flags & OutputFileFlags.Zippable) != 0;
|
||||
#else
|
||||
return _flags.HasFlag(OutputFileFlags.Zippable);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents attributes about the current file
|
||||
/// </summary>
|
||||
protected readonly OutputFileFlags _flags;
|
||||
|
||||
/// <summary>
|
||||
/// Create an OutputFile with a single filename
|
||||
/// </summary>
|
||||
public OutputFile(string filename, OutputFileFlags flags)
|
||||
: this([filename], flags)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create an OutputFile with a single filename
|
||||
/// </summary>
|
||||
public OutputFile(string filename, OutputFileFlags flags, string artifactKey)
|
||||
: this([filename], flags, artifactKey)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create an OutputFile with set of filenames
|
||||
/// </summary>
|
||||
public OutputFile(string[] filenames, OutputFileFlags flags)
|
||||
{
|
||||
Filenames = filenames;
|
||||
ArtifactKey = null;
|
||||
_flags = flags;
|
||||
|
||||
// Validate the inputs
|
||||
if (filenames.Length == 0)
|
||||
throw new ArgumentException($"{nameof(filenames)} must contain at least one value");
|
||||
if (IsArtifact && string.IsNullOrEmpty(ArtifactKey))
|
||||
throw new ArgumentException($"{nameof(flags)} should not contain the Artifact or Binary flag");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create an OutputFile with set of filenames
|
||||
/// </summary>
|
||||
public OutputFile(string[] filenames, OutputFileFlags flags, string artifactKey)
|
||||
{
|
||||
Filenames = filenames;
|
||||
ArtifactKey = artifactKey;
|
||||
_flags = flags;
|
||||
|
||||
// Validate the inputs
|
||||
if (filenames.Length == 0)
|
||||
throw new ArgumentException($"{nameof(filenames)} must contain at least one value");
|
||||
if (IsArtifact && string.IsNullOrEmpty(ArtifactKey))
|
||||
throw new ArgumentException($"{nameof(flags)} should not contain the Artifact or Binary flag");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if an output file exists in a base directory
|
||||
/// </summary>
|
||||
/// <param name="baseDirectory">Base directory to check in</param>
|
||||
public virtual bool Exists(string baseDirectory)
|
||||
{
|
||||
// Ensure the directory exists
|
||||
if (!Directory.Exists(baseDirectory))
|
||||
return false;
|
||||
|
||||
foreach (string filename in Filenames)
|
||||
{
|
||||
// Check for invalid filenames
|
||||
if (string.IsNullOrEmpty(filename))
|
||||
continue;
|
||||
|
||||
try
|
||||
{
|
||||
string possibleFile = Path.Combine(baseDirectory, filename);
|
||||
if (File.Exists(possibleFile))
|
||||
return true;
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#if NET452_OR_GREATER || NETCOREAPP
|
||||
/// <summary>
|
||||
/// Indicates if an output file exists in an archive
|
||||
/// </summary>
|
||||
/// <param name="archive">Zip archive to check in</param>
|
||||
public virtual bool Exists(ZipArchive? archive)
|
||||
{
|
||||
// If the archive is invalid
|
||||
if (archive == null)
|
||||
return false;
|
||||
|
||||
foreach (string filename in Filenames)
|
||||
{
|
||||
// Check for invalid filenames
|
||||
if (string.IsNullOrEmpty(filename))
|
||||
continue;
|
||||
|
||||
try
|
||||
{
|
||||
// Check all entries on filename alone
|
||||
if (archive.Entries.Any(e => e.Name == filename))
|
||||
return true;
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
157
MPF.Processors/PS3CFW.cs
Normal file
157
MPF.Processors/PS3CFW.cs
Normal file
@@ -0,0 +1,157 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
using SabreTools.Hashing;
|
||||
using SabreTools.Models.Logiqx;
|
||||
using SabreTools.RedumpLib;
|
||||
using SabreTools.RedumpLib.Data;
|
||||
|
||||
namespace MPF.Processors
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents processing PlayStation 3 Custom Firmware outputs
|
||||
/// </summary>
|
||||
public sealed class PS3CFW : BaseProcessor
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public PS3CFW(RedumpSystem? system, MediaType? type) : base(system, type) { }
|
||||
|
||||
#region BaseProcessor Implementations
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void GenerateSubmissionInfo(SubmissionInfo info, string basePath, bool redumpCompat)
|
||||
{
|
||||
// Ensure that required sections exist
|
||||
info = Builder.EnsureAllSections(info);
|
||||
|
||||
// TODO: Determine if there's a CFW version anywhere
|
||||
info.DumpingInfo!.DumpingDate = ProcessingTool.GetFileModifiedDate(basePath + ".iso")?.ToString("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
// Get the Datafile information
|
||||
Datafile? datafile = GeneratePS3CFWDatafile(basePath + ".iso");
|
||||
|
||||
// Fill in the hash data
|
||||
info.TracksAndWriteOffsets!.ClrMameProData = ProcessingTool.GenerateDatfile(datafile);
|
||||
|
||||
// Get the individual hash data, as per internal
|
||||
if (ProcessingTool.GetISOHashValues(datafile, out long size, out var crc32, out var md5, out var sha1))
|
||||
{
|
||||
info.SizeAndChecksums!.Size = size;
|
||||
info.SizeAndChecksums.CRC32 = crc32;
|
||||
info.SizeAndChecksums.MD5 = md5;
|
||||
info.SizeAndChecksums.SHA1 = sha1;
|
||||
}
|
||||
|
||||
// Get the PVD from the ISO
|
||||
if (GetPVD(basePath + ".iso", out string? pvd))
|
||||
info.Extras!.PVD = pvd;
|
||||
|
||||
// Try to determine the name of the GetKey file(s)
|
||||
string? getKeyBasePath = GetCFWBasePath(basePath);
|
||||
|
||||
// If GenerateSubmissionInfo is run, .getkey.log existence should already be checked
|
||||
if (!File.Exists(getKeyBasePath + ".getkey.log"))
|
||||
return;
|
||||
|
||||
// Get dumping date from GetKey log date
|
||||
info.DumpingInfo.DumpingDate = ProcessingTool.GetFileModifiedDate(getKeyBasePath + ".getkey.log")?.ToString("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
// TODO: Put info about abnormal PIC info beyond 132 bytes in comments?
|
||||
if (File.Exists(getKeyBasePath + ".disc.pic"))
|
||||
info.Extras!.PIC = GetPIC(getKeyBasePath + ".disc.pic", 264);
|
||||
|
||||
// Parse Disc Key, Disc ID, and PIC from the .getkey.log file
|
||||
if (ProcessingTool.ParseGetKeyLog(getKeyBasePath + ".getkey.log", out string? key, out string? id, out string? pic))
|
||||
{
|
||||
if (key != null)
|
||||
info.Extras!.DiscKey = key.ToUpperInvariant();
|
||||
if (id != null)
|
||||
info.Extras!.DiscID = id.ToUpperInvariant().Substring(0, 24) + "XXXXXXXX";
|
||||
if (string.IsNullOrEmpty(info.Extras!.PIC) && !string.IsNullOrEmpty(pic))
|
||||
{
|
||||
pic = Regex.Replace(pic, ".{32}", "$0\n");
|
||||
info.Extras.PIC = pic;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
internal override List<OutputFile> GetOutputFiles(string? baseDirectory, string baseFilename)
|
||||
{
|
||||
switch (Type)
|
||||
{
|
||||
case MediaType.BluRay:
|
||||
return [
|
||||
new($"{baseFilename}.iso", OutputFileFlags.Required),
|
||||
new($"{baseFilename}.getkey.log", OutputFileFlags.Required
|
||||
| OutputFileFlags.Artifact
|
||||
| OutputFileFlags.Zippable,
|
||||
"getkey_log"),
|
||||
new($"{baseFilename}.disc.pic", OutputFileFlags.Required
|
||||
| OutputFileFlags.Binary
|
||||
| OutputFileFlags.Zippable,
|
||||
"disc_pic"),
|
||||
];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Information Extraction Methods
|
||||
|
||||
/// <summary>
|
||||
/// Get a formatted datfile from the PS3 CFW output, if possible
|
||||
/// </summary>
|
||||
/// <param name="iso">Path to ISO file</param>
|
||||
/// <returns></returns>
|
||||
private static Datafile? GeneratePS3CFWDatafile(string iso)
|
||||
{
|
||||
// If the ISO file doesn't exist, we can't get info from it
|
||||
if (!File.Exists(iso))
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
if (HashTool.GetStandardHashes(iso, out long size, out string? crc, out string? md5, out string? sha1))
|
||||
{
|
||||
return new Datafile
|
||||
{
|
||||
Game = [new Game { Rom = [new Rom { Name = Path.GetFileName(iso), Size = size.ToString(), CRC = crc, MD5 = md5, SHA1 = sha1 }] }]
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// We don't care what the exception is right now
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Helper Functions
|
||||
|
||||
/// <summary>
|
||||
/// Estimate the base filename of the .getkey.log file associated with the dump
|
||||
/// </summary>
|
||||
/// <param name="iso">Path to ISO file</param>
|
||||
/// <returns>Base filename, null if not found</returns>
|
||||
private string? GetCFWBasePath(string iso)
|
||||
{
|
||||
string? dir = Path.GetDirectoryName(iso);
|
||||
dir ??= ".";
|
||||
|
||||
string[] files = Directory.GetFiles(dir, "*.getkey.log");
|
||||
|
||||
if (files.Length != 1)
|
||||
return null;
|
||||
|
||||
return files[0].Substring(0, files[0].Length - 11);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
1222
MPF.Processors/ProcessingTool.cs
Normal file
1222
MPF.Processors/ProcessingTool.cs
Normal file
File diff suppressed because it is too large
Load Diff
1783
MPF.Processors/Redumper.cs
Normal file
1783
MPF.Processors/Redumper.cs
Normal file
File diff suppressed because it is too large
Load Diff
88
MPF.Processors/RegexOutputFile.cs
Normal file
88
MPF.Processors/RegexOutputFile.cs
Normal file
@@ -0,0 +1,88 @@
|
||||
using System.IO;
|
||||
#if NET452_OR_GREATER || NETCOREAPP
|
||||
using System.IO.Compression;
|
||||
#endif
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace MPF.Processors
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a single output file with a Regex-matched name
|
||||
/// </summary>
|
||||
internal class RegexOutputFile : OutputFile
|
||||
{
|
||||
/// <summary>
|
||||
/// Create an OutputFile with a single filename
|
||||
/// </summary>
|
||||
public RegexOutputFile(string filename, OutputFileFlags flags)
|
||||
: base([filename], flags)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create an OutputFile with a single filename
|
||||
/// </summary>
|
||||
public RegexOutputFile(string filename, OutputFileFlags flags, string artifactKey)
|
||||
: base([filename], flags, artifactKey)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create an OutputFile with set of filenames
|
||||
/// </summary>
|
||||
public RegexOutputFile(string[] filenames, OutputFileFlags flags)
|
||||
: base(filenames, flags)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create an OutputFile with set of filenames
|
||||
/// </summary>
|
||||
public RegexOutputFile(string[] filenames, OutputFileFlags flags, string artifactKey)
|
||||
: base(filenames, flags, artifactKey)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Exists(string baseDirectory)
|
||||
{
|
||||
// Ensure the directory exists
|
||||
if (!Directory.Exists(baseDirectory))
|
||||
return false;
|
||||
|
||||
// Get list of all files in directory
|
||||
var directoryFiles = Directory.GetFiles(baseDirectory);
|
||||
foreach (string file in directoryFiles)
|
||||
{
|
||||
if (Filenames.Any(pattern => Regex.IsMatch(file, pattern)))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#if NET452_OR_GREATER || NETCOREAPP
|
||||
/// <summary>
|
||||
/// Indicates if an output file exists in an archive
|
||||
/// </summary>
|
||||
/// <param name="archive">Zip archive to check in</param>
|
||||
public override bool Exists(ZipArchive? archive)
|
||||
{
|
||||
// If the archive is invalid
|
||||
if (archive == null)
|
||||
return false;
|
||||
|
||||
// Get list of all files in archive
|
||||
var archiveFiles = archive.Entries.Select(e => e.Name).ToList();
|
||||
foreach (string file in archiveFiles)
|
||||
{
|
||||
if (Filenames.Any(pattern => Regex.IsMatch(file, pattern)))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
302
MPF.Processors/UmdImageCreator.cs
Normal file
302
MPF.Processors/UmdImageCreator.cs
Normal file
@@ -0,0 +1,302 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using SabreTools.Hashing;
|
||||
using SabreTools.Models.Logiqx;
|
||||
using SabreTools.RedumpLib;
|
||||
using SabreTools.RedumpLib.Data;
|
||||
|
||||
namespace MPF.Processors
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents processing UmdImageCreator outputs
|
||||
/// </summary>
|
||||
public sealed class UmdImageCreator : BaseProcessor
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public UmdImageCreator(RedumpSystem? system, MediaType? type) : base(system, type) { }
|
||||
|
||||
#region BaseProcessor Implementations
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void GenerateSubmissionInfo(SubmissionInfo info, string basePath, bool redumpCompat)
|
||||
{
|
||||
// Ensure that required sections exist
|
||||
info = Builder.EnsureAllSections(info);
|
||||
|
||||
// TODO: Determine if there's a UMDImageCreator version anywhere
|
||||
info.DumpingInfo!.DumpingDate = ProcessingTool.GetFileModifiedDate(basePath + "_disc.txt")?.ToString("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
// Fill in the volume labels
|
||||
if (GetVolumeLabels($"{basePath}_volDesc.txt", out var volLabels))
|
||||
VolumeLabels = volLabels;
|
||||
|
||||
// Extract info based generically on MediaType
|
||||
switch (Type)
|
||||
{
|
||||
case MediaType.UMD:
|
||||
info.Extras!.PVD = GetPVD(basePath + "_mainInfo.txt") ?? string.Empty;
|
||||
|
||||
if (HashTool.GetStandardHashes(basePath + ".iso", out long filesize, out var crc32, out var md5, out var sha1))
|
||||
{
|
||||
// Get the Datafile information
|
||||
var datafile = new Datafile
|
||||
{
|
||||
Game = [new Game { Rom = [new Rom { Name = string.Empty, Size = filesize.ToString(), CRC = crc32, MD5 = md5, SHA1 = sha1 }] }]
|
||||
};
|
||||
|
||||
// Fill in the hash data
|
||||
info.TracksAndWriteOffsets!.ClrMameProData = ProcessingTool.GenerateDatfile(datafile);
|
||||
|
||||
info.SizeAndChecksums!.Size = filesize;
|
||||
info.SizeAndChecksums.CRC32 = crc32;
|
||||
info.SizeAndChecksums.MD5 = md5;
|
||||
info.SizeAndChecksums.SHA1 = sha1;
|
||||
}
|
||||
|
||||
if (GetUMDAuxInfo(basePath + "_disc.txt",
|
||||
out var title,
|
||||
out DiscCategory? category,
|
||||
out string? serial,
|
||||
out var version,
|
||||
out var layer,
|
||||
out long size))
|
||||
{
|
||||
info.CommonDiscInfo!.Title = title ?? string.Empty;
|
||||
info.CommonDiscInfo.Category = category ?? DiscCategory.Games;
|
||||
info.CommonDiscInfo.CommentsSpecialFields![SiteCode.InternalSerialName] = serial ?? string.Empty;
|
||||
info.VersionAndEditions!.Version = version ?? string.Empty;
|
||||
info.SizeAndChecksums!.Size = size;
|
||||
|
||||
if (!string.IsNullOrEmpty(layer))
|
||||
info.SizeAndChecksums.Layerbreak = Int64.Parse(layer ?? "-1");
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
internal override List<OutputFile> GetOutputFiles(string? baseDirectory, string baseFilename)
|
||||
{
|
||||
switch (Type)
|
||||
{
|
||||
case MediaType.UMD:
|
||||
return [
|
||||
new($"{baseFilename}.iso", OutputFileFlags.Required),
|
||||
|
||||
new($"{baseFilename}_disc.txt", OutputFileFlags.Required
|
||||
| OutputFileFlags.Artifact
|
||||
| OutputFileFlags.Zippable,
|
||||
"disc"),
|
||||
new($"{baseFilename}_drive.txt", OutputFileFlags.Artifact
|
||||
| OutputFileFlags.Zippable,
|
||||
"drive"),
|
||||
new($"{baseFilename}_mainError.txt", OutputFileFlags.Required
|
||||
| OutputFileFlags.Artifact
|
||||
| OutputFileFlags.Zippable,
|
||||
"main_error"),
|
||||
new($"{baseFilename}_mainInfo.txt", OutputFileFlags.Required
|
||||
| OutputFileFlags.Artifact
|
||||
| OutputFileFlags.Zippable,
|
||||
"main_info"),
|
||||
new($"{baseFilename}_PFI.bin", OutputFileFlags.Binary
|
||||
| OutputFileFlags.Zippable,
|
||||
"pfi"),
|
||||
new($"{baseFilename}_volDesc.txt", OutputFileFlags.Required
|
||||
| OutputFileFlags.Artifact
|
||||
| OutputFileFlags.Zippable,
|
||||
"vol_desc"),
|
||||
];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Information Extraction Methods
|
||||
|
||||
/// <summary>
|
||||
/// Get the PVD from the input file, if possible
|
||||
/// </summary>
|
||||
/// <param name="mainInfo">_mainInfo.txt file location</param>
|
||||
/// <returns>Newline-deliminated PVD if possible, null on error</returns>
|
||||
private static string? GetPVD(string mainInfo)
|
||||
{
|
||||
// If the file doesn't exist, we can't get info from it
|
||||
if (!File.Exists(mainInfo))
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
// Make sure we're in the right sector
|
||||
using var sr = File.OpenText(mainInfo);
|
||||
while (sr.ReadLine()?.StartsWith("========== LBA[000016, 0x0000010]: Main Channel ==========") == false) ;
|
||||
|
||||
// Fast forward to the PVD
|
||||
while (sr.ReadLine()?.StartsWith("0310") == false) ;
|
||||
|
||||
// Now that we're at the PVD, read each line in and concatenate
|
||||
string pvd = "";
|
||||
for (int i = 0; i < 6; i++)
|
||||
pvd += sr.ReadLine() + "\n"; // 320-370
|
||||
|
||||
return pvd;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// We don't care what the exception is right now
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the UMD auxiliary info from the outputted files, if possible
|
||||
/// </summary>
|
||||
/// <param name="disc">_disc.txt file location</param>
|
||||
/// <returns>True on successful extraction of info, false otherwise</returns>
|
||||
private static bool GetUMDAuxInfo(string disc,
|
||||
out string? title,
|
||||
out DiscCategory? category,
|
||||
out string? serial,
|
||||
out string? version,
|
||||
out string? layer,
|
||||
out long size)
|
||||
{
|
||||
title = null; serial = null; version = null; layer = null;
|
||||
category = null;
|
||||
size = -1;
|
||||
|
||||
// If the file doesn't exist, we can't get info from it
|
||||
if (!File.Exists(disc))
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
// Loop through everything to get the first instance of each required field
|
||||
using var sr = File.OpenText(disc);
|
||||
while (!sr.EndOfStream)
|
||||
{
|
||||
string? line = sr.ReadLine()?.Trim();
|
||||
if (line == null)
|
||||
break;
|
||||
|
||||
if (line.StartsWith("TITLE") && title == null)
|
||||
title = line.Split(' ')[1];
|
||||
else if (line.StartsWith("DISC_ID") && version == null)
|
||||
serial = line.Split(' ')[1];
|
||||
else if (line.StartsWith("DISC_VERSION") && version == null)
|
||||
version = line.Split(' ')[1];
|
||||
else if (line.StartsWith("pspUmdTypes"))
|
||||
category = ProcessingTool.GetUMDCategory(line.Split(' ')[1]);
|
||||
else if (line.StartsWith("L0 length"))
|
||||
layer = line.Split(' ')[2];
|
||||
else if (line.StartsWith("FileSize:"))
|
||||
size = Int64.Parse(line.Split(' ')[1]);
|
||||
}
|
||||
|
||||
// If we have a serial, format it
|
||||
if (!string.IsNullOrEmpty(serial) && serial!.Length > 4)
|
||||
serial = serial.Substring(0, 4) + "-" + serial.Substring(4);
|
||||
|
||||
// If the L0 length is the size of the full disc, there's no layerbreak
|
||||
if (Int64.TryParse(layer, out long umdlayerValue) && umdlayerValue * 2048 == size)
|
||||
layer = null;
|
||||
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// We don't care what the exception is right now
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <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
|
||||
}
|
||||
}
|
||||
623
MPF.Processors/XboxBackupCreator.cs
Normal file
623
MPF.Processors/XboxBackupCreator.cs
Normal file
@@ -0,0 +1,623 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using SabreTools.Hashing;
|
||||
using SabreTools.Models.Logiqx;
|
||||
using SabreTools.RedumpLib;
|
||||
using SabreTools.RedumpLib.Data;
|
||||
|
||||
namespace MPF.Processors
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents processing Xbox Backup Creator outputs
|
||||
/// </summary>
|
||||
public sealed class XboxBackupCreator : BaseProcessor
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public XboxBackupCreator(RedumpSystem? system, MediaType? type) : base(system, type) { }
|
||||
|
||||
#region BaseProcessor Implementations
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void GenerateSubmissionInfo(SubmissionInfo info, string basePath, bool redumpCompat)
|
||||
{
|
||||
// Ensure that required sections exist
|
||||
info = Builder.EnsureAllSections(info);
|
||||
|
||||
// Get base directory
|
||||
string baseDirectory = Path.GetDirectoryName(basePath) ?? string.Empty;
|
||||
|
||||
// Get log filename
|
||||
string? logPath = GetLogName(baseDirectory);
|
||||
if (string.IsNullOrEmpty(logPath))
|
||||
return;
|
||||
|
||||
// XBC dump info
|
||||
info.DumpingInfo!.DumpingProgram ??= string.Empty;
|
||||
info.DumpingInfo.DumpingProgram += $" {GetVersion(logPath) ?? "Unknown Version"}";
|
||||
info.DumpingInfo.DumpingDate = ProcessingTool.GetFileModifiedDate(logPath)?.ToString("yyyy-MM-dd HH:mm:ss");
|
||||
info.DumpingInfo.Model = GetDrive(logPath) ?? "Unknown Drive";
|
||||
|
||||
// Look for read errors
|
||||
if (GetReadErrors(logPath, out long readErrors))
|
||||
info.CommonDiscInfo!.ErrorsCount = readErrors == -1 ? "Error retrieving error count" : readErrors.ToString();
|
||||
|
||||
// Extract info based generically on MediaType
|
||||
switch (Type)
|
||||
{
|
||||
case MediaType.DVD:
|
||||
|
||||
// Get Layerbreak from .dvd file if possible
|
||||
if (GetLayerbreak($"{basePath}.dvd", out long layerbreak))
|
||||
info.SizeAndChecksums!.Layerbreak = layerbreak;
|
||||
|
||||
// Hash data
|
||||
if (HashTool.GetStandardHashes(basePath + ".iso", out long filesize, out var crc32, out var md5, out var sha1))
|
||||
{
|
||||
// Get the Datafile information
|
||||
var datafile = new Datafile
|
||||
{
|
||||
Game = [new Game { Rom = [new Rom { Name = string.Empty, Size = filesize.ToString(), CRC = crc32, MD5 = md5, SHA1 = sha1 }] }]
|
||||
};
|
||||
|
||||
// Fill in the hash data
|
||||
info.TracksAndWriteOffsets!.ClrMameProData = ProcessingTool.GenerateDatfile(datafile);
|
||||
|
||||
info.SizeAndChecksums!.Size = filesize;
|
||||
info.SizeAndChecksums.CRC32 = crc32;
|
||||
info.SizeAndChecksums.MD5 = md5;
|
||||
info.SizeAndChecksums.SHA1 = sha1;
|
||||
}
|
||||
|
||||
switch (System)
|
||||
{
|
||||
case RedumpSystem.MicrosoftXbox:
|
||||
|
||||
// Parse DMI.bin
|
||||
string xmidString = ProcessingTool.GetXMID(Path.Combine(baseDirectory, "DMI.bin"));
|
||||
var xmid = SabreTools.Serialization.Wrappers.XMID.Create(xmidString);
|
||||
if (xmid != null)
|
||||
{
|
||||
info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.XMID] = xmidString?.TrimEnd('\0') ?? string.Empty;
|
||||
info.CommonDiscInfo.Serial = xmid.Serial ?? string.Empty;
|
||||
if (!redumpCompat)
|
||||
info.VersionAndEditions!.Version = xmid.Version ?? string.Empty;
|
||||
|
||||
info.CommonDiscInfo.Region = ProcessingTool.GetXGDRegion(xmid.Model.RegionIdentifier);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case RedumpSystem.MicrosoftXbox360:
|
||||
|
||||
// Get PVD from ISO
|
||||
if (GetPVD(basePath + ".iso", out string? pvd))
|
||||
info.Extras!.PVD = pvd;
|
||||
|
||||
// Parse Media ID
|
||||
//string? mediaID = GetMediaID(logPath);
|
||||
|
||||
// Parse DMI.bin
|
||||
string xemidString = ProcessingTool.GetXeMID(Path.Combine(baseDirectory, "DMI.bin"));
|
||||
var xemid = SabreTools.Serialization.Wrappers.XeMID.Create(xemidString);
|
||||
if (xemid != null)
|
||||
{
|
||||
info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.XeMID] = xemidString?.TrimEnd('\0') ?? string.Empty;
|
||||
info.CommonDiscInfo.Serial = xemid.Serial ?? string.Empty;
|
||||
if (!redumpCompat)
|
||||
info.VersionAndEditions!.Version = xemid.Version ?? string.Empty;
|
||||
|
||||
info.CommonDiscInfo.Region = ProcessingTool.GetXGDRegion(xemid.Model.RegionIdentifier);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// Get the output file paths
|
||||
string dmiPath = Path.Combine(baseDirectory, "DMI.bin");
|
||||
string pfiPath = Path.Combine(baseDirectory, "PFI.bin");
|
||||
string ssPath = Path.Combine(baseDirectory, "SS.bin");
|
||||
|
||||
// Deal with SS.bin
|
||||
if (File.Exists(ssPath))
|
||||
{
|
||||
// Save security sector ranges
|
||||
string? ranges = ProcessingTool.GetSSRanges(ssPath);
|
||||
if (!string.IsNullOrEmpty(ranges))
|
||||
info.Extras!.SecuritySectorRanges = ranges;
|
||||
|
||||
// TODO: Determine SS version?
|
||||
//info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.SSVersion] =
|
||||
|
||||
// Recreate RawSS.bin
|
||||
RecreateSS(logPath!, ssPath, Path.Combine(baseDirectory, "RawSS.bin"));
|
||||
|
||||
// Run ss_sector_range to get repeatable SS hash
|
||||
ProcessingTool.CleanSS(ssPath, ssPath);
|
||||
}
|
||||
|
||||
// DMI/PFI/SS CRC32 hashes
|
||||
if (File.Exists(dmiPath))
|
||||
info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.DMIHash] = HashTool.GetFileHash(dmiPath, HashType.CRC32)?.ToUpperInvariant() ?? string.Empty;
|
||||
if (File.Exists(pfiPath))
|
||||
info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.PFIHash] = HashTool.GetFileHash(pfiPath, HashType.CRC32)?.ToUpperInvariant() ?? string.Empty;
|
||||
if (File.Exists(ssPath))
|
||||
info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.SSHash] = HashTool.GetFileHash(ssPath, HashType.CRC32)?.ToUpperInvariant() ?? string.Empty;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
internal override List<OutputFile> GetOutputFiles(string? baseDirectory, string baseFilename)
|
||||
{
|
||||
switch (Type)
|
||||
{
|
||||
case MediaType.DVD:
|
||||
return [
|
||||
new($"{baseFilename}.dvd", OutputFileFlags.Artifact
|
||||
| OutputFileFlags.Zippable,
|
||||
"dvd"),
|
||||
new($"{baseFilename}.iso", OutputFileFlags.Required),
|
||||
|
||||
new("DMI.bin", OutputFileFlags.Required
|
||||
| OutputFileFlags.Binary
|
||||
| OutputFileFlags.Zippable,
|
||||
"dmi"),
|
||||
new RegexOutputFile("[lL]og.txt", OutputFileFlags.Required
|
||||
| OutputFileFlags.Artifact
|
||||
| OutputFileFlags.Zippable,
|
||||
"log"),
|
||||
new("PFI.bin", OutputFileFlags.Required
|
||||
| OutputFileFlags.Binary
|
||||
| OutputFileFlags.Zippable,
|
||||
"pfi"),
|
||||
new("RawSS.bin", OutputFileFlags.Binary
|
||||
| OutputFileFlags.Zippable,
|
||||
"raw_ss"),
|
||||
new("SS.bin", OutputFileFlags.Required
|
||||
| OutputFileFlags.Binary
|
||||
| OutputFileFlags.Zippable,
|
||||
"ss"),
|
||||
];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
#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(Path.Combine(baseDir, "Log.txt")))
|
||||
return Path.Combine(baseDir, "Log.txt");
|
||||
|
||||
// Search for a renamed log file (assume there is only one)
|
||||
string[] files = Directory.GetFiles(baseDir, "*.txt", SearchOption.TopDirectoryOnly);
|
||||
foreach (string file in files)
|
||||
{
|
||||
if (IsSuccessfulLog(file))
|
||||
return file;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if Log file has a successful read in it
|
||||
/// </summary>
|
||||
/// <param name="log">Path to log file</param>
|
||||
/// <returns>True if successful log found, false otherwise</returns>
|
||||
private static bool IsSuccessfulLog(string log)
|
||||
{
|
||||
if (!File.Exists(log))
|
||||
return false;
|
||||
|
||||
// Successful Example:
|
||||
// Read completed in 00:50:23
|
||||
// Failed Example:
|
||||
// Read failed
|
||||
|
||||
try
|
||||
{
|
||||
// If Version is not found, not a valid log file
|
||||
if (string.IsNullOrEmpty(GetVersion(log)))
|
||||
return false;
|
||||
|
||||
// Look for " Read completed in " in log file
|
||||
using var sr = File.OpenText(log);
|
||||
while (!sr.EndOfStream)
|
||||
{
|
||||
string? line = sr.ReadLine();
|
||||
if (line?.StartsWith(" Read completed in ") == true)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// We couldn't find a successful dump
|
||||
return false;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// We don't care what the exception is right now
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the XBC version if possible
|
||||
/// </summary>
|
||||
/// <param name="log">Path to XBC log file</param>
|
||||
/// <returns>Version if possible, null on error</returns>
|
||||
private static string? GetVersion(string? log)
|
||||
{
|
||||
if (string.IsNullOrEmpty(log) || !File.Exists(log))
|
||||
return null;
|
||||
|
||||
// Sample:
|
||||
// ====================================================================
|
||||
// Xbox Backup Creator v2.9 Build:0425 By Redline99
|
||||
//
|
||||
|
||||
try
|
||||
{
|
||||
// Assume version is appended after first mention of Xbox Backup Creator
|
||||
using var sr = File.OpenText(log);
|
||||
while (!sr.EndOfStream)
|
||||
{
|
||||
string? line = sr.ReadLine()?.Trim();
|
||||
if (line?.StartsWith("Xbox Backup Creator ") == true)
|
||||
return line.Substring("Xbox Backup Creator ".Length).Trim();
|
||||
}
|
||||
|
||||
// We couldn't detect the version
|
||||
return null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// We don't care what the exception is right now
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the drive model from the log
|
||||
/// </summary>
|
||||
/// <param name="log">Path to XBC log file</param>
|
||||
/// <returns>Drive model if found, null otherwise</returns>
|
||||
private static string? GetDrive(string? log)
|
||||
{
|
||||
if (string.IsNullOrEmpty(log) || !File.Exists(log))
|
||||
return null;
|
||||
|
||||
// Example:
|
||||
// ========================================
|
||||
// < --Security Sector Details -->
|
||||
// Source Drive: SH-D162D
|
||||
// ----------------------------------------
|
||||
|
||||
try
|
||||
{
|
||||
// Parse drive model from log file
|
||||
using var sr = File.OpenText(log);
|
||||
while (!sr.EndOfStream)
|
||||
{
|
||||
string? line = sr.ReadLine()?.Trim();
|
||||
if (line?.StartsWith("Source Drive: ") == true)
|
||||
{
|
||||
return line.Substring("Source Drive: ".Length).Trim();
|
||||
}
|
||||
}
|
||||
|
||||
// We couldn't detect the drive model
|
||||
return null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// We don't care what the exception is right now
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the Layerbreak value if possible
|
||||
/// </summary>
|
||||
/// <param name="dvd">Path to layerbreak file</param>
|
||||
/// <param name="layerbreak">Layerbreak value if found</param>
|
||||
/// <returns>True if successful, otherwise false</returns>
|
||||
/// <returns></returns>
|
||||
private static bool GetLayerbreak(string? dvd, out long layerbreak)
|
||||
{
|
||||
layerbreak = 0;
|
||||
|
||||
if (string.IsNullOrEmpty(dvd) || !File.Exists(dvd))
|
||||
return false;
|
||||
|
||||
// Example:
|
||||
// LayerBreak=1913776
|
||||
// track.iso
|
||||
|
||||
try
|
||||
{
|
||||
// Parse Layerbreak value from DVD file
|
||||
using var sr = File.OpenText(dvd);
|
||||
while (!sr.EndOfStream)
|
||||
{
|
||||
string? line = sr.ReadLine()?.Trim();
|
||||
if (line?.StartsWith("LayerBreak=") == true)
|
||||
{
|
||||
return long.TryParse(line.Substring("LayerBreak=".Length).Trim(), out layerbreak);
|
||||
}
|
||||
}
|
||||
|
||||
// We couldn't detect the Layerbreak
|
||||
return false;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// We don't care what the exception is right now
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the read error count if possible
|
||||
/// </summary>
|
||||
/// <param name="log">Path to XBC log file</param>
|
||||
/// <param name="readErrors">Read error count if found, -1 otherwise</param>
|
||||
/// <returns>True if sucessful, otherwise false</returns>
|
||||
private bool GetReadErrors(string? log, out long readErrors)
|
||||
{
|
||||
readErrors = -1;
|
||||
|
||||
if (string.IsNullOrEmpty(log) || !File.Exists(log))
|
||||
return false;
|
||||
|
||||
// TODO: Logic when more than one dump is in the logs
|
||||
|
||||
// Example: (replace [E] with drive letter)
|
||||
// Creating SplitVid backup image [E]
|
||||
// ...
|
||||
// Reading Game Partition
|
||||
// Setting read speed to 1x
|
||||
// Unrecovered read error at Partition LBA: 0
|
||||
|
||||
// Example: (replace track with base filename)
|
||||
// Creating Layer Break File
|
||||
// LayerBreak file saved as: "track.dvd"
|
||||
// A total of 1 sectors were zeroed out.
|
||||
|
||||
// Example: (for Original Xbox)
|
||||
// A total of 65,536 sectors were zeroed out.
|
||||
// A total of 31 sectors with read errors were recovered.
|
||||
|
||||
try
|
||||
{
|
||||
// Parse Layerbreak value from DVD file
|
||||
using var sr = File.OpenText(log);
|
||||
while (!sr.EndOfStream)
|
||||
{
|
||||
string? line = sr.ReadLine()?.Trim();
|
||||
if (line?.StartsWith("Creating Layer Break File") == true)
|
||||
{
|
||||
// Read error count is two lines below
|
||||
line = sr.ReadLine()?.Trim();
|
||||
line = sr.ReadLine()?.Trim();
|
||||
if (line?.StartsWith("A total of ") == true && line?.EndsWith(" sectors were zeroed out.") == true)
|
||||
{
|
||||
string? errorCount = line.Substring("A total of ".Length, line.Length - 36).Replace(",", "").Trim();
|
||||
bool success = long.TryParse(errorCount, out readErrors);
|
||||
|
||||
// Original Xbox should have 65536 read errors when dumping with XBC
|
||||
if (System == RedumpSystem.MicrosoftXbox)
|
||||
{
|
||||
if (readErrors == 65536)
|
||||
readErrors = 0;
|
||||
else if (readErrors > 65536)
|
||||
readErrors -= 65536;
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We couldn't detect the read error count
|
||||
return false;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// We don't care what the exception is right now
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get Xbox360 Media ID from XBC log file
|
||||
/// </summary>
|
||||
/// <param name="log">Path to XBC log file</param>
|
||||
/// <returns>Media ID if Log successfully parsed, null otherwise</returns>
|
||||
private string? GetMediaID(string? log)
|
||||
{
|
||||
if (string.IsNullOrEmpty(log) || !File.Exists(log))
|
||||
return null;
|
||||
|
||||
if (System == RedumpSystem.MicrosoftXbox)
|
||||
return null;
|
||||
|
||||
// Example:
|
||||
// ----------------------------------------
|
||||
// Media ID
|
||||
// A76B9983D170EFF8749A892BC-8B62A812
|
||||
// ----------------------------------------
|
||||
|
||||
try
|
||||
{
|
||||
// Parse Layerbreak value from DVD file
|
||||
using var sr = File.OpenText(log);
|
||||
while (!sr.EndOfStream)
|
||||
{
|
||||
string? line = sr.ReadLine()?.Trim();
|
||||
if (line?.StartsWith("Media ID") == true)
|
||||
{
|
||||
line = sr.ReadLine()?.Trim();
|
||||
return line?.Substring(25).Trim();
|
||||
}
|
||||
}
|
||||
|
||||
// We couldn't detect the Layerbreak
|
||||
return null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// We don't care what the exception is right now
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recreate an SS.bin file from XBC log and write it to a file
|
||||
/// </summary>
|
||||
/// <param name="log">Path to XBC log</param>
|
||||
/// <param name="cleanSS">Path to the clean SS file to read from</param>
|
||||
/// <param name="rawSS">Path to the raw SS file to write to</param>
|
||||
/// <returns>True if successful, false otherwise</returns>
|
||||
private static bool RecreateSS(string log, string cleanSS, string rawSS)
|
||||
{
|
||||
if (!File.Exists(log) || !File.Exists(cleanSS))
|
||||
return false;
|
||||
|
||||
byte[] ss = File.ReadAllBytes(cleanSS);
|
||||
if (ss.Length != 2048)
|
||||
return false;
|
||||
|
||||
if (!RecreateSS(log!, ss))
|
||||
return false;
|
||||
|
||||
File.WriteAllBytes(rawSS, ss);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recreate an SS.bin byte array from an XBC log.
|
||||
/// With help from https://github.com/hadzz/SS-Angle-Fixer/
|
||||
/// </summary>
|
||||
/// <param name="log">Path to XBC log</param>
|
||||
/// <param name="ss">Byte array of SS sector</param>
|
||||
/// <returns>True if successful, false otherwise</returns>
|
||||
private static bool RecreateSS(string log, byte[] ss)
|
||||
{
|
||||
// Log file must exist
|
||||
if (!File.Exists(log))
|
||||
return false;
|
||||
|
||||
// SS must be complete sector
|
||||
if (ss.Length != 2048)
|
||||
return false;
|
||||
|
||||
// Ignore XGD1 discs
|
||||
if (!ProcessingTool.GetXGDType(ss, out int xgdType))
|
||||
return false;
|
||||
if (xgdType == 0)
|
||||
return false;
|
||||
|
||||
// Don't recreate an already raw SS
|
||||
// (but do save to file, so return true)
|
||||
if (!ProcessingTool.IsCleanSS(ss))
|
||||
return true;
|
||||
|
||||
// Example replay table:
|
||||
/*
|
||||
----------------------------------------
|
||||
RT CID MOD DATA Drive Response
|
||||
-- -- -- ------------- -------------------
|
||||
01 14 00 033100 0340FF B7D8C32A B703590100
|
||||
03 BE 00 244530 24552F F4B9B528 BE46360500
|
||||
01 97 00 DBBAD0 DBCACF DD7787F4 484977ED00
|
||||
03 45 00 FCAF00 FCBEFF FB7A7773 AAB662FC00
|
||||
05 6B 00 033100 033E7F 0A31252A 0200000200
|
||||
07 46 00 244530 2452AF F8E77EBC 5B00005B00
|
||||
05 36 00 DBBAD0 DBC84F F5DFA735 B50000B500
|
||||
07 A1 00 FCAF00 FCBC7F 6B749DBF 0E01000E01
|
||||
E0 50 00 42F4E1 00B6F7 00000000 0000000000
|
||||
--------------------------------------------
|
||||
*/
|
||||
|
||||
try
|
||||
{
|
||||
// Parse Replay Table from log
|
||||
using var sr = File.OpenText(log);
|
||||
while (!sr.EndOfStream)
|
||||
{
|
||||
string? line = sr.ReadLine()?.Trim();
|
||||
if (line?.StartsWith("RT CID MOD DATA Drive Response") == true)
|
||||
{
|
||||
// Ignore next line
|
||||
line = sr.ReadLine()?.Trim();
|
||||
if (sr.EndOfStream)
|
||||
return false;
|
||||
|
||||
byte[][] responses = new byte[4][];
|
||||
|
||||
// Parse the nine rows from replay table
|
||||
for (int i = 0; i < 9; i++)
|
||||
{
|
||||
line = sr.ReadLine()?.Trim();
|
||||
// Validate line
|
||||
if (sr.EndOfStream || string.IsNullOrEmpty(line) || line!.Length < 44)
|
||||
return false;
|
||||
|
||||
// Save useful angle responses
|
||||
if (i >= 4 && i <= 7)
|
||||
{
|
||||
byte[]? angles = ProcessingTool.HexStringToByteArray(line!.Substring(34, 10));
|
||||
if (angles == null || angles.Length != 5)
|
||||
return false;
|
||||
responses[i - 4] = angles!;
|
||||
}
|
||||
}
|
||||
|
||||
int rtOffset = 0x204;
|
||||
if (xgdType == 3)
|
||||
rtOffset = 0x24;
|
||||
|
||||
// Replace angles
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
int offset = rtOffset + (9 * (i + 4));
|
||||
for (int j = 0; j < 5; j++)
|
||||
{
|
||||
// Ignore the middle byte
|
||||
if (j == 2)
|
||||
continue;
|
||||
|
||||
ss[offset + j] = responses[i][j];
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// We couldn't detect the replay table
|
||||
return false;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// We don't care what the exception is right now
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
270
MPF.Test/ExecutionContexts/DiscImageCreatorTests.cs
Normal file
270
MPF.Test/ExecutionContexts/DiscImageCreatorTests.cs
Normal file
@@ -0,0 +1,270 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using MPF.ExecutionContexts.DiscImageCreator;
|
||||
using MPF.Frontend;
|
||||
using SabreTools.RedumpLib.Data;
|
||||
using Xunit;
|
||||
|
||||
namespace MPF.Test.ExecutionContexts
|
||||
{
|
||||
public class DiscImageCreatorTests
|
||||
{
|
||||
#region Old Tests
|
||||
|
||||
[Theory]
|
||||
[InlineData(RedumpSystem.MicrosoftXbox, MediaType.CDROM, CommandStrings.CompactDisc)]
|
||||
[InlineData(RedumpSystem.MicrosoftXbox, MediaType.DVD, CommandStrings.XBOX)]
|
||||
[InlineData(RedumpSystem.MicrosoftXbox, MediaType.LaserDisc, null)]
|
||||
[InlineData(RedumpSystem.SonyPlayStation3, MediaType.BluRay, CommandStrings.BluRay)]
|
||||
[InlineData(RedumpSystem.AppleMacintosh, MediaType.FloppyDisk, CommandStrings.Floppy)]
|
||||
[InlineData(RedumpSystem.RawThrillsVarious, MediaType.GDROM, null)]
|
||||
public void ParametersFromSystemAndTypeTest(RedumpSystem? knownSystem, MediaType? mediaType, string? expected)
|
||||
{
|
||||
var options = new Options();
|
||||
var actual = new ExecutionContext(knownSystem, mediaType, "D:\\", "disc.bin", 16, options.Settings);
|
||||
Assert.Equal(expected, actual.BaseCommand);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(RedumpSystem.AppleMacintosh, MediaType.LaserDisc, null)] // Deliberately unsupported
|
||||
[InlineData(RedumpSystem.IBMPCcompatible, MediaType.CDROM, new string[] { FlagStrings.C2Opcode, FlagStrings.NoFixSubQSecuROM, FlagStrings.ScanFileProtect })]
|
||||
[InlineData(RedumpSystem.NintendoGameCube, MediaType.NintendoGameCubeGameDisc, new string[] { FlagStrings.Raw })]
|
||||
public void ParametersFromOptionsSpecialDefaultTest(RedumpSystem? knownSystem, MediaType? mediaType, string[]? expected)
|
||||
{
|
||||
var options = new Options();
|
||||
var actual = new ExecutionContext(knownSystem, mediaType, "D:\\", "disc.bin", 16, options.Settings);
|
||||
|
||||
var expectedSet = new HashSet<string>(expected ?? Array.Empty<string>());
|
||||
HashSet<string> actualSet = GenerateUsedKeys(actual);
|
||||
Assert.Equal(expectedSet, actualSet);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(RedumpSystem.SegaDreamcast, MediaType.GDROM, 1000, new string[] { FlagStrings.C2Opcode })]
|
||||
[InlineData(RedumpSystem.SegaDreamcast, MediaType.GDROM, -1, new string[] { FlagStrings.C2Opcode })]
|
||||
public void ParametersFromOptionsC2RereadTest(RedumpSystem? knownSystem, MediaType? mediaType, int rereadC2, string[] expected)
|
||||
{
|
||||
var options = new Options { DICRereadCount = rereadC2 };
|
||||
var actual = new ExecutionContext(knownSystem, mediaType, "D:\\", "disc.bin", 16, options.Settings);
|
||||
|
||||
var expectedSet = new HashSet<string>(expected ?? Array.Empty<string>());
|
||||
HashSet<string> actualSet = GenerateUsedKeys(actual);
|
||||
|
||||
Assert.Equal(expectedSet, actualSet);
|
||||
if (rereadC2 == -1 || !knownSystem.MediaTypes().Contains(mediaType))
|
||||
Assert.Null(actual.C2OpcodeValue[0]);
|
||||
else
|
||||
Assert.Equal(rereadC2, actual.C2OpcodeValue[0]);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(RedumpSystem.IBMPCcompatible, MediaType.DVD, 1000, new string[] { FlagStrings.DVDReread })]
|
||||
[InlineData(RedumpSystem.IBMPCcompatible, MediaType.DVD, -1, new string[] { FlagStrings.DVDReread })]
|
||||
[InlineData(RedumpSystem.IBMPCcompatible, MediaType.BluRay, 1000, new string[] { FlagStrings.DVDReread })]
|
||||
[InlineData(RedumpSystem.IBMPCcompatible, MediaType.BluRay, -1, new string[] { FlagStrings.DVDReread })]
|
||||
public void ParametersFromOptionsDVDRereadTest(RedumpSystem? knownSystem, MediaType? mediaType, int rereadDVDBD, string[] expected)
|
||||
{
|
||||
var options = new Options { DICDVDRereadCount = rereadDVDBD };
|
||||
var actual = new ExecutionContext(knownSystem, mediaType, "D:\\", "disc.bin", 16, options.Settings);
|
||||
|
||||
var expectedSet = new HashSet<string>(expected ?? Array.Empty<string>());
|
||||
HashSet<string> actualSet = GenerateUsedKeys(actual);
|
||||
|
||||
Assert.Equal(expectedSet, actualSet);
|
||||
if (rereadDVDBD == -1 || !knownSystem.MediaTypes().Contains(mediaType))
|
||||
Assert.Null(actual.DVDRereadValue);
|
||||
else
|
||||
Assert.Equal(rereadDVDBD, actual.DVDRereadValue);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(RedumpSystem.BDVideo, MediaType.BluRay, true, new string[] { FlagStrings.DVDReread })]
|
||||
[InlineData(RedumpSystem.BDVideo, MediaType.BluRay, false, new string[] { FlagStrings.DVDReread })]
|
||||
[InlineData(RedumpSystem.IBMPCcompatible, MediaType.CDROM, true, new string[] { FlagStrings.C2Opcode, FlagStrings.NoFixSubQSecuROM, FlagStrings.MultiSectorRead, FlagStrings.ScanFileProtect })]
|
||||
[InlineData(RedumpSystem.IBMPCcompatible, MediaType.CDROM, false, new string[] { FlagStrings.C2Opcode, FlagStrings.NoFixSubQSecuROM, FlagStrings.ScanFileProtect })]
|
||||
[InlineData(RedumpSystem.IBMPCcompatible, MediaType.DVD, true, new string[] { FlagStrings.DVDReread })]
|
||||
[InlineData(RedumpSystem.IBMPCcompatible, MediaType.DVD, false, new string[] { FlagStrings.DVDReread })]
|
||||
[InlineData(RedumpSystem.HDDVDVideo, MediaType.HDDVD, true, new string[] { FlagStrings.DVDReread })]
|
||||
[InlineData(RedumpSystem.HDDVDVideo, MediaType.HDDVD, false, new string[] { FlagStrings.DVDReread })]
|
||||
public void ParametersFromOptionsMultiSectorReadTest(RedumpSystem? knownSystem, MediaType? mediaType, bool multiSectorRead, string[] expected)
|
||||
{
|
||||
var options = new Options { DICMultiSectorRead = multiSectorRead };
|
||||
var actual = new ExecutionContext(knownSystem, mediaType, "D:\\", "disc.bin", 16, options.Settings);
|
||||
|
||||
var expectedSet = new HashSet<string>(expected ?? Array.Empty<string>());
|
||||
HashSet<string> actualSet = GenerateUsedKeys(actual);
|
||||
Assert.Equal(expectedSet, actualSet);
|
||||
if (expectedSet.Count != 1 && multiSectorRead)
|
||||
Assert.Equal(0, actual.MultiSectorReadValue);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(RedumpSystem.BDVideo, MediaType.BluRay, true, new string[] { FlagStrings.DVDReread })]
|
||||
[InlineData(RedumpSystem.BDVideo, MediaType.BluRay, false, new string[] { FlagStrings.DVDReread })]
|
||||
[InlineData(RedumpSystem.IBMPCcompatible, MediaType.CDROM, true, new string[] { FlagStrings.C2Opcode, FlagStrings.NoFixSubQSecuROM, FlagStrings.ScanFileProtect, FlagStrings.ScanSectorProtect, FlagStrings.SubchannelReadLevel })]
|
||||
[InlineData(RedumpSystem.IBMPCcompatible, MediaType.CDROM, false, new string[] { FlagStrings.C2Opcode, FlagStrings.NoFixSubQSecuROM, FlagStrings.ScanFileProtect })]
|
||||
[InlineData(RedumpSystem.IBMPCcompatible, MediaType.DVD, true, new string[] { FlagStrings.DVDReread, FlagStrings.ScanFileProtect })]
|
||||
[InlineData(RedumpSystem.IBMPCcompatible, MediaType.DVD, false, new string[] { FlagStrings.DVDReread })]
|
||||
[InlineData(RedumpSystem.HDDVDVideo, MediaType.HDDVD, true, new string[] { FlagStrings.DVDReread })]
|
||||
[InlineData(RedumpSystem.HDDVDVideo, MediaType.HDDVD, false, new string[] { FlagStrings.DVDReread })]
|
||||
public void ParametersFromOptionsParanoidModeTest(RedumpSystem? knownSystem, MediaType? mediaType, bool paranoidMode, string[] expected)
|
||||
{
|
||||
var options = new Options { DICParanoidMode = paranoidMode };
|
||||
var actual = new ExecutionContext(knownSystem, mediaType, "D:\\", "disc.bin", 16, options.Settings);
|
||||
|
||||
var expectedSet = new HashSet<string>(expected ?? Array.Empty<string>());
|
||||
HashSet<string> actualSet = GenerateUsedKeys(actual);
|
||||
Assert.Equal(expectedSet, actualSet);
|
||||
if (paranoidMode)
|
||||
{
|
||||
if (actualSet.Contains(FlagStrings.ScanSectorProtect))
|
||||
Assert.True(actual[FlagStrings.ScanSectorProtect]);
|
||||
|
||||
if (actualSet.Contains(FlagStrings.SubchannelReadLevel))
|
||||
{
|
||||
Assert.True(actual[FlagStrings.SubchannelReadLevel]);
|
||||
Assert.Equal(2, actual.SubchannelReadLevelValue);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (actualSet.Contains(FlagStrings.ScanSectorProtect))
|
||||
Assert.False(actual[FlagStrings.ScanSectorProtect]);
|
||||
|
||||
if (actualSet.Contains(FlagStrings.SubchannelReadLevel))
|
||||
Assert.False(actual[FlagStrings.SubchannelReadLevel]);
|
||||
|
||||
Assert.Null(actual.SubchannelReadLevelValue);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(null, false)]
|
||||
[InlineData("", false)]
|
||||
[InlineData("cd F test.bin 8 /c2 20", true)]
|
||||
[InlineData("fd A test.img", true)]
|
||||
[InlineData("dvd X super\\test.iso 8 /raw", true)]
|
||||
[InlineData("bd D longer\\path_test.iso 16", true)]
|
||||
[InlineData("stop D", true)]
|
||||
[InlineData("ls", false)]
|
||||
public void ValidateParametersTest(string? parameters, bool expected)
|
||||
{
|
||||
var actual = new ExecutionContext(parameters);
|
||||
Assert.Equal(expected, actual.IsValid());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(MediaType.CDROM, ".bin")]
|
||||
[InlineData(MediaType.DVD, ".iso")]
|
||||
[InlineData(MediaType.LaserDisc, ".raw")]
|
||||
[InlineData(MediaType.NintendoWiiUOpticalDisc, ".wud")]
|
||||
[InlineData(MediaType.FloppyDisk, ".img")]
|
||||
[InlineData(MediaType.Cassette, ".wav")]
|
||||
[InlineData(MediaType.NONE, null)]
|
||||
public void MediaTypeToExtensionTest(MediaType? mediaType, string? expected)
|
||||
{
|
||||
var actual = Converters.Extension(mediaType);
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(CommandStrings.Audio, MediaType.CDROM)]
|
||||
[InlineData(CommandStrings.BluRay, MediaType.BluRay)]
|
||||
[InlineData(CommandStrings.Close, null)]
|
||||
[InlineData(CommandStrings.CompactDisc, MediaType.CDROM)]
|
||||
[InlineData(CommandStrings.Data, MediaType.CDROM)]
|
||||
[InlineData(CommandStrings.DigitalVideoDisc, MediaType.DVD)]
|
||||
[InlineData(CommandStrings.Eject, null)]
|
||||
[InlineData(CommandStrings.Floppy, MediaType.FloppyDisk)]
|
||||
[InlineData(CommandStrings.GDROM, MediaType.GDROM)]
|
||||
[InlineData(CommandStrings.MDS, null)]
|
||||
[InlineData(CommandStrings.Reset, null)]
|
||||
[InlineData(CommandStrings.SACD, MediaType.CDROM)]
|
||||
[InlineData(CommandStrings.Start, null)]
|
||||
[InlineData(CommandStrings.Stop, null)]
|
||||
[InlineData(CommandStrings.Sub, null)]
|
||||
[InlineData(CommandStrings.Swap, MediaType.GDROM)]
|
||||
[InlineData(CommandStrings.XBOX, MediaType.DVD)]
|
||||
public void BaseCommandToMediaTypeTest(string command, MediaType? expected)
|
||||
{
|
||||
MediaType? actual = Converters.ToMediaType(command);
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(CommandStrings.Audio, RedumpSystem.AudioCD)]
|
||||
[InlineData(CommandStrings.BluRay, RedumpSystem.SonyPlayStation3)]
|
||||
[InlineData(CommandStrings.Close, null)]
|
||||
[InlineData(CommandStrings.CompactDisc, RedumpSystem.IBMPCcompatible)]
|
||||
[InlineData(CommandStrings.Data, RedumpSystem.IBMPCcompatible)]
|
||||
[InlineData(CommandStrings.DigitalVideoDisc, RedumpSystem.IBMPCcompatible)]
|
||||
[InlineData(CommandStrings.Eject, null)]
|
||||
[InlineData(CommandStrings.Floppy, RedumpSystem.IBMPCcompatible)]
|
||||
[InlineData(CommandStrings.GDROM, RedumpSystem.SegaDreamcast)]
|
||||
[InlineData(CommandStrings.MDS, null)]
|
||||
[InlineData(CommandStrings.Reset, null)]
|
||||
[InlineData(CommandStrings.SACD, RedumpSystem.SuperAudioCD)]
|
||||
[InlineData(CommandStrings.Start, null)]
|
||||
[InlineData(CommandStrings.Stop, null)]
|
||||
[InlineData(CommandStrings.Sub, null)]
|
||||
[InlineData(CommandStrings.Swap, RedumpSystem.SegaDreamcast)]
|
||||
[InlineData(CommandStrings.XBOX, RedumpSystem.MicrosoftXbox)]
|
||||
public void BaseCommandToRedumpSystemTest(string command, RedumpSystem? expected)
|
||||
{
|
||||
RedumpSystem? actual = Converters.ToRedumpSystem(command);
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
[Fact]
|
||||
public void DiscImageCreatorAudioParametersTest()
|
||||
{
|
||||
string originalParameters = "audio F \"ISO\\Audio CD\\Audio CD.bin\" 72 -5 0";
|
||||
|
||||
// Validate that a common audio commandline is parsed
|
||||
var executionContext = new ExecutionContext(originalParameters);
|
||||
Assert.NotNull(executionContext);
|
||||
|
||||
// Validate that the same set of parameters are generated on the output
|
||||
var newParameters = executionContext.GenerateParameters();
|
||||
Assert.NotNull(newParameters);
|
||||
Assert.Equal(originalParameters, newParameters);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DiscImageCreatorDataParametersTest()
|
||||
{
|
||||
string originalParameters = "data F \"ISO\\Data CD\\Data CD.bin\" 72 -5 0";
|
||||
|
||||
// Validate that a common audio commandline is parsed
|
||||
var executionContext = new ExecutionContext(originalParameters);
|
||||
Assert.NotNull(executionContext);
|
||||
|
||||
// Validate that the same set of parameters are generated on the output
|
||||
var newParameters = executionContext.GenerateParameters();
|
||||
Assert.NotNull(newParameters);
|
||||
Assert.Equal(originalParameters, newParameters);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a HashSet of keys that are considered to be set
|
||||
/// </summary>
|
||||
/// <param name="executionContext">ExecutionContext object representing how to invoke the internal program</param>
|
||||
/// <returns>HashSet representing the strings</returns>
|
||||
private static HashSet<string> GenerateUsedKeys(ExecutionContext executionContext)
|
||||
{
|
||||
var usedKeys = new HashSet<string>();
|
||||
if (executionContext?.Keys == null)
|
||||
return usedKeys;
|
||||
|
||||
foreach (string key in executionContext.Keys)
|
||||
{
|
||||
if (executionContext[key] == true)
|
||||
usedKeys.Add(key);
|
||||
}
|
||||
|
||||
return usedKeys;
|
||||
}
|
||||
}
|
||||
}
|
||||
21
MPF.Test/Frontend/AllowedSpeedsTest.cs
Normal file
21
MPF.Test/Frontend/AllowedSpeedsTest.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using MPF.Frontend;
|
||||
using SabreTools.RedumpLib.Data;
|
||||
using Xunit;
|
||||
|
||||
namespace MPF.Test.Frontend
|
||||
{
|
||||
public class UIElementsTest
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(MediaType.CDROM, 72)]
|
||||
[InlineData(MediaType.DVD, 24)]
|
||||
[InlineData(MediaType.BluRay, 16)]
|
||||
[InlineData(MediaType.LaserDisc, 1)]
|
||||
[InlineData(null, 1)]
|
||||
public void GetAllowedDriveSpeedForMediaTypeTest(MediaType? mediaType, int maxExpected)
|
||||
{
|
||||
var actual = InterfaceConstants.GetSpeedsForMediaType(mediaType);
|
||||
Assert.Equal(maxExpected, actual[actual.Count - 1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
31
MPF.Test/Frontend/DumpEnvironmentTests.cs
Normal file
31
MPF.Test/Frontend/DumpEnvironmentTests.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using MPF.Frontend;
|
||||
using SabreTools.RedumpLib.Data;
|
||||
using Xunit;
|
||||
|
||||
namespace MPF.Test.Frontend
|
||||
{
|
||||
public class DumpEnvironmentTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(null, 'D', false, MediaType.NONE, false)]
|
||||
[InlineData("", 'D', false, MediaType.NONE, false)]
|
||||
[InlineData("cd F test.bin 8 /c2 20", 'F', false, MediaType.CDROM, true)]
|
||||
[InlineData("fd A test.img", 'A', true, MediaType.FloppyDisk, true)]
|
||||
[InlineData("dvd X test.iso 8 /raw", 'X', false, MediaType.FloppyDisk, false)]
|
||||
[InlineData("stop D", 'D', false, MediaType.DVD, true)]
|
||||
public void ParametersValidTest(string? parameters, char letter, bool isFloppy, MediaType? mediaType, bool expected)
|
||||
{
|
||||
var options = new Options() { InternalProgram = InternalProgram.DiscImageCreator };
|
||||
|
||||
// TODO: This relies on creating real objects for the drive. Can we mock this out instead?
|
||||
var drive = isFloppy
|
||||
? Drive.Create(InternalDriveType.Floppy, letter.ToString())
|
||||
: Drive.Create(InternalDriveType.Optical, letter.ToString());
|
||||
|
||||
var env = new DumpEnvironment(options, string.Empty, drive, RedumpSystem.IBMPCcompatible, mediaType, null, parameters);
|
||||
|
||||
bool actual = env.ParametersValid();
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
}
|
||||
}
|
||||
96
MPF.Test/Frontend/EnumConverterTests.cs
Normal file
96
MPF.Test/Frontend/EnumConverterTests.cs
Normal file
@@ -0,0 +1,96 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using MPF.Frontend;
|
||||
using Xunit;
|
||||
|
||||
namespace MPF.Test.Frontend
|
||||
{
|
||||
public class EnumConverterTests
|
||||
{
|
||||
#region Cross-enumeration conversions
|
||||
|
||||
/// <summary>
|
||||
/// DiscType values that map to InternalDriveType
|
||||
/// </summary>
|
||||
private static readonly DriveType[] _mappableDriveTypes =
|
||||
[
|
||||
DriveType.CDRom,
|
||||
DriveType.Fixed,
|
||||
DriveType.Removable,
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// Check that every supported DriveType maps to an InternalDriveType
|
||||
/// </summary>
|
||||
/// <param name="driveType">DriveType value to check</param>
|
||||
/// <param name="expectNull">True to expect a null mapping, false otherwise</param>
|
||||
[Theory]
|
||||
[MemberData(nameof(GenerateDriveTypeMappingTestData))]
|
||||
public void ToInternalDriveTypeTest(DriveType driveType, bool expectNull)
|
||||
{
|
||||
var actual = Drive.ToInternalDriveType(driveType);
|
||||
|
||||
if (expectNull)
|
||||
Assert.Null(actual);
|
||||
else
|
||||
Assert.NotNull(actual);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a test set of DriveType values
|
||||
/// </summary>
|
||||
/// <returns>MemberData-compatible list of DriveType values</returns>
|
||||
public static List<object?[]> GenerateDriveTypeMappingTestData()
|
||||
{
|
||||
var testData = new List<object?[]>() { new object?[] { null, true } };
|
||||
foreach (DriveType driveType in Enum.GetValues(typeof(DriveType)))
|
||||
{
|
||||
if (_mappableDriveTypes.Contains(driveType))
|
||||
testData.Add([driveType, false]);
|
||||
else
|
||||
testData.Add([driveType, true]);
|
||||
}
|
||||
|
||||
return testData;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Convert to Long Name
|
||||
|
||||
// TODO: Maybe add a test for the generic "GetLongName" method
|
||||
|
||||
/// <summary>
|
||||
/// Check that every InternalProgram has a long name provided
|
||||
/// </summary>
|
||||
/// <param name="internalProgram">InternalProgram value to check</param>
|
||||
[Theory]
|
||||
[MemberData(nameof(GenerateInternalProgramTestData))]
|
||||
public void InternalProgramLongNameTest(InternalProgram? internalProgram)
|
||||
{
|
||||
string actual = internalProgram.LongName();
|
||||
Assert.NotNull(actual);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a test set of InternalProgram values
|
||||
/// </summary>
|
||||
/// <returns>MemberData-compatible list of InternalProgram values</returns>
|
||||
public static List<object?[]> GenerateInternalProgramTestData()
|
||||
{
|
||||
var testData = new List<object?[]>() { new object?[] { null } };
|
||||
foreach (InternalProgram? internalProgram in Enum.GetValues(typeof(InternalProgram)))
|
||||
{
|
||||
testData.Add([internalProgram]);
|
||||
}
|
||||
|
||||
return testData;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
// TODO: Add from-string tests
|
||||
}
|
||||
}
|
||||
42
MPF.Test/Frontend/ResultEventArgsTests.cs
Normal file
42
MPF.Test/Frontend/ResultEventArgsTests.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using MPF.Frontend;
|
||||
using Xunit;
|
||||
|
||||
namespace MPF.Test.Frontend
|
||||
{
|
||||
public class ResultEventArgsTests
|
||||
{
|
||||
[Fact]
|
||||
public void EmptySuccessTest()
|
||||
{
|
||||
var actual = ResultEventArgs.Success();
|
||||
Assert.True(actual);
|
||||
Assert.Empty(actual.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CustomMessageSuccessTest()
|
||||
{
|
||||
string message = "Success!";
|
||||
var actual = ResultEventArgs.Success(message);
|
||||
Assert.True(actual);
|
||||
Assert.Equal(message, actual.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EmptyFailureTest()
|
||||
{
|
||||
var actual = ResultEventArgs.Failure();
|
||||
Assert.False(actual);
|
||||
Assert.Empty(actual.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CustomMessageFailureTest()
|
||||
{
|
||||
string message = "Failure!";
|
||||
var actual = ResultEventArgs.Failure(message);
|
||||
Assert.False(actual);
|
||||
Assert.Equal(message, actual.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
160
MPF.Test/Frontend/Tools/InfoToolTests.cs
Normal file
160
MPF.Test/Frontend/Tools/InfoToolTests.cs
Normal file
@@ -0,0 +1,160 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using MPF.Frontend.Tools;
|
||||
using SabreTools.RedumpLib;
|
||||
using SabreTools.RedumpLib.Data;
|
||||
using Xunit;
|
||||
|
||||
namespace MPF.Test.Frontend.Tools
|
||||
{
|
||||
public class InfoToolTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(null, "")]
|
||||
[InlineData(" ", " ")]
|
||||
[InlineData("super\\blah.bin", "super\\blah.bin")]
|
||||
[InlineData("super\\hero\\blah.bin", "super\\hero\\blah.bin")]
|
||||
[InlineData("super.hero\\blah.bin", "super.hero\\blah.bin")]
|
||||
[InlineData("superhero\\blah.rev.bin", "superhero\\blah.rev.bin")]
|
||||
[InlineData("super&hero\\blah.bin", "super&hero\\blah.bin")]
|
||||
[InlineData("superhero\\blah&foo.bin", "superhero\\blah&foo.bin")]
|
||||
public void NormalizeOutputPathsTest(string? outputPath, string? expectedPath)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(expectedPath))
|
||||
expectedPath = Path.GetFullPath(expectedPath);
|
||||
|
||||
string actualPath = FrontendTool.NormalizeOutputPaths(outputPath, true);
|
||||
Assert.Equal(expectedPath, actualPath);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ProcessSpecialFieldsCompleteTest()
|
||||
{
|
||||
// Create a new SubmissionInfo object
|
||||
var info = new SubmissionInfo()
|
||||
{
|
||||
CommonDiscInfo = new CommonDiscInfoSection()
|
||||
{
|
||||
Comments = "This is a comments line\n[T:ISBN] ISBN Value",
|
||||
CommentsSpecialFields = new Dictionary<SiteCode, string>()
|
||||
{
|
||||
[SiteCode.VolumeLabel] = "VOLUME_LABEL",
|
||||
},
|
||||
|
||||
Contents = "This is a contents line\n[T:GF] Game Footage",
|
||||
ContentsSpecialFields = new Dictionary<SiteCode, string>()
|
||||
{
|
||||
[SiteCode.Patches] = "1.04 patch",
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
// Process the special fields
|
||||
Formatter.ProcessSpecialFields(info);
|
||||
|
||||
// Validate the basics
|
||||
Assert.NotNull(info.CommonDiscInfo.Comments);
|
||||
Assert.Null(info.CommonDiscInfo.CommentsSpecialFields);
|
||||
Assert.NotNull(info.CommonDiscInfo.Contents);
|
||||
Assert.Null(info.CommonDiscInfo.ContentsSpecialFields);
|
||||
|
||||
// Split the values
|
||||
string[] splitComments = info.CommonDiscInfo.Comments.Split('\n');
|
||||
string[] splitContents = info.CommonDiscInfo.Contents.Split('\n');
|
||||
|
||||
// Validate the lines
|
||||
Assert.Equal(3, splitComments.Length);
|
||||
Assert.Equal(5, splitContents.Length);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ProcessSpecialFieldsNullObjectTest()
|
||||
{
|
||||
// Create a new SubmissionInfo object
|
||||
var info = new SubmissionInfo()
|
||||
{
|
||||
CommonDiscInfo = null,
|
||||
};
|
||||
|
||||
// Process the special fields
|
||||
Formatter.ProcessSpecialFields(info);
|
||||
|
||||
// Validate
|
||||
Assert.Null(info.CommonDiscInfo);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ProcessSpecialFieldsNullCommentsContentsTest()
|
||||
{
|
||||
// Create a new SubmissionInfo object
|
||||
var info = new SubmissionInfo()
|
||||
{
|
||||
CommonDiscInfo = new CommonDiscInfoSection()
|
||||
{
|
||||
Comments = null,
|
||||
CommentsSpecialFields = new Dictionary<SiteCode, string>()
|
||||
{
|
||||
[SiteCode.VolumeLabel] = "VOLUME_LABEL",
|
||||
},
|
||||
|
||||
Contents = null,
|
||||
ContentsSpecialFields = new Dictionary<SiteCode, string>()
|
||||
{
|
||||
[SiteCode.Patches] = "1.04 patch",
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
// Process the special fields
|
||||
Formatter.ProcessSpecialFields(info);
|
||||
|
||||
// Validate the basics
|
||||
Assert.NotNull(info.CommonDiscInfo.Comments);
|
||||
Assert.Null(info.CommonDiscInfo.CommentsSpecialFields);
|
||||
Assert.NotNull(info.CommonDiscInfo.Contents);
|
||||
Assert.Null(info.CommonDiscInfo.ContentsSpecialFields);
|
||||
|
||||
// Split the values
|
||||
string[] splitComments = info.CommonDiscInfo.Comments.Split('\n');
|
||||
string[] splitContents = info.CommonDiscInfo.Contents.Split('\n');
|
||||
|
||||
// Validate the lines
|
||||
Assert.Single(splitComments);
|
||||
Assert.Equal(2, splitContents.Length);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ProcessSpecialFieldsNullDictionariesTest()
|
||||
{
|
||||
// Create a new SubmissionInfo object
|
||||
var info = new SubmissionInfo()
|
||||
{
|
||||
CommonDiscInfo = new CommonDiscInfoSection()
|
||||
{
|
||||
Comments = "This is a comments line\n[T:ISBN] ISBN Value",
|
||||
CommentsSpecialFields = null,
|
||||
|
||||
Contents = "This is a contents line\n[T:GF] Game Footage",
|
||||
ContentsSpecialFields = null,
|
||||
}
|
||||
};
|
||||
|
||||
// Process the special fields
|
||||
Formatter.ProcessSpecialFields(info);
|
||||
|
||||
// Validate the basics
|
||||
Assert.NotNull(info.CommonDiscInfo.Comments);
|
||||
Assert.Null(info.CommonDiscInfo.CommentsSpecialFields);
|
||||
Assert.NotNull(info.CommonDiscInfo.Contents);
|
||||
Assert.Null(info.CommonDiscInfo.ContentsSpecialFields);
|
||||
|
||||
// Split the values
|
||||
string[] splitComments = info.CommonDiscInfo.Comments.Split('\n');
|
||||
string[] splitContents = info.CommonDiscInfo.Contents.Split('\n');
|
||||
|
||||
// Validate the lines
|
||||
Assert.Equal(2, splitComments.Length);
|
||||
Assert.Equal(2, splitContents.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
234
MPF.Test/Frontend/Tools/ProtectionTests.cs
Normal file
234
MPF.Test/Frontend/Tools/ProtectionTests.cs
Normal file
@@ -0,0 +1,234 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using MPF.Frontend.Tools;
|
||||
using Xunit;
|
||||
|
||||
namespace MPF.Test.Frontend.Tools
|
||||
{
|
||||
public class ProtectionTests
|
||||
{
|
||||
[Fact]
|
||||
public void SanitizeFoundProtectionsActiveMARKTest()
|
||||
{
|
||||
List<string> protections =
|
||||
[
|
||||
"ActiveMARK",
|
||||
"ActiveMARK 5",
|
||||
];
|
||||
|
||||
string sanitized = ProtectionTool.SanitizeFoundProtections(protections);
|
||||
Assert.Equal("ActiveMARK 5", sanitized);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SanitizeFoundProtectionsCactusDataShieldTest()
|
||||
{
|
||||
List<string> protections =
|
||||
[
|
||||
"Cactus Data Shield 200",
|
||||
"Cactus Data Shield 200 (Build 3.0.100a)",
|
||||
];
|
||||
|
||||
string sanitized = ProtectionTool.SanitizeFoundProtections(protections);
|
||||
Assert.Equal("Cactus Data Shield 200 (Build 3.0.100a)", sanitized);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SanitizeFoundProtectionsCDCheckTest()
|
||||
{
|
||||
List<string> protections =
|
||||
[
|
||||
"Anything Else Protection",
|
||||
"Executable-Based CD Check",
|
||||
];
|
||||
|
||||
string sanitized = ProtectionTool.SanitizeFoundProtections(protections);
|
||||
Assert.Equal("Anything Else Protection", sanitized);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SanitizeFoundProtectionsCDCopsTest()
|
||||
{
|
||||
List<string> protections =
|
||||
[
|
||||
"CD-Cops",
|
||||
"CD-Cops v1.2.0",
|
||||
];
|
||||
|
||||
string sanitized = ProtectionTool.SanitizeFoundProtections(protections);
|
||||
Assert.Equal("CD-Cops v1.2.0", sanitized);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SanitizeFoundProtectionsCDKeyTest()
|
||||
{
|
||||
List<string> protections =
|
||||
[
|
||||
"Anything Else Protection",
|
||||
"CD-Key / Serial",
|
||||
];
|
||||
|
||||
string sanitized = ProtectionTool.SanitizeFoundProtections(protections);
|
||||
Assert.Equal("Anything Else Protection", sanitized);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SanitizeFoundProtectionsEACdKeyTest()
|
||||
{
|
||||
List<string> protections =
|
||||
[
|
||||
"EA CdKey Registration Module",
|
||||
"EA CdKey Registration Module v1.2.0",
|
||||
];
|
||||
|
||||
string sanitized = ProtectionTool.SanitizeFoundProtections(protections);
|
||||
Assert.Equal("EA CdKey Registration Module v1.2.0", sanitized);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SanitizeFoundProtectionsEADRMTest()
|
||||
{
|
||||
List<string> protections =
|
||||
[
|
||||
"EA DRM Protection",
|
||||
"EA DRM Protection v1.2.0",
|
||||
];
|
||||
|
||||
string sanitized = ProtectionTool.SanitizeFoundProtections(protections);
|
||||
Assert.Equal("EA DRM Protection v1.2.0", sanitized);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SanitizeFoundProtectionsGFWLTest()
|
||||
{
|
||||
List<string> protections =
|
||||
[
|
||||
"Games for Windows LIVE",
|
||||
"Games for Windows LIVE v1.2.0",
|
||||
];
|
||||
|
||||
string sanitized = ProtectionTool.SanitizeFoundProtections(protections);
|
||||
Assert.Equal("Games for Windows LIVE v1.2.0", sanitized);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SanitizeFoundProtectionsGFWLZDPPTest()
|
||||
{
|
||||
List<string> protections =
|
||||
[
|
||||
"Games for Windows LIVE",
|
||||
"Games for Windows LIVE Zero Day Piracy Protection",
|
||||
];
|
||||
|
||||
string sanitized = ProtectionTool.SanitizeFoundProtections(protections);
|
||||
Assert.Equal("Games for Windows LIVE, Games for Windows LIVE Zero Day Piracy Protection", sanitized);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SanitizeFoundProtectionsImpulseReactorTest()
|
||||
{
|
||||
List<string> protections =
|
||||
[
|
||||
"Impulse Reactor",
|
||||
"Impulse Reactor Core Module v1.2.0",
|
||||
];
|
||||
|
||||
string sanitized = ProtectionTool.SanitizeFoundProtections(protections);
|
||||
Assert.Equal("Impulse Reactor Core Module v1.2.0", sanitized);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0)]
|
||||
[InlineData(1)]
|
||||
[InlineData(2)]
|
||||
[InlineData(3)]
|
||||
public void SanitizeFoundProtectionsJoWoodXProtTest(int skip)
|
||||
{
|
||||
List<string> protections =
|
||||
[
|
||||
"JoWood X-Prot 1.2.0.00",
|
||||
"JoWood X-Prot v2",
|
||||
"JoWood X-Prot v1.4+",
|
||||
"JoWood X-Prot v1.0-v1.3",
|
||||
"JoWood X-Prot",
|
||||
];
|
||||
|
||||
// Safeguard for the future
|
||||
if (skip >= protections.Count)
|
||||
throw new ArgumentException("Invalid skip value", nameof(skip));
|
||||
|
||||
// The list is in order of preference
|
||||
protections = protections.Skip(skip).ToList();
|
||||
|
||||
string sanitized = ProtectionTool.SanitizeFoundProtections(protections);
|
||||
Assert.Equal(protections[0], sanitized);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SanitizeFoundProtectionsOnlineRegistrationTest()
|
||||
{
|
||||
List<string> protections =
|
||||
[
|
||||
"Anything Else Protection",
|
||||
"Executable-Based Online Registration",
|
||||
];
|
||||
|
||||
string sanitized = ProtectionTool.SanitizeFoundProtections(protections);
|
||||
Assert.Equal("Anything Else Protection", sanitized);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0)]
|
||||
[InlineData(1)]
|
||||
[InlineData(2)]
|
||||
[InlineData(3)]
|
||||
public void SanitizeFoundProtectionStarForceTest(int skip)
|
||||
{
|
||||
List<string> protections =
|
||||
[
|
||||
"StarForce 1.20.000.000",
|
||||
"StarForce 5 [Protected Module]",
|
||||
"StarForce 5",
|
||||
"StarForce 3-5",
|
||||
"StarForce",
|
||||
];
|
||||
|
||||
// Safeguard for the future
|
||||
if (skip >= protections.Count)
|
||||
throw new ArgumentException("Invalid skip value", nameof(skip));
|
||||
|
||||
// The list is in order of preference
|
||||
protections = protections.Skip(skip).ToList();
|
||||
|
||||
string sanitized = ProtectionTool.SanitizeFoundProtections(protections);
|
||||
Assert.Equal(protections[0], sanitized);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SanitizeFoundProtectionsSysiphusTest()
|
||||
{
|
||||
List<string> protections =
|
||||
[
|
||||
"Sysiphus",
|
||||
"Sysiphus v1.2.0",
|
||||
];
|
||||
|
||||
string sanitized = ProtectionTool.SanitizeFoundProtections(protections);
|
||||
Assert.Equal("Sysiphus v1.2.0", sanitized);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SanitizeFoundProtectionsXCPTest()
|
||||
{
|
||||
List<string> protections =
|
||||
[
|
||||
"XCP",
|
||||
"XCP v1.2.0",
|
||||
];
|
||||
|
||||
string sanitized = ProtectionTool.SanitizeFoundProtections(protections);
|
||||
Assert.Equal("XCP v1.2.0", sanitized);
|
||||
}
|
||||
}
|
||||
}
|
||||
39
MPF.Test/MPF.Test.csproj
Normal file
39
MPF.Test/MPF.Test.csproj
Normal file
@@ -0,0 +1,39 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net6.0;net8.0</TargetFrameworks>
|
||||
<CheckEolTargetFramework>false</CheckEolTargetFramework>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MPF.ExecutionContexts\MPF.ExecutionContexts.csproj" />
|
||||
<ProjectReference Include="..\MPF.Frontend\MPF.Frontend.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeCoverage" Version="17.11.1" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="SabreTools.RedumpLib" Version="1.4.4" />
|
||||
<PackageReference Include="xunit" Version="2.9.2" />
|
||||
<PackageReference Include="xunit.abstractions" Version="2.0.3" />
|
||||
<PackageReference Include="xunit.analyzers" Version="1.16.0" />
|
||||
<PackageReference Include="xunit.assert" Version="2.9.2" />
|
||||
<PackageReference Include="xunit.core" Version="2.9.2" />
|
||||
<PackageReference Include="xunit.extensibility.core" Version="2.9.2" />
|
||||
<PackageReference Include="xunit.extensibility.execution" Version="2.9.2" />
|
||||
<PackageReference Include="xunit.runner.console" Version="2.9.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
583
MPF.UI/App.xaml
Normal file
583
MPF.UI/App.xaml
Normal file
@@ -0,0 +1,583 @@
|
||||
<Application
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:windows="clr-namespace:MPF.UI.Windows"
|
||||
x:Class="MPF.UI.App">
|
||||
<Application.MainWindow>
|
||||
<windows:MainWindow Visibility="Visible"/>
|
||||
</Application.MainWindow>
|
||||
|
||||
<Application.Resources>
|
||||
|
||||
<!-- Button -->
|
||||
<SolidColorBrush x:Key="Button.Static.Background" Color="#FFDDDDDD"/>
|
||||
<SolidColorBrush x:Key="Button.Static.Border" Color="#FF707070"/>
|
||||
<SolidColorBrush x:Key="Button.MouseOver.Background" Color="#FFBEE6FD"/>
|
||||
<SolidColorBrush x:Key="Button.MouseOver.Border" Color="#FF3C7FB1"/>
|
||||
<SolidColorBrush x:Key="Button.Pressed.Background" Color="#FFC4E5F6"/>
|
||||
<SolidColorBrush x:Key="Button.Pressed.Border" Color="#FF2C628B"/>
|
||||
<SolidColorBrush x:Key="Button.Disabled.Background" Color="#FFF4F4F4"/>
|
||||
<SolidColorBrush x:Key="Button.Disabled.Border" Color="#FFADB2B5"/>
|
||||
<SolidColorBrush x:Key="Button.Disabled.Foreground" Color="#FF838383"/>
|
||||
<Style x:Key="CustomButtonStyle" TargetType="{x:Type Button}">
|
||||
<Setter Property="FocusVisualStyle" Value="{DynamicResource FocusVisual}"/>
|
||||
<Setter Property="Background" Value="{DynamicResource Button.Static.Background}"/>
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource Button.Static.Border}"/>
|
||||
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Center"/>
|
||||
<Setter Property="VerticalContentAlignment" Value="Center"/>
|
||||
<Setter Property="Padding" Value="1"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type Button}">
|
||||
<Border x:Name="border" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" SnapsToDevicePixels="true">
|
||||
<ContentPresenter x:Name="contentPresenter" Focusable="False" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsDefaulted" Value="true">
|
||||
<Setter Property="BorderBrush" TargetName="border" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsMouseOver" Value="true">
|
||||
<Setter Property="Background" TargetName="border" Value="{DynamicResource Button.MouseOver.Background}"/>
|
||||
<Setter Property="BorderBrush" TargetName="border" Value="{DynamicResource Button.MouseOver.Border}"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsPressed" Value="true">
|
||||
<Setter Property="Background" TargetName="border" Value="{DynamicResource Button.Pressed.Background}"/>
|
||||
<Setter Property="BorderBrush" TargetName="border" Value="{DynamicResource Button.Pressed.Border}"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsEnabled" Value="false">
|
||||
<Setter Property="Background" TargetName="border" Value="{DynamicResource Button.Disabled.Background}"/>
|
||||
<Setter Property="BorderBrush" TargetName="border" Value="{DynamicResource Button.Disabled.Border}"/>
|
||||
<Setter Property="TextElement.Foreground" TargetName="contentPresenter" Value="{DynamicResource Button.Disabled.Foreground}"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<!-- ComboBox -->
|
||||
<Style x:Key="FocusVisual">
|
||||
<Setter Property="Control.Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate>
|
||||
<Rectangle Margin="2" SnapsToDevicePixels="true" Stroke="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" StrokeThickness="1" StrokeDashArray="1 2"/>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<LinearGradientBrush x:Key="ComboBox.Static.Background" EndPoint="0,1" StartPoint="0,0">
|
||||
<GradientStop Color="#FFF0F0F0" Offset="0.0"/>
|
||||
<GradientStop Color="#FFE5E5E5" Offset="1.0"/>
|
||||
</LinearGradientBrush>
|
||||
<SolidColorBrush x:Key="ComboBox.Static.Border" Color="#FFACACAC"/>
|
||||
<SolidColorBrush x:Key="ComboBox.Static.Editable.Background" Color="#FFFFFFFF"/>
|
||||
<SolidColorBrush x:Key="ComboBox.Static.Editable.Border" Color="#FFABADB3"/>
|
||||
<SolidColorBrush x:Key="ComboBox.Static.Editable.Button.Background" Color="Transparent"/>
|
||||
<SolidColorBrush x:Key="ComboBox.Static.Editable.Button.Border" Color="Transparent"/>
|
||||
<SolidColorBrush x:Key="ComboBox.MouseOver.Glyph" Color="#FF000000"/>
|
||||
<LinearGradientBrush x:Key="ComboBox.MouseOver.Background" EndPoint="0,1" StartPoint="0,0">
|
||||
<GradientStop Color="#FFECF4FC" Offset="0.0"/>
|
||||
<GradientStop Color="#FFDCECFC" Offset="1.0"/>
|
||||
</LinearGradientBrush>
|
||||
<SolidColorBrush x:Key="ComboBox.MouseOver.Border" Color="#FF7EB4EA"/>
|
||||
<SolidColorBrush x:Key="ComboBox.MouseOver.Editable.Background" Color="#FFFFFFFF"/>
|
||||
<SolidColorBrush x:Key="ComboBox.MouseOver.Editable.Border" Color="#FF7EB4EA"/>
|
||||
<LinearGradientBrush x:Key="ComboBox.MouseOver.Editable.Button.Background" EndPoint="0,1" StartPoint="0,0">
|
||||
<GradientStop Color="#FFEBF4FC" Offset="0.0"/>
|
||||
<GradientStop Color="#FFDCECFC" Offset="1.0"/>
|
||||
</LinearGradientBrush>
|
||||
<SolidColorBrush x:Key="ComboBox.MouseOver.Editable.Button.Border" Color="#FF7EB4EA"/>
|
||||
<SolidColorBrush x:Key="ComboBox.Pressed.Glyph" Color="#FF000000"/>
|
||||
<LinearGradientBrush x:Key="ComboBox.Pressed.Background" EndPoint="0,1" StartPoint="0,0">
|
||||
<GradientStop Color="#FFDAECFC" Offset="0.0"/>
|
||||
<GradientStop Color="#FFC4E0FC" Offset="1.0"/>
|
||||
</LinearGradientBrush>
|
||||
<SolidColorBrush x:Key="ComboBox.Pressed.Border" Color="#FF569DE5"/>
|
||||
<SolidColorBrush x:Key="ComboBox.Pressed.Editable.Background" Color="#FFFFFFFF"/>
|
||||
<SolidColorBrush x:Key="ComboBox.Pressed.Editable.Border" Color="#FF569DE5"/>
|
||||
<LinearGradientBrush x:Key="ComboBox.Pressed.Editable.Button.Background" EndPoint="0,1" StartPoint="0,0">
|
||||
<GradientStop Color="#FFDAEBFC" Offset="0.0"/>
|
||||
<GradientStop Color="#FFC4E0FC" Offset="1.0"/>
|
||||
</LinearGradientBrush>
|
||||
<SolidColorBrush x:Key="ComboBox.Pressed.Editable.Button.Border" Color="#FF569DE5"/>
|
||||
<SolidColorBrush x:Key="ComboBox.Disabled.Glyph" Color="#FFBFBFBF"/>
|
||||
<SolidColorBrush x:Key="ComboBox.Disabled.Background" Color="#FFF0F0F0"/>
|
||||
<SolidColorBrush x:Key="ComboBox.Disabled.Border" Color="#FFD9D9D9"/>
|
||||
<SolidColorBrush x:Key="ComboBox.Disabled.Editable.Background" Color="#FFFFFFFF"/>
|
||||
<SolidColorBrush x:Key="ComboBox.Disabled.Editable.Border" Color="#FFBFBFBF"/>
|
||||
<SolidColorBrush x:Key="ComboBox.Disabled.Editable.Button.Background" Color="Transparent"/>
|
||||
<SolidColorBrush x:Key="ComboBox.Disabled.Editable.Button.Border" Color="Transparent"/>
|
||||
<SolidColorBrush x:Key="ComboBox.Static.Glyph" Color="#FF606060"/>
|
||||
<Style x:Key="ComboBoxToggleButton" TargetType="{x:Type ToggleButton}">
|
||||
<Setter Property="OverridesDefaultStyle" Value="true"/>
|
||||
<Setter Property="IsTabStop" Value="false"/>
|
||||
<Setter Property="Focusable" Value="false"/>
|
||||
<Setter Property="ClickMode" Value="Press"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type ToggleButton}">
|
||||
<Border x:Name="templateRoot" BorderBrush="{DynamicResource ComboBox.Static.Border}" BorderThickness="{TemplateBinding BorderThickness}" Background="{DynamicResource ComboBox.Static.Background}" SnapsToDevicePixels="true">
|
||||
<Border x:Name="splitBorder" BorderBrush="Transparent" BorderThickness="1" HorizontalAlignment="Right" Margin="0" SnapsToDevicePixels="true" Width="{DynamicResource {x:Static SystemParameters.VerticalScrollBarWidthKey}}">
|
||||
<Path x:Name="arrow" Data="F1 M 0,0 L 2.667,2.66665 L 5.3334,0 L 5.3334,-1.78168 L 2.6667,0.88501 L0,-1.78168 L0,0 Z" Fill="{DynamicResource ComboBox.Static.Glyph}" HorizontalAlignment="Center" Margin="0" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<MultiDataTrigger>
|
||||
<MultiDataTrigger.Conditions>
|
||||
<Condition Binding="{Binding IsEditable, RelativeSource={RelativeSource AncestorType={x:Type ComboBox}}}" Value="true"/>
|
||||
<Condition Binding="{Binding IsMouseOver, RelativeSource={RelativeSource Self}}" Value="false"/>
|
||||
<Condition Binding="{Binding IsPressed, RelativeSource={RelativeSource Self}}" Value="false"/>
|
||||
<Condition Binding="{Binding IsEnabled, RelativeSource={RelativeSource Self}}" Value="true"/>
|
||||
</MultiDataTrigger.Conditions>
|
||||
<Setter Property="Background" TargetName="templateRoot" Value="{DynamicResource ComboBox.Static.Editable.Background}"/>
|
||||
<Setter Property="BorderBrush" TargetName="templateRoot" Value="{DynamicResource ComboBox.Static.Editable.Border}"/>
|
||||
<Setter Property="Background" TargetName="splitBorder" Value="{DynamicResource ComboBox.Static.Editable.Button.Background}"/>
|
||||
<Setter Property="BorderBrush" TargetName="splitBorder" Value="{DynamicResource ComboBox.Static.Editable.Button.Border}"/>
|
||||
</MultiDataTrigger>
|
||||
<Trigger Property="IsMouseOver" Value="true">
|
||||
<Setter Property="Fill" TargetName="arrow" Value="{DynamicResource ComboBox.MouseOver.Glyph}"/>
|
||||
</Trigger>
|
||||
<MultiDataTrigger>
|
||||
<MultiDataTrigger.Conditions>
|
||||
<Condition Binding="{Binding IsMouseOver, RelativeSource={RelativeSource Self}}" Value="true"/>
|
||||
<Condition Binding="{Binding IsEditable, RelativeSource={RelativeSource AncestorType={x:Type ComboBox}}}" Value="false"/>
|
||||
</MultiDataTrigger.Conditions>
|
||||
<Setter Property="Background" TargetName="templateRoot" Value="{DynamicResource ComboBox.MouseOver.Background}"/>
|
||||
<Setter Property="BorderBrush" TargetName="templateRoot" Value="{DynamicResource ComboBox.MouseOver.Border}"/>
|
||||
</MultiDataTrigger>
|
||||
<MultiDataTrigger>
|
||||
<MultiDataTrigger.Conditions>
|
||||
<Condition Binding="{Binding IsMouseOver, RelativeSource={RelativeSource Self}}" Value="true"/>
|
||||
<Condition Binding="{Binding IsEditable, RelativeSource={RelativeSource AncestorType={x:Type ComboBox}}}" Value="true"/>
|
||||
</MultiDataTrigger.Conditions>
|
||||
<Setter Property="Background" TargetName="templateRoot" Value="{DynamicResource ComboBox.MouseOver.Editable.Background}"/>
|
||||
<Setter Property="BorderBrush" TargetName="templateRoot" Value="{DynamicResource ComboBox.MouseOver.Editable.Border}"/>
|
||||
<Setter Property="Background" TargetName="splitBorder" Value="{DynamicResource ComboBox.MouseOver.Editable.Button.Background}"/>
|
||||
<Setter Property="BorderBrush" TargetName="splitBorder" Value="{DynamicResource ComboBox.MouseOver.Editable.Button.Border}"/>
|
||||
</MultiDataTrigger>
|
||||
<Trigger Property="IsPressed" Value="true">
|
||||
<Setter Property="Fill" TargetName="arrow" Value="{DynamicResource ComboBox.Pressed.Glyph}"/>
|
||||
</Trigger>
|
||||
<MultiDataTrigger>
|
||||
<MultiDataTrigger.Conditions>
|
||||
<Condition Binding="{Binding IsPressed, RelativeSource={RelativeSource Self}}" Value="true"/>
|
||||
<Condition Binding="{Binding IsEditable, RelativeSource={RelativeSource AncestorType={x:Type ComboBox}}}" Value="false"/>
|
||||
</MultiDataTrigger.Conditions>
|
||||
<Setter Property="Background" TargetName="templateRoot" Value="{DynamicResource ComboBox.Pressed.Background}"/>
|
||||
<Setter Property="BorderBrush" TargetName="templateRoot" Value="{DynamicResource ComboBox.Pressed.Border}"/>
|
||||
</MultiDataTrigger>
|
||||
<MultiDataTrigger>
|
||||
<MultiDataTrigger.Conditions>
|
||||
<Condition Binding="{Binding IsPressed, RelativeSource={RelativeSource Self}}" Value="true"/>
|
||||
<Condition Binding="{Binding IsEditable, RelativeSource={RelativeSource AncestorType={x:Type ComboBox}}}" Value="true"/>
|
||||
</MultiDataTrigger.Conditions>
|
||||
<Setter Property="Background" TargetName="templateRoot" Value="{DynamicResource ComboBox.Pressed.Editable.Background}"/>
|
||||
<Setter Property="BorderBrush" TargetName="templateRoot" Value="{DynamicResource ComboBox.Pressed.Editable.Border}"/>
|
||||
<Setter Property="Background" TargetName="splitBorder" Value="{DynamicResource ComboBox.Pressed.Editable.Button.Background}"/>
|
||||
<Setter Property="BorderBrush" TargetName="splitBorder" Value="{DynamicResource ComboBox.Pressed.Editable.Button.Border}"/>
|
||||
</MultiDataTrigger>
|
||||
<Trigger Property="IsEnabled" Value="false">
|
||||
<Setter Property="Fill" TargetName="arrow" Value="{DynamicResource ComboBox.Disabled.Glyph}"/>
|
||||
</Trigger>
|
||||
<MultiDataTrigger>
|
||||
<MultiDataTrigger.Conditions>
|
||||
<Condition Binding="{Binding IsEnabled, RelativeSource={RelativeSource Self}}" Value="false"/>
|
||||
<Condition Binding="{Binding IsEditable, RelativeSource={RelativeSource AncestorType={x:Type ComboBox}}}" Value="false"/>
|
||||
</MultiDataTrigger.Conditions>
|
||||
<Setter Property="Background" TargetName="templateRoot" Value="{DynamicResource ComboBox.Disabled.Background}"/>
|
||||
<Setter Property="BorderBrush" TargetName="templateRoot" Value="{DynamicResource ComboBox.Disabled.Border}"/>
|
||||
</MultiDataTrigger>
|
||||
<MultiDataTrigger>
|
||||
<MultiDataTrigger.Conditions>
|
||||
<Condition Binding="{Binding IsEnabled, RelativeSource={RelativeSource Self}}" Value="false"/>
|
||||
<Condition Binding="{Binding IsEditable, RelativeSource={RelativeSource AncestorType={x:Type ComboBox}}}" Value="true"/>
|
||||
</MultiDataTrigger.Conditions>
|
||||
<Setter Property="Background" TargetName="templateRoot" Value="{DynamicResource ComboBox.Disabled.Editable.Background}"/>
|
||||
<Setter Property="BorderBrush" TargetName="templateRoot" Value="{DynamicResource ComboBox.Disabled.Editable.Border}"/>
|
||||
<Setter Property="Background" TargetName="splitBorder" Value="{DynamicResource ComboBox.Disabled.Editable.Button.Background}"/>
|
||||
<Setter Property="BorderBrush" TargetName="splitBorder" Value="{DynamicResource ComboBox.Disabled.Editable.Button.Border}"/>
|
||||
</MultiDataTrigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<SolidColorBrush x:Key="TextBox.Static.Background" Color="#FFFFFFFF"/>
|
||||
|
||||
<!-- ContextMenu -->
|
||||
<SolidColorBrush x:Key="ContextMenu.Static.Border" Color="#FF888888"/>
|
||||
<Style x:Key="CustomContextMenuStyle" TargetType="{x:Type ContextMenu}">
|
||||
<Setter Property="SnapsToDevicePixels" Value="True" />
|
||||
<Setter Property="OverridesDefaultStyle" Value="True" />
|
||||
<Setter Property="Grid.IsSharedSizeScope" Value="true" />
|
||||
<Setter Property="HasDropShadow" Value="True" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type ContextMenu}">
|
||||
<Border x:Name="Border" Background="{Binding Background}" BorderBrush="{DynamicResource ContextMenu.Static.Border}" BorderThickness="1">
|
||||
<StackPanel IsItemsHost="True" KeyboardNavigation.DirectionalNavigation="Cycle" />
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="HasDropShadow" Value="true">
|
||||
<Setter TargetName="Border" Property="Padding" Value="0,3,0,3" />
|
||||
<Setter TargetName="Border" Property="CornerRadius" Value="4" />
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<!-- CustomMessageBox -->
|
||||
<SolidColorBrush x:Key="CustomMessageBox.Static.Background" Color="#FFE5E5E5"/>
|
||||
|
||||
<!-- MenuItem -->
|
||||
<SolidColorBrush x:Key="MenuItem.SubMenu.Background" Color="#FFF0F0F0"/>
|
||||
<SolidColorBrush x:Key="MenuItem.SubMenu.Border" Color="#FF999999"/>
|
||||
<SolidColorBrush x:Key="MenuItem.SubMenu.HoverRectangle" Color="Transparent"/>
|
||||
<ControlTemplate x:Key="CustomMenuItemTemplate" TargetType="{x:Type MenuItem}">
|
||||
<Border x:Name="templateRoot" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" SnapsToDevicePixels="True">
|
||||
<Grid VerticalAlignment="Center">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<ContentPresenter x:Name="Icon" Content="{TemplateBinding Icon}" ContentSource="Icon" HorizontalAlignment="Center" Height="16" Margin="3" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="Center" Width="16"/>
|
||||
<Path x:Name="GlyphPanel" Data="F1M10,1.2L4.7,9.1 4.5,9.1 0,5.2 1.3,3.5 4.3,6.1 8.3,0 10,1.2z" Fill="{TemplateBinding Foreground}" FlowDirection="LeftToRight" Margin="3" Visibility="Collapsed" VerticalAlignment="Center"/>
|
||||
<ContentPresenter ContentTemplate="{TemplateBinding HeaderTemplate}" Content="{TemplateBinding Header}" Grid.Column="1" ContentStringFormat="{TemplateBinding HeaderStringFormat}" ContentSource="Header" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
|
||||
<Popup x:Name="PART_Popup" AllowsTransparency="True" Focusable="False" IsOpen="{Binding IsSubmenuOpen, RelativeSource={RelativeSource TemplatedParent}}" PopupAnimation="{DynamicResource {x:Static SystemParameters.MenuPopupAnimationKey}}" Placement="Bottom">
|
||||
<Border x:Name="SubMenuBorder" BorderBrush="{DynamicResource MenuItem.SubMenu.Border}" BorderThickness="1" Background="{DynamicResource MenuItem.SubMenu.Background}" Padding="2">
|
||||
<ScrollViewer x:Name="SubMenuScrollViewer" Style="{DynamicResource {ComponentResourceKey ResourceId=MenuScrollViewer, TypeInTargetAssembly={x:Type FrameworkElement}}}">
|
||||
<!--<Grid RenderOptions.ClearTypeHint="Enabled">--> <!-- Not supported in .NET Framework 3.5 -->
|
||||
<Grid> <!-- Not supported in .NET Framework 3.5 -->
|
||||
<Canvas HorizontalAlignment="Left" Height="0" VerticalAlignment="Top" Width="0">
|
||||
<Rectangle x:Name="OpaqueRect" Fill="{Binding Background, ElementName=SubMenuBorder}" Height="{Binding ActualHeight, ElementName=SubMenuBorder}" Width="{Binding ActualWidth, ElementName=SubMenuBorder}"/>
|
||||
</Canvas>
|
||||
<Rectangle Fill="{DynamicResource MenuItem.SubMenu.HoverRectangle}" HorizontalAlignment="Left" Margin="29,2,0,2" Width="1"/>
|
||||
<ItemsPresenter x:Name="ItemsPresenter" KeyboardNavigation.DirectionalNavigation="Cycle" Grid.IsSharedSizeScope="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" KeyboardNavigation.TabNavigation="Cycle"/>
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
</Popup>
|
||||
</Grid>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsSuspendingPopupAnimation" Value="True">
|
||||
<Setter Property="PopupAnimation" TargetName="PART_Popup" Value="None"/>
|
||||
</Trigger>
|
||||
<Trigger Property="Icon" Value="{x:Null}">
|
||||
<Setter Property="Visibility" TargetName="Icon" Value="Collapsed"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsChecked" Value="True">
|
||||
<Setter Property="Visibility" TargetName="GlyphPanel" Value="Visible"/>
|
||||
<Setter Property="Visibility" TargetName="Icon" Value="Collapsed"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsHighlighted" Value="True">
|
||||
<Setter Property="Background" TargetName="templateRoot" Value="#3D26A0DA"/>
|
||||
<Setter Property="BorderBrush" TargetName="templateRoot" Value="#FF26A0DA"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsEnabled" Value="False">
|
||||
<Setter Property="TextElement.Foreground" TargetName="templateRoot" Value="#FF707070"/>
|
||||
<Setter Property="Fill" TargetName="GlyphPanel" Value="#FF707070"/>
|
||||
</Trigger>
|
||||
<Trigger Property="CanContentScroll" SourceName="SubMenuScrollViewer" Value="False">
|
||||
<Setter Property="Canvas.Top" TargetName="OpaqueRect" Value="{Binding VerticalOffset, ElementName=SubMenuScrollViewer}"/>
|
||||
<Setter Property="Canvas.Left" TargetName="OpaqueRect" Value="{Binding HorizontalOffset, ElementName=SubMenuScrollViewer}"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
|
||||
<!-- ProgressBar -->
|
||||
<SolidColorBrush x:Key="ProgressBar.Progress" Color="#FF06B025"/>
|
||||
<SolidColorBrush x:Key="ProgressBar.Background" Color="#FFE6E6E6"/>
|
||||
<SolidColorBrush x:Key="ProgressBar.Border" Color="#FFBCBCBC"/>
|
||||
|
||||
<!-- ScrollViewer -->
|
||||
<SolidColorBrush x:Key="ScrollViewer.ScrollBar.Background" Color="LightGray"/>
|
||||
<SolidColorBrush x:Key="ScrollViewer.ScrollBar.Foreground" Color="DarkGray"/>
|
||||
<ControlTemplate x:Key="CustomScrollViewerControlStyle" TargetType="{x:Type ScrollViewer}">
|
||||
<Grid x:Name="Grid" Background="{TemplateBinding Background}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Rectangle x:Name="Corner" Grid.Column="1" Fill="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" Grid.Row="1"/>
|
||||
<ScrollContentPresenter x:Name="PART_ScrollContentPresenter" CanContentScroll="{TemplateBinding CanContentScroll}" CanHorizontallyScroll="False" CanVerticallyScroll="False" ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" Grid.Column="0" Margin="{TemplateBinding Padding}" Grid.Row="0"/>
|
||||
<ScrollBar x:Name="PART_VerticalScrollBar" AutomationProperties.AutomationId="VerticalScrollBar" Cursor="Arrow" Grid.Column="1" Maximum="{TemplateBinding ScrollableHeight}" Minimum="0" Grid.Row="0" Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}" Value="{Binding VerticalOffset, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}" ViewportSize="{TemplateBinding ViewportHeight}" Background="{DynamicResource ScrollViewer.ScrollBar.Background}" Foreground="{DynamicResource ScrollViewer.ScrollBar.Foreground}"/>
|
||||
<ScrollBar x:Name="PART_HorizontalScrollBar" AutomationProperties.AutomationId="HorizontalScrollBar" Cursor="Arrow" Grid.Column="0" Maximum="{TemplateBinding ScrollableWidth}" Minimum="0" Orientation="Horizontal" Grid.Row="1" Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}" Value="{Binding HorizontalOffset, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}" ViewportSize="{TemplateBinding ViewportWidth}" Background="{DynamicResource ScrollViewer.ScrollBar.Background}" Foreground="{DynamicResource ScrollViewer.ScrollBar.Foreground}"/>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
|
||||
<!-- TabControl -->
|
||||
<SolidColorBrush x:Key="TabItem.Selected.Background" Color="#FFFFFF"/>
|
||||
<SolidColorBrush x:Key="TabItem.Selected.Border" Color="#ACACAC"/>
|
||||
<Style x:Key="CustomTabControlStyle" TargetType="{x:Type TabControl}">
|
||||
<Setter Property="Padding" Value="2"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Center"/>
|
||||
<Setter Property="VerticalContentAlignment" Value="Center"/>
|
||||
<Setter Property="Background" Value="{DynamicResource TabItem.Selected.Background}"/>
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource TabItem.Selected.Border}"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type TabControl}">
|
||||
<Grid x:Name="templateRoot" ClipToBounds="true" SnapsToDevicePixels="true" KeyboardNavigation.TabNavigation="Local">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition x:Name="ColumnDefinition0"/>
|
||||
<ColumnDefinition x:Name="ColumnDefinition1" Width="0"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition x:Name="RowDefinition0" Height="Auto"/>
|
||||
<RowDefinition x:Name="RowDefinition1" Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<TabPanel x:Name="headerPanel" Background="Transparent" Grid.Column="0" IsItemsHost="true" Margin="2,2,2,0" Grid.Row="0" KeyboardNavigation.TabIndex="1" Panel.ZIndex="1"/>
|
||||
<Border x:Name="contentPanel" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Grid.Column="0" KeyboardNavigation.DirectionalNavigation="Contained" Grid.Row="1" KeyboardNavigation.TabIndex="2" KeyboardNavigation.TabNavigation="Local">
|
||||
<ContentPresenter x:Name="PART_SelectedContentHost" ContentSource="SelectedContent" Margin="{TemplateBinding Padding}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
|
||||
</Border>
|
||||
</Grid>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="TabStripPlacement" Value="Bottom">
|
||||
<Setter Property="Grid.Row" TargetName="headerPanel" Value="1"/>
|
||||
<Setter Property="Grid.Row" TargetName="contentPanel" Value="0"/>
|
||||
<Setter Property="Height" TargetName="RowDefinition0" Value="*"/>
|
||||
<Setter Property="Height" TargetName="RowDefinition1" Value="Auto"/>
|
||||
<Setter Property="Margin" TargetName="headerPanel" Value="2,0,2,2"/>
|
||||
</Trigger>
|
||||
<Trigger Property="TabStripPlacement" Value="Left">
|
||||
<Setter Property="Grid.Row" TargetName="headerPanel" Value="0"/>
|
||||
<Setter Property="Grid.Row" TargetName="contentPanel" Value="0"/>
|
||||
<Setter Property="Grid.Column" TargetName="headerPanel" Value="0"/>
|
||||
<Setter Property="Grid.Column" TargetName="contentPanel" Value="1"/>
|
||||
<Setter Property="Width" TargetName="ColumnDefinition0" Value="Auto"/>
|
||||
<Setter Property="Width" TargetName="ColumnDefinition1" Value="*"/>
|
||||
<Setter Property="Height" TargetName="RowDefinition0" Value="*"/>
|
||||
<Setter Property="Height" TargetName="RowDefinition1" Value="0"/>
|
||||
<Setter Property="Margin" TargetName="headerPanel" Value="2,2,0,2"/>
|
||||
</Trigger>
|
||||
<Trigger Property="TabStripPlacement" Value="Right">
|
||||
<Setter Property="Grid.Row" TargetName="headerPanel" Value="0"/>
|
||||
<Setter Property="Grid.Row" TargetName="contentPanel" Value="0"/>
|
||||
<Setter Property="Grid.Column" TargetName="headerPanel" Value="1"/>
|
||||
<Setter Property="Grid.Column" TargetName="contentPanel" Value="0"/>
|
||||
<Setter Property="Width" TargetName="ColumnDefinition0" Value="*"/>
|
||||
<Setter Property="Width" TargetName="ColumnDefinition1" Value="Auto"/>
|
||||
<Setter Property="Height" TargetName="RowDefinition0" Value="*"/>
|
||||
<Setter Property="Height" TargetName="RowDefinition1" Value="0"/>
|
||||
<Setter Property="Margin" TargetName="headerPanel" Value="0,2,2,2"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsEnabled" Value="false">
|
||||
<Setter Property="TextElement.Foreground" TargetName="templateRoot" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<!-- TabItem -->
|
||||
<LinearGradientBrush x:Key="TabItem.Static.Background" EndPoint="0,1" StartPoint="0,0">
|
||||
<GradientStop Color="#F0F0F0" Offset="0.0"/>
|
||||
<GradientStop Color="#E5E5E5" Offset="1.0"/>
|
||||
</LinearGradientBrush>
|
||||
<SolidColorBrush x:Key="TabItem.Static.Border" Color="#ACACAC"/>
|
||||
<LinearGradientBrush x:Key="TabItem.MouseOver.Background" EndPoint="0,1" StartPoint="0,0">
|
||||
<GradientStop Color="#ECF4FC" Offset="0.0"/>
|
||||
<GradientStop Color="#DCECFC" Offset="1.0"/>
|
||||
</LinearGradientBrush>
|
||||
<SolidColorBrush x:Key="TabItem.MouseOver.Border" Color="#7EB4EA"/>
|
||||
<SolidColorBrush x:Key="TabItem.Disabled.Background" Color="#F0F0F0"/>
|
||||
<SolidColorBrush x:Key="TabItem.Disabled.Border" Color="#D9D9D9"/>
|
||||
<Style x:Key="CustomTabItemStyle" TargetType="{x:Type TabItem}">
|
||||
<Setter Property="FocusVisualStyle" Value="{DynamicResource FocusVisual}"/>
|
||||
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.WindowTextBrushKey}}"/>
|
||||
<Setter Property="Background" Value="{DynamicResource TabItem.Static.Background}"/>
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource TabItem.Static.Border}"/>
|
||||
<Setter Property="Margin" Value="0"/>
|
||||
<Setter Property="Padding" Value="6,2,6,2"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
|
||||
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type TabItem}">
|
||||
<Grid x:Name="templateRoot" SnapsToDevicePixels="true">
|
||||
<Border x:Name="mainBorder" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="1,1,1,0" Background="{TemplateBinding Background}" Margin="0">
|
||||
<Border x:Name="innerBorder" BorderBrush="{DynamicResource TabItem.Selected.Border}" BorderThickness="1,1,1,0" Background="{DynamicResource TabItem.Selected.Background}" Margin="-1" Opacity="0"/>
|
||||
</Border>
|
||||
<ContentPresenter x:Name="contentPresenter" ContentSource="Header" Focusable="False" HorizontalAlignment="{Binding HorizontalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{Binding VerticalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
|
||||
</Grid>
|
||||
<ControlTemplate.Triggers>
|
||||
<MultiDataTrigger>
|
||||
<MultiDataTrigger.Conditions>
|
||||
<Condition Binding="{Binding IsMouseOver, RelativeSource={RelativeSource Self}}" Value="true"/>
|
||||
<Condition Binding="{Binding TabStripPlacement, RelativeSource={RelativeSource AncestorType={x:Type TabControl}}}" Value="Left"/>
|
||||
</MultiDataTrigger.Conditions>
|
||||
<Setter Property="Background" TargetName="mainBorder" Value="{DynamicResource TabItem.MouseOver.Background}"/>
|
||||
<Setter Property="BorderBrush" TargetName="mainBorder" Value="{DynamicResource TabItem.MouseOver.Border}"/>
|
||||
<Setter Property="BorderThickness" TargetName="innerBorder" Value="1,1,0,1"/>
|
||||
<Setter Property="BorderThickness" TargetName="mainBorder" Value="1,1,0,1"/>
|
||||
</MultiDataTrigger>
|
||||
<MultiDataTrigger>
|
||||
<MultiDataTrigger.Conditions>
|
||||
<Condition Binding="{Binding IsMouseOver, RelativeSource={RelativeSource Self}}" Value="true"/>
|
||||
<Condition Binding="{Binding TabStripPlacement, RelativeSource={RelativeSource AncestorType={x:Type TabControl}}}" Value="Bottom"/>
|
||||
</MultiDataTrigger.Conditions>
|
||||
<Setter Property="Background" TargetName="mainBorder" Value="{DynamicResource TabItem.MouseOver.Background}"/>
|
||||
<Setter Property="BorderBrush" TargetName="mainBorder" Value="{DynamicResource TabItem.MouseOver.Border}"/>
|
||||
<Setter Property="BorderThickness" TargetName="innerBorder" Value="1,0,1,1"/>
|
||||
<Setter Property="BorderThickness" TargetName="mainBorder" Value="1,0,1,1"/>
|
||||
</MultiDataTrigger>
|
||||
<MultiDataTrigger>
|
||||
<MultiDataTrigger.Conditions>
|
||||
<Condition Binding="{Binding IsMouseOver, RelativeSource={RelativeSource Self}}" Value="true"/>
|
||||
<Condition Binding="{Binding TabStripPlacement, RelativeSource={RelativeSource AncestorType={x:Type TabControl}}}" Value="Right"/>
|
||||
</MultiDataTrigger.Conditions>
|
||||
<Setter Property="Background" TargetName="mainBorder" Value="{DynamicResource TabItem.MouseOver.Background}"/>
|
||||
<Setter Property="BorderBrush" TargetName="mainBorder" Value="{DynamicResource TabItem.MouseOver.Border}"/>
|
||||
<Setter Property="BorderThickness" TargetName="innerBorder" Value="0,1,1,1"/>
|
||||
<Setter Property="BorderThickness" TargetName="mainBorder" Value="0,1,1,1"/>
|
||||
</MultiDataTrigger>
|
||||
<MultiDataTrigger>
|
||||
<MultiDataTrigger.Conditions>
|
||||
<Condition Binding="{Binding IsMouseOver, RelativeSource={RelativeSource Self}}" Value="true"/>
|
||||
<Condition Binding="{Binding TabStripPlacement, RelativeSource={RelativeSource AncestorType={x:Type TabControl}}}" Value="Top"/>
|
||||
</MultiDataTrigger.Conditions>
|
||||
<Setter Property="Background" TargetName="mainBorder" Value="{DynamicResource TabItem.MouseOver.Background}"/>
|
||||
<Setter Property="BorderBrush" TargetName="mainBorder" Value="{DynamicResource TabItem.MouseOver.Border}"/>
|
||||
<Setter Property="BorderThickness" TargetName="innerBorder" Value="1,1,1,0"/>
|
||||
<Setter Property="BorderThickness" TargetName="mainBorder" Value="1,1,1,0"/>
|
||||
</MultiDataTrigger>
|
||||
<MultiDataTrigger>
|
||||
<MultiDataTrigger.Conditions>
|
||||
<Condition Binding="{Binding IsEnabled, RelativeSource={RelativeSource Self}}" Value="false"/>
|
||||
<Condition Binding="{Binding TabStripPlacement, RelativeSource={RelativeSource AncestorType={x:Type TabControl}}}" Value="Left"/>
|
||||
</MultiDataTrigger.Conditions>
|
||||
<Setter Property="Opacity" TargetName="contentPresenter" Value="0.56"/>
|
||||
<Setter Property="Background" TargetName="mainBorder" Value="{DynamicResource TabItem.Disabled.Background}"/>
|
||||
<Setter Property="BorderBrush" TargetName="mainBorder" Value="{DynamicResource TabItem.Disabled.Border}"/>
|
||||
<Setter Property="BorderThickness" TargetName="innerBorder" Value="1,1,0,1"/>
|
||||
<Setter Property="BorderThickness" TargetName="mainBorder" Value="1,1,0,1"/>
|
||||
</MultiDataTrigger>
|
||||
<MultiDataTrigger>
|
||||
<MultiDataTrigger.Conditions>
|
||||
<Condition Binding="{Binding IsEnabled, RelativeSource={RelativeSource Self}}" Value="false"/>
|
||||
<Condition Binding="{Binding TabStripPlacement, RelativeSource={RelativeSource AncestorType={x:Type TabControl}}}" Value="Bottom"/>
|
||||
</MultiDataTrigger.Conditions>
|
||||
<Setter Property="Opacity" TargetName="contentPresenter" Value="0.56"/>
|
||||
<Setter Property="Background" TargetName="mainBorder" Value="{DynamicResource TabItem.Disabled.Background}"/>
|
||||
<Setter Property="BorderBrush" TargetName="mainBorder" Value="{DynamicResource TabItem.Disabled.Border}"/>
|
||||
<Setter Property="BorderThickness" TargetName="innerBorder" Value="1,0,1,1"/>
|
||||
<Setter Property="BorderThickness" TargetName="mainBorder" Value="1,0,1,1"/>
|
||||
</MultiDataTrigger>
|
||||
<MultiDataTrigger>
|
||||
<MultiDataTrigger.Conditions>
|
||||
<Condition Binding="{Binding IsEnabled, RelativeSource={RelativeSource Self}}" Value="false"/>
|
||||
<Condition Binding="{Binding TabStripPlacement, RelativeSource={RelativeSource AncestorType={x:Type TabControl}}}" Value="Right"/>
|
||||
</MultiDataTrigger.Conditions>
|
||||
<Setter Property="Opacity" TargetName="contentPresenter" Value="0.56"/>
|
||||
<Setter Property="Background" TargetName="mainBorder" Value="{DynamicResource TabItem.Disabled.Background}"/>
|
||||
<Setter Property="BorderBrush" TargetName="mainBorder" Value="{DynamicResource TabItem.Disabled.Border}"/>
|
||||
<Setter Property="BorderThickness" TargetName="innerBorder" Value="0,1,1,1"/>
|
||||
<Setter Property="BorderThickness" TargetName="mainBorder" Value="0,1,1,1"/>
|
||||
</MultiDataTrigger>
|
||||
<MultiDataTrigger>
|
||||
<MultiDataTrigger.Conditions>
|
||||
<Condition Binding="{Binding IsEnabled, RelativeSource={RelativeSource Self}}" Value="false"/>
|
||||
<Condition Binding="{Binding TabStripPlacement, RelativeSource={RelativeSource AncestorType={x:Type TabControl}}}" Value="Top"/>
|
||||
</MultiDataTrigger.Conditions>
|
||||
<Setter Property="Opacity" TargetName="contentPresenter" Value="0.56"/>
|
||||
<Setter Property="Background" TargetName="mainBorder" Value="{DynamicResource TabItem.Disabled.Background}"/>
|
||||
<Setter Property="BorderBrush" TargetName="mainBorder" Value="{DynamicResource TabItem.Disabled.Border}"/>
|
||||
<Setter Property="BorderThickness" TargetName="innerBorder" Value="1,1,1,0"/>
|
||||
<Setter Property="BorderThickness" TargetName="mainBorder" Value="1,1,1,0"/>
|
||||
</MultiDataTrigger>
|
||||
<MultiDataTrigger>
|
||||
<MultiDataTrigger.Conditions>
|
||||
<Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="false"/>
|
||||
<Condition Binding="{Binding TabStripPlacement, RelativeSource={RelativeSource AncestorType={x:Type TabControl}}}" Value="Left"/>
|
||||
</MultiDataTrigger.Conditions>
|
||||
<Setter Property="BorderThickness" TargetName="innerBorder" Value="1,1,0,1"/>
|
||||
<Setter Property="BorderThickness" TargetName="mainBorder" Value="1,1,0,1"/>
|
||||
</MultiDataTrigger>
|
||||
<MultiDataTrigger>
|
||||
<MultiDataTrigger.Conditions>
|
||||
<Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="true"/>
|
||||
<Condition Binding="{Binding TabStripPlacement, RelativeSource={RelativeSource AncestorType={x:Type TabControl}}}" Value="Left"/>
|
||||
</MultiDataTrigger.Conditions>
|
||||
<Setter Property="Panel.ZIndex" Value="1"/>
|
||||
<Setter Property="Margin" Value="-2,-2,0,-2"/>
|
||||
<Setter Property="Opacity" TargetName="innerBorder" Value="1"/>
|
||||
<Setter Property="BorderThickness" TargetName="innerBorder" Value="1,1,0,1"/>
|
||||
<Setter Property="BorderThickness" TargetName="mainBorder" Value="1,1,0,1"/>
|
||||
</MultiDataTrigger>
|
||||
<MultiDataTrigger>
|
||||
<MultiDataTrigger.Conditions>
|
||||
<Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="false"/>
|
||||
<Condition Binding="{Binding TabStripPlacement, RelativeSource={RelativeSource AncestorType={x:Type TabControl}}}" Value="Bottom"/>
|
||||
</MultiDataTrigger.Conditions>
|
||||
<Setter Property="BorderThickness" TargetName="innerBorder" Value="1,0,1,1"/>
|
||||
<Setter Property="BorderThickness" TargetName="mainBorder" Value="1,0,1,1"/>
|
||||
</MultiDataTrigger>
|
||||
<MultiDataTrigger>
|
||||
<MultiDataTrigger.Conditions>
|
||||
<Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="true"/>
|
||||
<Condition Binding="{Binding TabStripPlacement, RelativeSource={RelativeSource AncestorType={x:Type TabControl}}}" Value="Bottom"/>
|
||||
</MultiDataTrigger.Conditions>
|
||||
<Setter Property="Panel.ZIndex" Value="1"/>
|
||||
<Setter Property="Margin" Value="-2,0,-2,-2"/>
|
||||
<Setter Property="Opacity" TargetName="innerBorder" Value="1"/>
|
||||
<Setter Property="BorderThickness" TargetName="innerBorder" Value="1,0,1,1"/>
|
||||
<Setter Property="BorderThickness" TargetName="mainBorder" Value="1,0,1,1"/>
|
||||
</MultiDataTrigger>
|
||||
<MultiDataTrigger>
|
||||
<MultiDataTrigger.Conditions>
|
||||
<Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="false"/>
|
||||
<Condition Binding="{Binding TabStripPlacement, RelativeSource={RelativeSource AncestorType={x:Type TabControl}}}" Value="Right"/>
|
||||
</MultiDataTrigger.Conditions>
|
||||
<Setter Property="BorderThickness" TargetName="innerBorder" Value="0,1,1,1"/>
|
||||
<Setter Property="BorderThickness" TargetName="mainBorder" Value="0,1,1,1"/>
|
||||
</MultiDataTrigger>
|
||||
<MultiDataTrigger>
|
||||
<MultiDataTrigger.Conditions>
|
||||
<Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="true"/>
|
||||
<Condition Binding="{Binding TabStripPlacement, RelativeSource={RelativeSource AncestorType={x:Type TabControl}}}" Value="Right"/>
|
||||
</MultiDataTrigger.Conditions>
|
||||
<Setter Property="Panel.ZIndex" Value="1"/>
|
||||
<Setter Property="Margin" Value="0,-2,-2,-2"/>
|
||||
<Setter Property="Opacity" TargetName="innerBorder" Value="1"/>
|
||||
<Setter Property="BorderThickness" TargetName="innerBorder" Value="0,1,1,1"/>
|
||||
<Setter Property="BorderThickness" TargetName="mainBorder" Value="0,1,1,1"/>
|
||||
</MultiDataTrigger>
|
||||
<MultiDataTrigger>
|
||||
<MultiDataTrigger.Conditions>
|
||||
<Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="false"/>
|
||||
<Condition Binding="{Binding TabStripPlacement, RelativeSource={RelativeSource AncestorType={x:Type TabControl}}}" Value="Top"/>
|
||||
</MultiDataTrigger.Conditions>
|
||||
<Setter Property="BorderThickness" TargetName="innerBorder" Value="1,1,1,0"/>
|
||||
<Setter Property="BorderThickness" TargetName="mainBorder" Value="1,1,1,0"/>
|
||||
</MultiDataTrigger>
|
||||
<MultiDataTrigger>
|
||||
<MultiDataTrigger.Conditions>
|
||||
<Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="true"/>
|
||||
<Condition Binding="{Binding TabStripPlacement, RelativeSource={RelativeSource AncestorType={x:Type TabControl}}}" Value="Top"/>
|
||||
</MultiDataTrigger.Conditions>
|
||||
<Setter Property="Panel.ZIndex" Value="1"/>
|
||||
<Setter Property="Margin" Value="-2,-2,-2,0"/>
|
||||
<Setter Property="Opacity" TargetName="innerBorder" Value="1"/>
|
||||
<Setter Property="BorderThickness" TargetName="innerBorder" Value="1,1,1,0"/>
|
||||
<Setter Property="BorderThickness" TargetName="mainBorder" Value="1,1,1,0"/>
|
||||
</MultiDataTrigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
497
MPF.UI/App.xaml.cs
Normal file
497
MPF.UI/App.xaml.cs
Normal file
@@ -0,0 +1,497 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Markup;
|
||||
|
||||
namespace MPF.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for App.xaml
|
||||
/// </summary>
|
||||
public partial class App : Application
|
||||
{
|
||||
|
||||
#if NET35_OR_GREATER || NETCOREAPP
|
||||
|
||||
#region ControlTemplates
|
||||
|
||||
/// <summary>
|
||||
/// ComboBoxTemplate ControlTemplate XAML (.NET Framework 4.0 and above)
|
||||
/// </summary>
|
||||
private const string _comboBoxTemplateDefault = @"<ControlTemplate TargetType=""{x:Type ComboBox}"">
|
||||
<Grid x:Name=""templateRoot"" SnapsToDevicePixels=""true"">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width=""*""/>
|
||||
<ColumnDefinition MinWidth=""{DynamicResource {x:Static SystemParameters.VerticalScrollBarWidthKey}}"" Width=""0""/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Popup x:Name=""PART_Popup"" AllowsTransparency=""true"" Grid.ColumnSpan=""2"" IsOpen=""{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"" Margin=""1"" PopupAnimation=""{DynamicResource {x:Static SystemParameters.ComboBoxPopupAnimationKey}}"" Placement=""Bottom"">
|
||||
<themes:SystemDropShadowChrome x:Name=""shadow"" Color=""Transparent"" MaxHeight=""{TemplateBinding MaxDropDownHeight}"" MinWidth=""{Binding ActualWidth, ElementName=templateRoot}"">
|
||||
<Border x:Name=""dropDownBorder"" BorderBrush=""{DynamicResource {x:Static SystemColors.WindowFrameBrushKey}}"" BorderThickness=""1"" Background=""{DynamicResource {x:Static SystemColors.WindowBrushKey}}"">
|
||||
<ScrollViewer x:Name=""DropDownScrollViewer"">
|
||||
<Grid x:Name=""grid"" RenderOptions.ClearTypeHint=""Enabled"">
|
||||
<Canvas x:Name=""canvas"" HorizontalAlignment=""Left"" Height=""0"" VerticalAlignment=""Top"" Width=""0"">
|
||||
<Rectangle x:Name=""opaqueRect"" Fill=""{Binding Background, ElementName=dropDownBorder}"" Height=""{Binding ActualHeight, ElementName=dropDownBorder}"" Width=""{Binding ActualWidth, ElementName=dropDownBorder}""/>
|
||||
</Canvas>
|
||||
<ItemsPresenter x:Name=""ItemsPresenter"" KeyboardNavigation.DirectionalNavigation=""Contained"" SnapsToDevicePixels=""{TemplateBinding SnapsToDevicePixels}""/>
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
</themes:SystemDropShadowChrome>
|
||||
</Popup>
|
||||
<ToggleButton x:Name=""toggleButton"" BorderBrush=""{TemplateBinding BorderBrush}"" BorderThickness=""{TemplateBinding BorderThickness}"" Background=""{TemplateBinding Background}"" Grid.ColumnSpan=""2"" IsChecked=""{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"" Style=""{DynamicResource ComboBoxToggleButton}""/>
|
||||
<ContentPresenter x:Name=""contentPresenter"" ContentTemplate=""{TemplateBinding SelectionBoxItemTemplate}"" ContentTemplateSelector=""{TemplateBinding ItemTemplateSelector}"" Content=""{TemplateBinding SelectionBoxItem}"" ContentStringFormat=""{TemplateBinding SelectionBoxItemStringFormat}"" HorizontalAlignment=""{TemplateBinding HorizontalContentAlignment}"" IsHitTestVisible=""false"" Margin=""{TemplateBinding Padding}"" SnapsToDevicePixels=""{TemplateBinding SnapsToDevicePixels}"" VerticalAlignment=""{TemplateBinding VerticalContentAlignment}""/>
|
||||
</Grid>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property=""HasDropShadow"" SourceName=""PART_Popup"" Value=""true"">
|
||||
<Setter Property=""Margin"" TargetName=""shadow"" Value=""0,0,5,5""/>
|
||||
<Setter Property=""Color"" TargetName=""shadow"" Value=""#71000000""/>
|
||||
</Trigger>
|
||||
<Trigger Property=""HasItems"" Value=""false"">
|
||||
<Setter Property=""Height"" TargetName=""dropDownBorder"" Value=""95""/>
|
||||
</Trigger>
|
||||
<MultiTrigger>
|
||||
<MultiTrigger.Conditions>
|
||||
<Condition Property=""IsGrouping"" Value=""true""/>
|
||||
<Condition Property=""VirtualizingPanel.IsVirtualizingWhenGrouping"" Value=""false""/>
|
||||
</MultiTrigger.Conditions>
|
||||
<Setter Property=""ScrollViewer.CanContentScroll"" Value=""false""/>
|
||||
</MultiTrigger>
|
||||
<Trigger Property=""ScrollViewer.CanContentScroll"" SourceName=""DropDownScrollViewer"" Value=""false"">
|
||||
<Setter Property=""Canvas.Top"" TargetName=""opaqueRect"" Value=""{Binding VerticalOffset, ElementName=DropDownScrollViewer}""/>
|
||||
<Setter Property=""Canvas.Left"" TargetName=""opaqueRect"" Value=""{Binding HorizontalOffset, ElementName=DropDownScrollViewer}""/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>";
|
||||
|
||||
/// <summary>
|
||||
/// ComboBoxTemplate ControlTemplate XAML (.NET Framework 3.5)
|
||||
/// </summary>
|
||||
private const string _comboBoxTemplateNet35 = @"<ControlTemplate TargetType=""{x:Type ComboBox}"">
|
||||
<Grid x:Name=""templateRoot"" SnapsToDevicePixels=""true"">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width=""*""/>
|
||||
<ColumnDefinition MinWidth=""{DynamicResource {x:Static SystemParameters.VerticalScrollBarWidthKey}}"" Width=""0""/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Popup x:Name=""PART_Popup"" AllowsTransparency=""true"" Grid.ColumnSpan=""2"" IsOpen=""{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"" Margin=""1"" PopupAnimation=""{DynamicResource {x:Static SystemParameters.ComboBoxPopupAnimationKey}}"" Placement=""Bottom"">
|
||||
<Border x:Name=""dropDownBorder"" BorderBrush=""{DynamicResource {x:Static SystemColors.WindowFrameBrushKey}}"" BorderThickness=""1"" Background=""{DynamicResource {x:Static SystemColors.WindowBrushKey}}"">
|
||||
<ScrollViewer x:Name=""DropDownScrollViewer"">
|
||||
<Grid x:Name=""grid"">
|
||||
<Canvas x:Name=""canvas"" HorizontalAlignment=""Left"" Height=""0"" VerticalAlignment=""Top"" Width=""0"">
|
||||
<Rectangle x:Name=""opaqueRect"" Fill=""{Binding Background, ElementName=dropDownBorder}"" Height=""{Binding ActualHeight, ElementName=dropDownBorder}"" Width=""{Binding ActualWidth, ElementName=dropDownBorder}""/>
|
||||
</Canvas>
|
||||
<ItemsPresenter x:Name=""ItemsPresenter"" KeyboardNavigation.DirectionalNavigation=""Contained"" SnapsToDevicePixels=""{TemplateBinding SnapsToDevicePixels}""/>
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
</Popup>
|
||||
<ToggleButton x:Name=""toggleButton"" BorderBrush=""{TemplateBinding BorderBrush}"" BorderThickness=""{TemplateBinding BorderThickness}"" Background=""{TemplateBinding Background}"" Grid.ColumnSpan=""2"" IsChecked=""{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"" Style=""{DynamicResource ComboBoxToggleButton}""/>
|
||||
<ContentPresenter x:Name=""contentPresenter"" ContentTemplate=""{TemplateBinding SelectionBoxItemTemplate}"" ContentTemplateSelector=""{TemplateBinding ItemTemplateSelector}"" Content=""{TemplateBinding SelectionBoxItem}"" ContentStringFormat=""{TemplateBinding SelectionBoxItemStringFormat}"" HorizontalAlignment=""{TemplateBinding HorizontalContentAlignment}"" IsHitTestVisible=""false"" Margin=""{TemplateBinding Padding}"" SnapsToDevicePixels=""{TemplateBinding SnapsToDevicePixels}"" VerticalAlignment=""{TemplateBinding VerticalContentAlignment}""/>
|
||||
</Grid>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property=""HasItems"" Value=""false"">
|
||||
<Setter Property=""Height"" TargetName=""dropDownBorder"" Value=""95""/>
|
||||
</Trigger>
|
||||
<MultiTrigger>
|
||||
<MultiTrigger.Conditions>
|
||||
<Condition Property=""IsGrouping"" Value=""true""/>
|
||||
</MultiTrigger.Conditions>
|
||||
<Setter Property=""ScrollViewer.CanContentScroll"" Value=""false""/>
|
||||
</MultiTrigger>
|
||||
<Trigger Property=""ScrollViewer.CanContentScroll"" SourceName=""DropDownScrollViewer"" Value=""false"">
|
||||
<Setter Property=""Canvas.Top"" TargetName=""opaqueRect"" Value=""{Binding VerticalOffset, ElementName=DropDownScrollViewer}""/>
|
||||
<Setter Property=""Canvas.Left"" TargetName=""opaqueRect"" Value=""{Binding HorizontalOffset, ElementName=DropDownScrollViewer}""/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>";
|
||||
|
||||
/// <summary>
|
||||
/// ComboBoxEditableTemplate ControlTemplate XAML (.NET Framework 4.0 and above)
|
||||
/// </summary>
|
||||
private const string _comboBoxEditableTemplateDefault = @"<ControlTemplate TargetType=""{x:Type ComboBox}"">
|
||||
<Grid x:Name=""templateRoot"" SnapsToDevicePixels=""true"">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width=""*""/>
|
||||
<ColumnDefinition MinWidth=""{DynamicResource {x:Static SystemParameters.VerticalScrollBarWidthKey}}"" Width=""0""/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Popup x:Name=""PART_Popup"" AllowsTransparency=""true"" Grid.ColumnSpan=""2"" IsOpen=""{Binding IsDropDownOpen, RelativeSource={RelativeSource TemplatedParent}}"" PopupAnimation=""{DynamicResource {x:Static SystemParameters.ComboBoxPopupAnimationKey}}"" Placement=""Bottom"">
|
||||
<themes:SystemDropShadowChrome x:Name=""shadow"" Color=""Transparent"" MaxHeight=""{TemplateBinding MaxDropDownHeight}"" MinWidth=""{Binding ActualWidth, ElementName=templateRoot}"">
|
||||
<Border x:Name=""dropDownBorder"" BorderBrush=""{DynamicResource {x:Static SystemColors.WindowFrameBrushKey}}"" BorderThickness=""1"" Background=""{DynamicResource {x:Static SystemColors.WindowBrushKey}}"">
|
||||
<ScrollViewer x:Name=""DropDownScrollViewer"">
|
||||
<Grid x:Name=""grid"" RenderOptions.ClearTypeHint=""Enabled"">
|
||||
<Canvas x:Name=""canvas"" HorizontalAlignment=""Left"" Height=""0"" VerticalAlignment=""Top"" Width=""0"">
|
||||
<Rectangle x:Name=""opaqueRect"" Fill=""{Binding Background, ElementName=dropDownBorder}"" Height=""{Binding ActualHeight, ElementName=dropDownBorder}"" Width=""{Binding ActualWidth, ElementName=dropDownBorder}""/>
|
||||
</Canvas>
|
||||
<ItemsPresenter x:Name=""ItemsPresenter"" KeyboardNavigation.DirectionalNavigation=""Contained"" SnapsToDevicePixels=""{TemplateBinding SnapsToDevicePixels}""/>
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
</themes:SystemDropShadowChrome>
|
||||
</Popup>
|
||||
<ToggleButton x:Name=""toggleButton"" BorderBrush=""{TemplateBinding BorderBrush}"" BorderThickness=""{TemplateBinding BorderThickness}"" Background=""{TemplateBinding Background}"" Grid.ColumnSpan=""2"" IsChecked=""{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"" Style=""{DynamicResource ComboBoxToggleButton}""/>
|
||||
<Border x:Name=""border"" Background=""{DynamicResource TextBox.Static.Background}"" Margin=""{TemplateBinding BorderThickness}"">
|
||||
<TextBox x:Name=""PART_EditableTextBox"" HorizontalContentAlignment=""{TemplateBinding HorizontalContentAlignment}"" IsReadOnly=""{Binding IsReadOnly, RelativeSource={RelativeSource TemplatedParent}}"" Margin=""{TemplateBinding Padding}"" Style=""{DynamicResource ComboBoxEditableTextBox}"" VerticalContentAlignment=""{TemplateBinding VerticalContentAlignment}""/>
|
||||
</Border>
|
||||
</Grid>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property=""IsEnabled"" Value=""false"">
|
||||
<Setter Property=""Opacity"" TargetName=""border"" Value=""0.56""/>
|
||||
</Trigger>
|
||||
<Trigger Property=""IsKeyboardFocusWithin"" Value=""true"">
|
||||
<Setter Property=""Foreground"" Value=""Black""/>
|
||||
</Trigger>
|
||||
<Trigger Property=""HasDropShadow"" SourceName=""PART_Popup"" Value=""true"">
|
||||
<Setter Property=""Margin"" TargetName=""shadow"" Value=""0,0,5,5""/>
|
||||
<Setter Property=""Color"" TargetName=""shadow"" Value=""#71000000""/>
|
||||
</Trigger>
|
||||
<Trigger Property=""HasItems"" Value=""false"">
|
||||
<Setter Property=""Height"" TargetName=""dropDownBorder"" Value=""95""/>
|
||||
</Trigger>
|
||||
<MultiTrigger>
|
||||
<MultiTrigger.Conditions>
|
||||
<Condition Property=""IsGrouping"" Value=""true""/>
|
||||
<Condition Property=""VirtualizingPanel.IsVirtualizingWhenGrouping"" Value=""false""/>
|
||||
</MultiTrigger.Conditions>
|
||||
<Setter Property=""ScrollViewer.CanContentScroll"" Value=""false""/>
|
||||
</MultiTrigger>
|
||||
<Trigger Property=""ScrollViewer.CanContentScroll"" SourceName=""DropDownScrollViewer"" Value=""false"">
|
||||
<Setter Property=""Canvas.Top"" TargetName=""opaqueRect"" Value=""{Binding VerticalOffset, ElementName=DropDownScrollViewer}""/>
|
||||
<Setter Property=""Canvas.Left"" TargetName=""opaqueRect"" Value=""{Binding HorizontalOffset, ElementName=DropDownScrollViewer}""/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>";
|
||||
|
||||
/// <summary>
|
||||
/// ComboBoxEditableTemplate ControlTemplate XAML (.NET Framework 3.5)
|
||||
/// </summary>
|
||||
private const string _comboBoxEditableTemplateNet35 = @"<ControlTemplate TargetType=""{x:Type ComboBox}"">
|
||||
<Grid x:Name=""templateRoot"" SnapsToDevicePixels=""true"">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width=""*""/>
|
||||
<ColumnDefinition MinWidth=""{DynamicResource {x:Static SystemParameters.VerticalScrollBarWidthKey}}"" Width=""0""/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Popup x:Name=""PART_Popup"" AllowsTransparency=""true"" Grid.ColumnSpan=""2"" IsOpen=""{Binding IsDropDownOpen, RelativeSource={RelativeSource TemplatedParent}}"" PopupAnimation=""{DynamicResource {x:Static SystemParameters.ComboBoxPopupAnimationKey}}"" Placement=""Bottom"">
|
||||
<Border x:Name=""dropDownBorder"" BorderBrush=""{DynamicResource {x:Static SystemColors.WindowFrameBrushKey}}"" BorderThickness=""1"" Background=""{DynamicResource {x:Static SystemColors.WindowBrushKey}}"">
|
||||
<ScrollViewer x:Name=""DropDownScrollViewer"">
|
||||
<Grid x:Name=""grid"">
|
||||
<Canvas x:Name=""canvas"" HorizontalAlignment=""Left"" Height=""0"" VerticalAlignment=""Top"" Width=""0"">
|
||||
<Rectangle x:Name=""opaqueRect"" Fill=""{Binding Background, ElementName=dropDownBorder}"" Height=""{Binding ActualHeight, ElementName=dropDownBorder}"" Width=""{Binding ActualWidth, ElementName=dropDownBorder}""/>
|
||||
</Canvas>
|
||||
<ItemsPresenter x:Name=""ItemsPresenter"" KeyboardNavigation.DirectionalNavigation=""Contained"" SnapsToDevicePixels=""{TemplateBinding SnapsToDevicePixels}""/>
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
</Popup>
|
||||
<ToggleButton x:Name=""toggleButton"" BorderBrush=""{TemplateBinding BorderBrush}"" BorderThickness=""{TemplateBinding BorderThickness}"" Background=""{TemplateBinding Background}"" Grid.ColumnSpan=""2"" IsChecked=""{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"" Style=""{DynamicResource ComboBoxToggleButton}""/>
|
||||
<Border x:Name=""border"" Background=""{DynamicResource TextBox.Static.Background}"" Margin=""{TemplateBinding BorderThickness}"">
|
||||
<TextBox x:Name=""PART_EditableTextBox"" HorizontalContentAlignment=""{TemplateBinding HorizontalContentAlignment}"" IsReadOnly=""{Binding IsReadOnly, RelativeSource={RelativeSource TemplatedParent}}"" Margin=""{TemplateBinding Padding}"" Style=""{DynamicResource ComboBoxEditableTextBox}"" VerticalContentAlignment=""{TemplateBinding VerticalContentAlignment}""/>
|
||||
</Border>
|
||||
</Grid>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property=""IsEnabled"" Value=""false"">
|
||||
<Setter Property=""Opacity"" TargetName=""border"" Value=""0.56""/>
|
||||
</Trigger>
|
||||
<Trigger Property=""IsKeyboardFocusWithin"" Value=""true"">
|
||||
<Setter Property=""Foreground"" Value=""Black""/>
|
||||
</Trigger>
|
||||
<Trigger Property=""HasItems"" Value=""false"">
|
||||
<Setter Property=""Height"" TargetName=""dropDownBorder"" Value=""95""/>
|
||||
</Trigger>
|
||||
<MultiTrigger>
|
||||
<MultiTrigger.Conditions>
|
||||
<Condition Property=""IsGrouping"" Value=""true""/>
|
||||
</MultiTrigger.Conditions>
|
||||
<Setter Property=""ScrollViewer.CanContentScroll"" Value=""false""/>
|
||||
</MultiTrigger>
|
||||
<Trigger Property=""ScrollViewer.CanContentScroll"" SourceName=""DropDownScrollViewer"" Value=""false"">
|
||||
<Setter Property=""Canvas.Top"" TargetName=""opaqueRect"" Value=""{Binding VerticalOffset, ElementName=DropDownScrollViewer}""/>
|
||||
<Setter Property=""Canvas.Left"" TargetName=""opaqueRect"" Value=""{Binding HorizontalOffset, ElementName=DropDownScrollViewer}""/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>";
|
||||
|
||||
#endregion
|
||||
|
||||
#region Styles
|
||||
|
||||
/// <summary>
|
||||
/// ComboBoxEditableTextBox Style XAML (.NET Framework 4.0 and above)
|
||||
/// </summary>
|
||||
private const string _comboBoxEditableTextBoxStyleDefault = @"<Style TargetType=""{x:Type TextBox}"">
|
||||
<Setter Property=""OverridesDefaultStyle"" Value=""true""/>
|
||||
<Setter Property=""AllowDrop"" Value=""true""/>
|
||||
<Setter Property=""MinWidth"" Value=""0""/>
|
||||
<Setter Property=""MinHeight"" Value=""0""/>
|
||||
<Setter Property=""FocusVisualStyle"" Value=""{x:Null}""/>
|
||||
<Setter Property=""ScrollViewer.PanningMode"" Value=""VerticalFirst""/>
|
||||
<Setter Property=""Stylus.IsFlicksEnabled"" Value=""False""/>
|
||||
<Setter Property=""Template"">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType=""{x:Type TextBox}"">
|
||||
<ScrollViewer x:Name=""PART_ContentHost"" Background=""Transparent"" Focusable=""false"" HorizontalScrollBarVisibility=""Hidden"" VerticalScrollBarVisibility=""Hidden""/>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>";
|
||||
|
||||
/// <summary>
|
||||
/// ComboBoxEditableTextBox Style XAML (.NET Framework 3.5)
|
||||
/// </summary>
|
||||
private const string _comboBoxEditableTextBoxStyleNet35 = @"<Style TargetType=""{x:Type TextBox}"">
|
||||
<Setter Property=""OverridesDefaultStyle"" Value=""true""/>
|
||||
<Setter Property=""AllowDrop"" Value=""true""/>
|
||||
<Setter Property=""MinWidth"" Value=""0""/>
|
||||
<Setter Property=""MinHeight"" Value=""0""/>
|
||||
<Setter Property=""FocusVisualStyle"" Value=""{x:Null}""/>
|
||||
<Setter Property=""Stylus.IsFlicksEnabled"" Value=""False""/>
|
||||
<Setter Property=""Template"">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType=""{x:Type TextBox}"">
|
||||
<ScrollViewer x:Name=""PART_ContentHost"" Background=""Transparent"" Focusable=""false"" HorizontalScrollBarVisibility=""Hidden"" VerticalScrollBarVisibility=""Hidden""/>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>";
|
||||
|
||||
/// <summary>
|
||||
/// CustomComboBoxStyle Style XAML (.NET Framework 4.0 and above)
|
||||
/// </summary>
|
||||
private const string _customComboBoxStyleDefault = @"<Style x:Key=""CustomComboBoxStyle"" TargetType=""{x:Type ComboBox}"">
|
||||
<Setter Property=""FocusVisualStyle"" Value=""{DynamicResource FocusVisual}""/>
|
||||
<Setter Property=""Background"" Value=""{DynamicResource ComboBox.Static.Background}""/>
|
||||
<Setter Property=""BorderBrush"" Value=""{DynamicResource ComboBox.Static.Border}""/>
|
||||
<Setter Property=""Foreground"" Value=""{DynamicResource {x:Static SystemColors.WindowTextBrushKey}}""/>
|
||||
<Setter Property=""BorderThickness"" Value=""1""/>
|
||||
<Setter Property=""ScrollViewer.HorizontalScrollBarVisibility"" Value=""Auto""/>
|
||||
<Setter Property=""ScrollViewer.VerticalScrollBarVisibility"" Value=""Auto""/>
|
||||
<Setter Property=""Padding"" Value=""6,3,5,3""/>
|
||||
<Setter Property=""ScrollViewer.CanContentScroll"" Value=""true""/>
|
||||
<Setter Property=""ScrollViewer.PanningMode"" Value=""VerticalFirst""/>
|
||||
<Setter Property=""Stylus.IsFlicksEnabled"" Value=""False""/>
|
||||
<Setter Property=""Template"" Value=""{DynamicResource ComboBoxTemplate}""/>
|
||||
<Style.Triggers>
|
||||
<Trigger Property=""IsEditable"" Value=""true"">
|
||||
<Setter Property=""IsTabStop"" Value=""false""/>
|
||||
<Setter Property=""Padding"" Value=""2""/>
|
||||
<Setter Property=""Template"" Value=""{DynamicResource ComboBoxEditableTemplate}""/>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>";
|
||||
|
||||
/// <summary>
|
||||
/// CustomComboBoxStyle Style XAML (.NET Framework 3.5)
|
||||
/// </summary>
|
||||
private const string _customComboBoxStyleNet35 = @"<Style TargetType=""{x:Type ComboBox}"">
|
||||
<Setter Property=""FocusVisualStyle"" Value=""{DynamicResource FocusVisual}""/>
|
||||
<Setter Property=""Background"" Value=""{DynamicResource ComboBox.Static.Background}""/>
|
||||
<Setter Property=""BorderBrush"" Value=""{DynamicResource ComboBox.Static.Border}""/>
|
||||
<Setter Property=""Foreground"" Value=""{DynamicResource {x:Static SystemColors.WindowTextBrushKey}}""/>
|
||||
<Setter Property=""BorderThickness"" Value=""1""/>
|
||||
<Setter Property=""ScrollViewer.HorizontalScrollBarVisibility"" Value=""Auto""/>
|
||||
<Setter Property=""ScrollViewer.VerticalScrollBarVisibility"" Value=""Auto""/>
|
||||
<Setter Property=""Padding"" Value=""6,3,5,3""/>
|
||||
<Setter Property=""ScrollViewer.CanContentScroll"" Value=""true""/>
|
||||
<Setter Property=""Stylus.IsFlicksEnabled"" Value=""False""/>
|
||||
<Setter Property=""Template"" Value=""{DynamicResource ComboBoxTemplate}""/>
|
||||
<Style.Triggers>
|
||||
<Trigger Property=""IsEditable"" Value=""true"">
|
||||
<Setter Property=""IsTabStop"" Value=""false""/>
|
||||
<Setter Property=""Padding"" Value=""2""/>
|
||||
<Setter Property=""Template"" Value=""{DynamicResource ComboBoxEditableTemplate}""/>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>";
|
||||
|
||||
/// <summary>
|
||||
/// CustomProgressBarStyle Style XAML (.NET Framework 4.0 and above)
|
||||
/// </summary>
|
||||
private const string _customProgressBarStyleDefault = @"<Style x:Key=""CustomProgressBarStyle"" TargetType=""{x:Type ProgressBar}"">
|
||||
<Setter Property=""Foreground"" Value=""{DynamicResource ProgressBar.Progress}""/>
|
||||
<Setter Property=""Background"" Value=""{DynamicResource ProgressBar.Background}""/>
|
||||
<Setter Property=""BorderBrush"" Value=""{DynamicResource ProgressBar.Border}""/>
|
||||
<Setter Property=""BorderThickness"" Value=""1""/>
|
||||
<Setter Property=""Template"">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType=""{x:Type ProgressBar}"">
|
||||
<Grid x:Name=""TemplateRoot"">
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name=""CommonStates"">
|
||||
<VisualState x:Name=""Determinate""/>
|
||||
<VisualState x:Name=""Indeterminate"">
|
||||
<Storyboard RepeatBehavior=""Forever"">
|
||||
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty=""(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)"" Storyboard.TargetName=""Animation"">
|
||||
<EasingDoubleKeyFrame KeyTime=""0"" Value=""0.25""/>
|
||||
<EasingDoubleKeyFrame KeyTime=""0:0:1"" Value=""0.25""/>
|
||||
<EasingDoubleKeyFrame KeyTime=""0:0:2"" Value=""0.25""/>
|
||||
</DoubleAnimationUsingKeyFrames>
|
||||
<PointAnimationUsingKeyFrames Storyboard.TargetProperty=""(UIElement.RenderTransformOrigin)"" Storyboard.TargetName=""Animation"">
|
||||
<EasingPointKeyFrame KeyTime=""0"" Value=""-0.5,0.5""/>
|
||||
<EasingPointKeyFrame KeyTime=""0:0:1"" Value=""0.5,0.5""/>
|
||||
<EasingPointKeyFrame KeyTime=""0:0:2"" Value=""1.5,0.5""/>
|
||||
</PointAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<Border BorderBrush=""{TemplateBinding BorderBrush}"" BorderThickness=""{TemplateBinding BorderThickness}"" Background=""{TemplateBinding Background}""/>
|
||||
<Rectangle x:Name=""PART_Track""/>
|
||||
<Grid x:Name=""PART_Indicator"" ClipToBounds=""true"" HorizontalAlignment=""Left"">
|
||||
<Rectangle x:Name=""Indicator"" Fill=""{TemplateBinding Foreground}""/>
|
||||
<Rectangle x:Name=""Animation"" Fill=""{TemplateBinding Foreground}"" RenderTransformOrigin=""0.5,0.5"">
|
||||
<Rectangle.RenderTransform>
|
||||
<TransformGroup>
|
||||
<ScaleTransform/>
|
||||
<SkewTransform/>
|
||||
<RotateTransform/>
|
||||
<TranslateTransform/>
|
||||
</TransformGroup>
|
||||
</Rectangle.RenderTransform>
|
||||
</Rectangle>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property=""Orientation"" Value=""Vertical"">
|
||||
<Setter Property=""LayoutTransform"" TargetName=""TemplateRoot"">
|
||||
<Setter.Value>
|
||||
<RotateTransform Angle=""-90""/>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Trigger>
|
||||
<Trigger Property=""IsIndeterminate"" Value=""true"">
|
||||
<Setter Property=""Visibility"" TargetName=""Indicator"" Value=""Collapsed""/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>";
|
||||
|
||||
/// <summary>
|
||||
/// CustomProgressBarStyle Style XAML (.NET Framework 3.5)
|
||||
/// </summary>
|
||||
private const string _customProgressBarStyleNet35 = @"<Style TargetType=""{x:Type ProgressBar}"">
|
||||
<Setter Property=""Foreground"" Value=""{DynamicResource ProgressBar.Progress}""/>
|
||||
<Setter Property=""Background"" Value=""{DynamicResource ProgressBar.Background}""/>
|
||||
<Setter Property=""BorderBrush"" Value=""{DynamicResource ProgressBar.Border}""/>
|
||||
<Setter Property=""BorderThickness"" Value=""1""/>
|
||||
<Setter Property=""Template"">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType=""{x:Type ProgressBar}"">
|
||||
<Grid x:Name=""TemplateRoot"">
|
||||
<Border BorderBrush=""{TemplateBinding BorderBrush}"" BorderThickness=""{TemplateBinding BorderThickness}"" Background=""{TemplateBinding Background}""/>
|
||||
<Rectangle x:Name=""PART_Track""/>
|
||||
<Grid x:Name=""PART_Indicator"" ClipToBounds=""true"" HorizontalAlignment=""Left"">
|
||||
<Rectangle x:Name=""Indicator"" Fill=""{TemplateBinding Foreground}""/>
|
||||
<Rectangle x:Name=""Animation"" Fill=""{TemplateBinding Foreground}"" RenderTransformOrigin=""0.5,0.5"">
|
||||
<Rectangle.RenderTransform>
|
||||
<TransformGroup>
|
||||
<ScaleTransform/>
|
||||
<SkewTransform/>
|
||||
<RotateTransform/>
|
||||
<TranslateTransform/>
|
||||
</TransformGroup>
|
||||
</Rectangle.RenderTransform>
|
||||
</Rectangle>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property=""Orientation"" Value=""Vertical"">
|
||||
<Setter Property=""LayoutTransform"" TargetName=""TemplateRoot"">
|
||||
<Setter.Value>
|
||||
<RotateTransform Angle=""-90""/>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Trigger>
|
||||
<Trigger Property=""IsIndeterminate"" Value=""true"">
|
||||
<Setter Property=""Visibility"" TargetName=""Indicator"" Value=""Collapsed""/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>";
|
||||
|
||||
#endregion
|
||||
|
||||
public App()
|
||||
{
|
||||
#if NET40_OR_GREATER || NETCOREAPP
|
||||
InitializeComponent();
|
||||
#endif
|
||||
|
||||
// Create control templates
|
||||
CreateControlTemplate("ComboBoxTemplate");
|
||||
CreateControlTemplate("ComboBoxEditableTemplate");
|
||||
|
||||
// Create styles
|
||||
CreateStyle("ComboBoxEditableTextBox");
|
||||
CreateStyle("CustomComboBoxStyle");
|
||||
CreateStyle("CustomProgressBarStyle");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create an XAML parser context with the required namespaces
|
||||
/// </summary>
|
||||
private ParserContext CreateParserContext()
|
||||
{
|
||||
var context = new ParserContext();
|
||||
|
||||
context.XmlnsDictionary[""] = "http://schemas.microsoft.com/winfx/2006/xaml/presentation";
|
||||
context.XmlnsDictionary["x"] = "http://schemas.microsoft.com/winfx/2006/xaml";
|
||||
#if NETFRAMEWORK
|
||||
context.XmlnsDictionary["themes"] = "clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero";
|
||||
#else
|
||||
context.XmlnsDictionary["themes"] = "clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero2";
|
||||
#endif
|
||||
context.XamlTypeMapper = new XamlTypeMapper([]);
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a named control template and add it to the current set of resources
|
||||
/// </summary>
|
||||
private void CreateControlTemplate(string resourceName)
|
||||
{
|
||||
var parserContext = CreateParserContext();
|
||||
var controlTemplate = resourceName switch
|
||||
{
|
||||
#if NET35
|
||||
"ComboBoxTemplate" => XamlReader.Parse(_comboBoxTemplateNet35, parserContext) as ControlTemplate,
|
||||
"ComboBoxEditableTemplate" => XamlReader.Parse(_comboBoxEditableTemplateNet35, parserContext) as ControlTemplate,
|
||||
#else
|
||||
"ComboBoxTemplate" => XamlReader.Parse(_comboBoxTemplateDefault, parserContext) as ControlTemplate,
|
||||
"ComboBoxEditableTemplate" => XamlReader.Parse(_comboBoxEditableTemplateDefault, parserContext) as ControlTemplate,
|
||||
#endif
|
||||
_ => throw new ArgumentException($"'{resourceName}' is not a recognized control template", nameof(resourceName)),
|
||||
};
|
||||
|
||||
// Add the control template
|
||||
Resources[resourceName] = controlTemplate;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a named style and add it to the current set of resources
|
||||
/// </summary>
|
||||
private void CreateStyle(string resourceName)
|
||||
{
|
||||
var parserContext = CreateParserContext();
|
||||
var style = resourceName switch
|
||||
{
|
||||
#if NET35
|
||||
"ComboBoxEditableTextBox" => XamlReader.Parse(_comboBoxEditableTextBoxStyleNet35, parserContext) as Style,
|
||||
"CustomComboBoxStyle" => XamlReader.Parse(_customComboBoxStyleNet35, parserContext) as Style,
|
||||
"CustomProgressBarStyle" => XamlReader.Parse(_customProgressBarStyleNet35, parserContext) as Style,
|
||||
#else
|
||||
"ComboBoxEditableTextBox" => XamlReader.Parse(_comboBoxEditableTextBoxStyleDefault, parserContext) as Style,
|
||||
"CustomComboBoxStyle" => XamlReader.Parse(_customComboBoxStyleDefault, parserContext) as Style,
|
||||
"CustomProgressBarStyle" => XamlReader.Parse(_customProgressBarStyleDefault, parserContext) as Style,
|
||||
#endif
|
||||
_ => throw new ArgumentException($"'{resourceName}' is not a recognized style", nameof(resourceName)),
|
||||
};
|
||||
|
||||
// Add the style
|
||||
Resources[resourceName] = style;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
40
MPF.UI/Constants.cs
Normal file
40
MPF.UI/Constants.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Windows.Media;
|
||||
using static MPF.Frontend.InterfaceConstants;
|
||||
|
||||
namespace MPF.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// Variables for UI elements
|
||||
/// </summary>
|
||||
public static class Constants
|
||||
{
|
||||
/// <summary>
|
||||
/// Set of accepted speeds for CD and GD media
|
||||
/// </summary>
|
||||
public static DoubleCollection SpeedsForCDAsCollection => GetDoubleCollectionFromIntList(CD);
|
||||
|
||||
/// <summary>
|
||||
/// Set of accepted speeds for DVD media
|
||||
/// </summary>
|
||||
public static DoubleCollection SpeedsForDVDAsCollection => GetDoubleCollectionFromIntList(DVD);
|
||||
|
||||
/// <summary>
|
||||
/// Set of accepted speeds for HD-DVD media
|
||||
/// </summary>
|
||||
public static DoubleCollection SpeedsForHDDVDAsCollection => GetDoubleCollectionFromIntList(HDDVD);
|
||||
|
||||
/// <summary>
|
||||
/// Set of accepted speeds for BD media
|
||||
/// </summary>
|
||||
public static DoubleCollection SpeedsForBDAsCollection => GetDoubleCollectionFromIntList(BD);
|
||||
|
||||
/// <summary>
|
||||
/// Create a DoubleCollection out of a list of integer values
|
||||
/// </summary>
|
||||
private static DoubleCollection GetDoubleCollectionFromIntList(IList<int> list)
|
||||
=> new(list.Select(i => Convert.ToDouble(i)).ToList());
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user