mirror of
https://github.com/SabreTools/MPF.git
synced 2026-02-04 13:45:29 +00:00
Compare commits
2130 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
a1148f80c8 | ||
|
|
632654d00b | ||
|
|
a6d6b800a5 | ||
|
|
8527cc5746 | ||
|
|
f94f54f4d7 | ||
|
|
2091ef1d92 | ||
|
|
af865bca9e | ||
|
|
23588fa5ae | ||
|
|
e763fec01e | ||
|
|
732ff2ccca | ||
|
|
f960aca8a9 | ||
|
|
335c1388d4 | ||
|
|
87c0d5b44b | ||
|
|
1a44fb5c8a | ||
|
|
285921cae3 | ||
|
|
2255dba640 | ||
|
|
99158f31c9 | ||
|
|
53a2ef227b | ||
|
|
c0efc63741 | ||
|
|
905bd1a7dc | ||
|
|
5d49d4833f | ||
|
|
8fd3e2d405 | ||
|
|
1521a918f3 | ||
|
|
5f580ee025 | ||
|
|
9c7cee0aa3 | ||
|
|
d549609a0f | ||
|
|
207fdbb49c | ||
|
|
63e63a0033 | ||
|
|
73be55a645 | ||
|
|
d913aa247e | ||
|
|
d9a9f3bfd4 | ||
|
|
ea3e040b3f | ||
|
|
ed67522675 | ||
|
|
6e3902bbb1 | ||
|
|
3f802e8548 | ||
|
|
c3c4cf6e5b | ||
|
|
39258c4e85 | ||
|
|
df81259fba | ||
|
|
a835c81951 | ||
|
|
af4f3e8444 | ||
|
|
085916e1e6 | ||
|
|
557354c844 | ||
|
|
a23a74ffa4 | ||
|
|
958aeeae32 | ||
|
|
c73ff42500 | ||
|
|
24a93e298b | ||
|
|
ae81bb0743 | ||
|
|
cf7863fbb2 | ||
|
|
5e0e71120d | ||
|
|
5adbdd29e6 | ||
|
|
fd855b9024 | ||
|
|
17c283ff67 | ||
|
|
a37e6aaf18 | ||
|
|
1dbf23ca95 | ||
|
|
2e52d00107 | ||
|
|
eb69b74b4a | ||
|
|
db4a785558 | ||
|
|
9bdd2d6951 | ||
|
|
de48c8f0f9 | ||
|
|
c59f7c2dae | ||
|
|
2740a6a1c1 | ||
|
|
56e8470069 | ||
|
|
a5f1a12aee | ||
|
|
8749b486a8 | ||
|
|
3446957f68 | ||
|
|
cb9813db3d | ||
|
|
6e6b716c18 | ||
|
|
400ef38c60 | ||
|
|
87d5fc0e66 | ||
|
|
d5d8ae0501 | ||
|
|
fae7a5254d | ||
|
|
97be13f020 | ||
|
|
dd0dfc5cf5 | ||
|
|
a7a295434d | ||
|
|
319f5563cb | ||
|
|
939174d8c4 | ||
|
|
1d5d7b265e | ||
|
|
a2ac7222f9 | ||
|
|
4f0b5904c3 | ||
|
|
7ff848347c | ||
|
|
a1c89d17b8 | ||
|
|
3575c43703 | ||
|
|
928c9966fa | ||
|
|
a404a270f1 | ||
|
|
dfba97685a | ||
|
|
8de2f1c9bb | ||
|
|
f57b29615b | ||
|
|
565a9bb09a | ||
|
|
d9d61cf665 | ||
|
|
106f16d703 | ||
|
|
8b600a6a35 | ||
|
|
b251ae7927 | ||
|
|
3a749b67e3 | ||
|
|
3abc277aab | ||
|
|
894f2c6fde | ||
|
|
8c43615235 | ||
|
|
bbf51a7f94 | ||
|
|
515e58fae8 | ||
|
|
f3319f4ee2 | ||
|
|
e2789ddd11 | ||
|
|
1c04f6e30c | ||
|
|
c7d4d319ce |
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://ci.appveyor.com/project/mnadareski/mpf/build/artifacts) to see if the feature already exists.
|
||||
- Is it copy protection related? If so, report the issue [here](https://github.com/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://ci.appveyor.com/project/mnadareski/mpf/build/artifacts) to see if the feature already exists.
|
||||
- Is it copy protection related? If so, report the issue [here](https://github.com/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.
|
||||
49
.github/ISSUE_TEMPLATE/issue-report.md
vendored
Normal file
49
.github/ISSUE_TEMPLATE/issue-report.md
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
---
|
||||
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://ci.appveyor.com/project/mnadareski/mpf/build/artifacts) to see if the issue has already been addressed.
|
||||
- Is it copy protection related? If so, report the issue [here](https://github.com/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 6.0 running on (Operating System)
|
||||
- [ ] .NET 8.0 running on (Operating System)
|
||||
|
||||
**Describe the issue**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
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.
|
||||
50
.github/workflows/build_check.yml
vendored
Normal file
50
.github/workflows/build_check.yml
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
name: MPF Check
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "master" ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
project: [MPF.Check]
|
||||
runtime: [win-x86, win-x64, win-arm64, linux-x64, linux-arm64, osx-x64, osx-arm64]
|
||||
framework: [net8.0] #[net20, net35, net40, net452, net472, net48, netcoreapp3.1, net5.0, net6.0, net7.0, net8.0]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: 8.0.x
|
||||
|
||||
- name: Restore dependencies
|
||||
run: dotnet restore
|
||||
|
||||
- name: Build
|
||||
run: dotnet publish ${{ matrix.project }}/${{ matrix.project }}.csproj -f ${{ matrix.framework }} -r ${{ matrix.runtime }} -c Debug --self-contained true --version-suffix ${{ github.sha }} ${{ (startsWith(matrix.framework, 'net5') || startsWith(matrix.framework, 'net6') || startsWith(matrix.framework, 'net7') || startsWith(matrix.framework, 'net8')) && '-p:PublishSingleFile=true' || ''}}
|
||||
|
||||
- name: Archive build
|
||||
run: zip -r ${{ matrix.project }}_${{ matrix.framework }}_${{ matrix.runtime }}_debug.zip ${{ matrix.project }}/bin/Debug/${{ matrix.framework }}/${{ matrix.runtime }}/publish/
|
||||
|
||||
- name: Upload build
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.project }}_${{ matrix.framework }}_${{ matrix.runtime }}_debug
|
||||
path: ${{ matrix.project }}_${{ matrix.framework }}_${{ matrix.runtime }}_debug.zip
|
||||
|
||||
- name: Upload to rolling
|
||||
uses: ncipollo/release-action@v1.14.0
|
||||
with:
|
||||
allowUpdates: True
|
||||
artifacts: ${{ matrix.project }}_${{ matrix.framework }}_${{ matrix.runtime }}_debug.zip
|
||||
body: 'Last built commit: ${{ github.sha }}'
|
||||
name: 'Rolling Release'
|
||||
prerelease: True
|
||||
replacesArtifacts: True
|
||||
tag: "rolling"
|
||||
updateOnlyUnreleased: True
|
||||
50
.github/workflows/build_cli.yml
vendored
Normal file
50
.github/workflows/build_cli.yml
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
name: MPF CLI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "master" ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
project: [MPF.CLI]
|
||||
runtime: [win-x86, win-x64, win-arm64, linux-x64, linux-arm64, osx-x64, osx-arm64]
|
||||
framework: [net8.0] #[net20, net35, net40, net452, net472, net48, netcoreapp3.1, net5.0, net6.0, net7.0, net8.0]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: 8.0.x
|
||||
|
||||
- name: Restore dependencies
|
||||
run: dotnet restore
|
||||
|
||||
- name: Build
|
||||
run: dotnet publish ${{ matrix.project }}/${{ matrix.project }}.csproj -f ${{ matrix.framework }} -r ${{ matrix.runtime }} -c Debug --self-contained true --version-suffix ${{ github.sha }} ${{ (startsWith(matrix.framework, 'net5') || startsWith(matrix.framework, 'net6') || startsWith(matrix.framework, 'net7') || startsWith(matrix.framework, 'net8')) && '-p:PublishSingleFile=true' || ''}}
|
||||
|
||||
- name: Archive build
|
||||
run: zip -r ${{ matrix.project }}_${{ matrix.framework }}_${{ matrix.runtime }}_debug.zip ${{ matrix.project }}/bin/Debug/${{ matrix.framework }}/${{ matrix.runtime }}/publish/
|
||||
|
||||
- name: Upload build
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.project }}_${{ matrix.framework }}_${{ matrix.runtime }}_debug
|
||||
path: ${{ matrix.project }}_${{ matrix.framework }}_${{ matrix.runtime }}_debug.zip
|
||||
|
||||
- name: Upload to rolling
|
||||
uses: ncipollo/release-action@v1.14.0
|
||||
with:
|
||||
allowUpdates: True
|
||||
artifacts: ${{ matrix.project }}_${{ matrix.framework }}_${{ matrix.runtime }}_debug.zip
|
||||
body: 'Last built commit: ${{ github.sha }}'
|
||||
name: 'Rolling Release'
|
||||
prerelease: True
|
||||
replacesArtifacts: True
|
||||
tag: "rolling"
|
||||
updateOnlyUnreleased: True
|
||||
61
.github/workflows/build_nupkg.yml
vendored
Normal file
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
|
||||
64
.github/workflows/build_ui.yml
vendored
Normal file
64
.github/workflows/build_ui.yml
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
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]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: 8.0.x
|
||||
|
||||
- name: Restore dependencies
|
||||
run: dotnet restore
|
||||
|
||||
- name: Build
|
||||
run: dotnet publish ${{ matrix.project }}/${{ matrix.project }}.csproj -f ${{ matrix.framework }} -r ${{ matrix.runtime }} -c Debug --self-contained true --version-suffix ${{ github.sha }} ${{ (startsWith(matrix.framework, 'net5') || startsWith(matrix.framework, 'net6') || startsWith(matrix.framework, 'net7') || startsWith(matrix.framework, 'net8')) && '-p:PublishSingleFile=true' || ''}}
|
||||
|
||||
- name: Bundle DiscImageCreator
|
||||
run: |
|
||||
wget https://github.com/user-attachments/files/15521936/DiscImageCreator_20240601.zip
|
||||
unzip -u DiscImageCreator_20240601.zip
|
||||
mkdir -p MPF.UI/bin/Debug/${{ matrix.framework }}/${{ matrix.runtime }}/publish/Programs/Creator
|
||||
mv Release_ANSI/* MPF.UI/bin/Debug/${{ matrix.framework }}/${{ matrix.runtime }}/publish/Programs/Creator/
|
||||
|
||||
- name: Bundle Redumper
|
||||
run: |
|
||||
wget https://github.com/superg/redumper/releases/download/build_371/redumper-2024.05.27_build371-win64.zip
|
||||
unzip redumper-2024.05.27_build371-win64.zip
|
||||
mkdir -p MPF.UI/bin/Debug/${{ matrix.framework }}/${{ matrix.runtime }}/publish/Programs/Redumper
|
||||
mv redumper-2024.05.27_build371-win64/bin/redumper.exe MPF.UI/bin/Debug/${{ matrix.framework }}/${{ matrix.runtime }}/publish/Programs/Redumper/
|
||||
|
||||
- name: Archive build
|
||||
run: zip -r ${{ matrix.project }}_${{ matrix.framework }}_${{ matrix.runtime }}_debug.zip ${{ matrix.project }}/bin/Debug/${{ matrix.framework }}/${{ matrix.runtime }}/publish/
|
||||
|
||||
- name: Upload build
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.project }}_${{ matrix.framework }}_${{ matrix.runtime }}_debug
|
||||
path: ${{ matrix.project }}_${{ matrix.framework }}_${{ matrix.runtime }}_debug.zip
|
||||
|
||||
- name: Upload to rolling
|
||||
uses: ncipollo/release-action@v1.14.0
|
||||
with:
|
||||
allowUpdates: True
|
||||
artifacts: ${{ matrix.project }}_${{ matrix.framework }}_${{ matrix.runtime }}_debug.zip
|
||||
body: 'Last built commit: ${{ github.sha }}'
|
||||
name: 'Rolling Release'
|
||||
prerelease: True
|
||||
replacesArtifacts: True
|
||||
tag: "rolling"
|
||||
updateOnlyUnreleased: True
|
||||
23
.github/workflows/check_pr.yml
vendored
Normal file
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"
|
||||
}
|
||||
]
|
||||
}
|
||||
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
|
||||
{
|
||||
}
|
||||
}
|
||||
1586
CHANGELIST.md
Normal file
1586
CHANGELIST.md
Normal file
File diff suppressed because it is too large
Load Diff
145
DICUI.csproj
145
DICUI.csproj
@@ -1,145 +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.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>
|
||||
<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="MainWindow.xaml.cs">
|
||||
<DependentUpon>MainWindow.xaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Properties\AssemblyInfo.cs">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Properties\Resources.Designer.cs">
|
||||
<AutoGen>True</AutoGen>
|
||||
<DesignTime>True</DesignTime>
|
||||
<DependentUpon>Resources.resx</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Properties\Settings.Designer.cs">
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Settings.settings</DependentUpon>
|
||||
<DesignTimeSharedInput>True</DesignTimeSharedInput>
|
||||
</Compile>
|
||||
<EmbeddedResource Include="Properties\Resources.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
<None Include="Properties\Settings.settings">
|
||||
<Generator>SettingsSingleFileGenerator</Generator>
|
||||
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
|
||||
</None>
|
||||
</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
|
||||
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.1</VersionPrefix>
|
||||
|
||||
<!-- Package Properties -->
|
||||
<Title>MPF CLI</Title>
|
||||
<Authors>Matt Nadareski;ReignStumble;Jakz</Authors>
|
||||
<Description>CLI frontend for various dumping programs</Description>
|
||||
<Copyright>Copyright (c) Matt Nadareski 2019-2024</Copyright>
|
||||
<PackageProjectUrl>https://github.com/SabreTools/</PackageProjectUrl>
|
||||
<RepositoryUrl>https://github.com/SabreTools/MPF</RepositoryUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Support All Frameworks -->
|
||||
<PropertyGroup Condition="$(TargetFramework.StartsWith(`net2`)) OR $(TargetFramework.StartsWith(`net3`)) OR $(TargetFramework.StartsWith(`net4`))">
|
||||
<RuntimeIdentifiers>win-x86;win-x64</RuntimeIdentifiers>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="$(TargetFramework.StartsWith(`netcoreapp`)) OR $(TargetFramework.StartsWith(`net5`))">
|
||||
<RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64</RuntimeIdentifiers>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="$(TargetFramework.StartsWith(`net6`)) OR $(TargetFramework.StartsWith(`net7`)) OR $(TargetFramework.StartsWith(`net8`))">
|
||||
<RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64;osx-arm64</RuntimeIdentifiers>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="$(RuntimeIdentifier.StartsWith(`osx-arm`))">
|
||||
<TargetFrameworks>net6.0;net7.0;net8.0</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Support for old .NET versions -->
|
||||
<ItemGroup Condition="$(TargetFramework.StartsWith(`net2`)) OR $(TargetFramework.StartsWith(`net3`)) OR $(TargetFramework.StartsWith(`net40`))">
|
||||
<PackageReference Include="MinAsyncBridge" Version="0.12.4" />
|
||||
<PackageReference Include="MinTasksExtensionsBridge" Version="0.3.4" />
|
||||
<PackageReference Include="MinThreadingBridge" Version="0.11.4" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MPF.Frontend\MPF.Frontend.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BinaryObjectScanner" PrivateAssets="build; analyzers" ExcludeAssets="contentFiles" Version="3.1.13" GeneratePathProperty="true">
|
||||
<IncludeAssets>runtime; compile; build; native; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="SabreTools.RedumpLib" Version="1.4.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="$(PkgBinaryObjectScanner)\content\**" PackagePath="contentFiles\any\any;content" CopyToOutputDirectory="Always" PackageCopyToOutput="true" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
305
MPF.CLI/Program.cs
Normal file
305
MPF.CLI/Program.cs
Normal file
@@ -0,0 +1,305 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
#if NET40
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
#endif
|
||||
using BinaryObjectScanner;
|
||||
using MPF.Frontend;
|
||||
using MPF.Frontend.Tools;
|
||||
using SabreTools.RedumpLib.Data;
|
||||
using SabreTools.RedumpLib.Web;
|
||||
|
||||
namespace MPF.CLI
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
// Load options from the config file
|
||||
var options = OptionsLoader.LoadFromConfig();
|
||||
if (options.FirstRun)
|
||||
{
|
||||
// Application paths
|
||||
options.AaruPath = "FILL ME IN";
|
||||
options.DiscImageCreatorPath = "FILL ME IN";
|
||||
options.RedumperPath = "FILL ME IN";
|
||||
options.InternalProgram = InternalProgram.NONE;
|
||||
|
||||
// Reset first run
|
||||
options.FirstRun = false;
|
||||
OptionsLoader.SaveToConfig(options, saveDefault: true);
|
||||
}
|
||||
|
||||
// Try processing the standalone arguments
|
||||
bool? standaloneProcessed = OptionsLoader.ProcessStandaloneArguments(args);
|
||||
if (standaloneProcessed != false)
|
||||
{
|
||||
if (standaloneProcessed == null)
|
||||
DisplayHelp();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for the minimum number of arguments
|
||||
if (args.Length < 4)
|
||||
{
|
||||
DisplayHelp("Not enough arguments have been provided, exiting...");
|
||||
return;
|
||||
}
|
||||
|
||||
// Try processing the common arguments
|
||||
(bool success, MediaType mediaType, RedumpSystem? knownSystem, var error) = OptionsLoader.ProcessCommonArguments(args);
|
||||
if (!success)
|
||||
{
|
||||
DisplayHelp(error);
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate the internal program
|
||||
switch (options.InternalProgram)
|
||||
{
|
||||
case InternalProgram.Aaru:
|
||||
if (!File.Exists(options.AaruPath))
|
||||
{
|
||||
DisplayHelp("A path needs to be supplied for Aaru, exiting...");
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case InternalProgram.DiscImageCreator:
|
||||
if (!File.Exists(options.DiscImageCreatorPath))
|
||||
{
|
||||
DisplayHelp("A path needs to be supplied for DIC, exiting...");
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case InternalProgram.Redumper:
|
||||
if (!File.Exists(options.RedumperPath))
|
||||
{
|
||||
DisplayHelp("A path needs to be supplied for Redumper, exiting...");
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
DisplayHelp($"{options.InternalProgram} is not a supported dumping program, exiting...");
|
||||
break;
|
||||
}
|
||||
|
||||
// Make new Progress objects
|
||||
var resultProgress = new Progress<ResultEventArgs>();
|
||||
resultProgress.ProgressChanged += ConsoleLogger.ProgressUpdated;
|
||||
var protectionProgress = new Progress<ProtectionProgress>();
|
||||
protectionProgress.ProgressChanged += ConsoleLogger.ProgressUpdated;
|
||||
|
||||
// Validate the supplied credentials
|
||||
(bool? _, string? message) = RedumpClient.ValidateCredentials(options.RedumpUsername ?? string.Empty, options.RedumpPassword ?? string.Empty).GetAwaiter().GetResult();
|
||||
if (!string.IsNullOrEmpty(message))
|
||||
Console.WriteLine(message);
|
||||
|
||||
// Process any custom parameters
|
||||
(CommandOptions opts, int startIndex) = LoadFromArguments(args, options, startIndex: 2);
|
||||
|
||||
// Ensure we have the values we need
|
||||
if (opts.CustomParams == null && (opts.DevicePath == null || opts.DevicePath == null))
|
||||
{
|
||||
DisplayHelp("Both a device path and file path need to be supplied, exiting...");
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the speed from the options
|
||||
int speed = opts.DriveSpeed ?? FrontendTool.GetDefaultSpeedForMediaType(mediaType, options);
|
||||
|
||||
// Populate an environment
|
||||
var drive = Drive.Create(null, opts.DevicePath ?? string.Empty);
|
||||
var env = new DumpEnvironment(options, opts.FilePath, drive, knownSystem, mediaType, options.InternalProgram, parameters: null);
|
||||
|
||||
// Process the parameters
|
||||
string? paramStr = opts.CustomParams ?? env.GetFullParameters(speed);
|
||||
if (string.IsNullOrEmpty(paramStr))
|
||||
{
|
||||
DisplayHelp("No valid environment could be created, exiting...");
|
||||
return;
|
||||
}
|
||||
env.SetExecutionContext(paramStr);
|
||||
|
||||
// Invoke the dumping program
|
||||
Console.WriteLine($"Invoking {options.InternalProgram} using '{paramStr}'");
|
||||
var dumpResult = env.Run(resultProgress).GetAwaiter().GetResult();
|
||||
Console.WriteLine(dumpResult.Message);
|
||||
if (!dumpResult)
|
||||
return;
|
||||
|
||||
// If it was not a dumping command
|
||||
if (!env.IsDumpingCommand())
|
||||
{
|
||||
Console.WriteLine("Execution not recognized as dumping command, skipping processing...");
|
||||
return;
|
||||
}
|
||||
|
||||
// Finally, attempt to do the output dance
|
||||
#if NET40
|
||||
var verifyTask = env.VerifyAndSaveDumpOutput(resultProgress, protectionProgress);
|
||||
verifyTask.Wait();
|
||||
var verifyResult = verifyTask.Result;
|
||||
#else
|
||||
var verifyResult = env.VerifyAndSaveDumpOutput(resultProgress, protectionProgress).ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
#endif
|
||||
Console.WriteLine(verifyResult.Message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Display help for MPF.CLI
|
||||
/// </summary>
|
||||
/// <param name="error">Error string to prefix the help text with</param>
|
||||
private static void DisplayHelp(string? error = null)
|
||||
{
|
||||
if (error != null)
|
||||
Console.WriteLine(error);
|
||||
|
||||
Console.WriteLine("Usage:");
|
||||
Console.WriteLine("MPF.CLI <mediatype> <system> [options]");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Standalone Options:");
|
||||
Console.WriteLine("-h, -? Show this help text");
|
||||
Console.WriteLine("-lc, --listcodes List supported comment/content site codes");
|
||||
Console.WriteLine("-lm, --listmedia List supported media types");
|
||||
Console.WriteLine("-ls, --listsystems List supported system types");
|
||||
Console.WriteLine("-lp, --listprograms List supported dumping program outputs");
|
||||
Console.WriteLine();
|
||||
|
||||
Console.WriteLine("CLI Options:");
|
||||
Console.WriteLine("-u, --use <program> Override default dumping program");
|
||||
Console.WriteLine("-d, --device <devicepath> Physical drive path (Required if no custom parameters set)");
|
||||
Console.WriteLine("-f, --file \"<filepath>\" Output file path (Required if no custom parameters set)");
|
||||
Console.WriteLine("-s, --speed <speed> Override default dumping speed");
|
||||
Console.WriteLine("-c, --custom \"<params>\" Custom parameters to use");
|
||||
Console.WriteLine();
|
||||
|
||||
Console.WriteLine("Custom parameters, if used, will fully replace the default parameters.");
|
||||
Console.WriteLine("All parameters need to be supplied if doing this.");
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load the current set of options from application arguments
|
||||
/// </summary>
|
||||
private static (CommandOptions, int) LoadFromArguments(string[] args, Frontend.Options options, int startIndex = 0)
|
||||
{
|
||||
// Create return values
|
||||
var opts = new CommandOptions();
|
||||
|
||||
// If we have no arguments, just return
|
||||
if (args == null || args.Length == 0)
|
||||
return (opts, 0);
|
||||
|
||||
// If we have an invalid start index, just return
|
||||
if (startIndex < 0 || startIndex >= args.Length)
|
||||
return (opts, startIndex);
|
||||
|
||||
// Loop through the arguments and parse out values
|
||||
for (; startIndex < args.Length; startIndex++)
|
||||
{
|
||||
// Use specific program
|
||||
if (args[startIndex].StartsWith("-u=") || args[startIndex].StartsWith("--use="))
|
||||
{
|
||||
string internalProgram = args[startIndex].Split('=')[1];
|
||||
options.InternalProgram = Frontend.Options.ToInternalProgram(internalProgram);
|
||||
}
|
||||
else if (args[startIndex] == "-u" || args[startIndex] == "--use")
|
||||
{
|
||||
string internalProgram = args[startIndex + 1];
|
||||
options.InternalProgram = Frontend.Options.ToInternalProgram(internalProgram);
|
||||
startIndex++;
|
||||
}
|
||||
|
||||
// Use a device path
|
||||
else if (args[startIndex].StartsWith("-d=") || args[startIndex].StartsWith("--device="))
|
||||
{
|
||||
opts.DevicePath = args[startIndex].Split('=')[1].Trim('"');
|
||||
}
|
||||
else if (args[startIndex] == "-d" || args[startIndex] == "--device")
|
||||
{
|
||||
opts.DevicePath = args[startIndex + 1].Trim('"');
|
||||
startIndex++;
|
||||
}
|
||||
|
||||
// Use a file path
|
||||
else if (args[startIndex].StartsWith("-f=") || args[startIndex].StartsWith("--file="))
|
||||
{
|
||||
opts.FilePath = args[startIndex].Split('=')[1].Trim('"');
|
||||
}
|
||||
else if (args[startIndex] == "-f" || args[startIndex] == "--file")
|
||||
{
|
||||
opts.FilePath = args[startIndex + 1].Trim('"');
|
||||
startIndex++;
|
||||
}
|
||||
|
||||
// Set an override speed
|
||||
else if (args[startIndex].StartsWith("-s=") || args[startIndex].StartsWith("--speed="))
|
||||
{
|
||||
if (!int.TryParse(args[startIndex].Split('=')[1].Trim('"'), out int speed))
|
||||
speed = -1;
|
||||
|
||||
opts.DriveSpeed = speed;
|
||||
}
|
||||
else if (args[startIndex] == "-s" || args[startIndex] == "--speed")
|
||||
{
|
||||
if (!int.TryParse(args[startIndex + 1].Trim('"'), out int speed))
|
||||
speed = -1;
|
||||
|
||||
opts.DriveSpeed = speed;
|
||||
startIndex++;
|
||||
}
|
||||
|
||||
// Use a custom parameters
|
||||
else if (args[startIndex].StartsWith("-c=") || args[startIndex].StartsWith("--custom="))
|
||||
{
|
||||
opts.CustomParams = args[startIndex].Split('=')[1].Trim('"');
|
||||
}
|
||||
else if (args[startIndex] == "-c" || args[startIndex] == "--custom")
|
||||
{
|
||||
opts.CustomParams = args[startIndex + 1].Trim('"');
|
||||
startIndex++;
|
||||
}
|
||||
|
||||
// Default, we fall out
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return (opts, startIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents commandline options
|
||||
/// </summary>
|
||||
private class CommandOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Path to the device to dump
|
||||
/// </summary>
|
||||
/// <remarks>Required if custom parameters are not set</remarks>
|
||||
public string? DevicePath { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Path to the output file
|
||||
/// </summary>
|
||||
/// <remarks>Required if custom parameters are not set</remarks>
|
||||
public string? FilePath { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Override drive speed
|
||||
/// </summary>
|
||||
public int? DriveSpeed { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Custom parameters for dumping
|
||||
/// </summary>
|
||||
public string? CustomParams { get; set; } = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.2"/>
|
||||
</startup>
|
||||
</configuration>
|
||||
</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.1</VersionPrefix>
|
||||
|
||||
<!-- Package Properties -->
|
||||
<Title>MPF Check</Title>
|
||||
<Authors>Matt Nadareski;ReignStumble;Jakz</Authors>
|
||||
<Description>Validator for various dumping programs</Description>
|
||||
<Copyright>Copyright (c) Matt Nadareski 2019-2024</Copyright>
|
||||
<PackageProjectUrl>https://github.com/SabreTools/</PackageProjectUrl>
|
||||
<RepositoryUrl>https://github.com/SabreTools/MPF</RepositoryUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- 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>
|
||||
|
||||
<!-- Support for old .NET versions -->
|
||||
<ItemGroup Condition="$(TargetFramework.StartsWith(`net2`)) OR $(TargetFramework.StartsWith(`net3`)) OR $(TargetFramework.StartsWith(`net40`))">
|
||||
<PackageReference Include="MinAsyncBridge" Version="0.12.4" />
|
||||
<PackageReference Include="MinTasksExtensionsBridge" Version="0.3.4" />
|
||||
<PackageReference Include="MinThreadingBridge" Version="0.11.4" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MPF.Frontend\MPF.Frontend.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BinaryObjectScanner" PrivateAssets="build; analyzers" ExcludeAssets="contentFiles" Version="3.1.13" GeneratePathProperty="true">
|
||||
<IncludeAssets>runtime; compile; build; native; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="SabreTools.RedumpLib" Version="1.4.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="$(PkgBinaryObjectScanner)\content\**" PackagePath="contentFiles\any\any;content" CopyToOutputDirectory="Always" PackageCopyToOutput="true" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
282
MPF.Check/Program.cs
Normal file
282
MPF.Check/Program.cs
Normal file
@@ -0,0 +1,282 @@
|
||||
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()
|
||||
{
|
||||
RedumpUsername = null,
|
||||
RedumpPassword = null,
|
||||
InternalProgram = InternalProgram.NONE,
|
||||
AddFilenameSuffix = false,
|
||||
OutputSubmissionJSON = false,
|
||||
IncludeArtifacts = false,
|
||||
CompressLogFiles = false,
|
||||
DeleteUnnecessaryFiles = false,
|
||||
};
|
||||
|
||||
// Try processing the standalone arguments
|
||||
bool? standaloneProcessed = OptionsLoader.ProcessStandaloneArguments(args);
|
||||
if (standaloneProcessed != false)
|
||||
{
|
||||
if (standaloneProcessed == null)
|
||||
DisplayHelp();
|
||||
return;
|
||||
}
|
||||
|
||||
// Try processing the common arguments
|
||||
(bool success, MediaType mediaType, RedumpSystem? knownSystem, var error) = OptionsLoader.ProcessCommonArguments(args);
|
||||
if (!success)
|
||||
{
|
||||
DisplayHelp(error);
|
||||
return;
|
||||
}
|
||||
|
||||
// Loop through and process options
|
||||
(CommandOptions opts, int startIndex) = LoadFromArguments(args, options, startIndex: 2);
|
||||
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? _, string? message) = RedumpClient.ValidateCredentials(options.RedumpUsername ?? string.Empty, options.RedumpPassword ?? string.Empty).GetAwaiter().GetResult();
|
||||
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("-c, --credentials <user> <pw> Redump username and password");
|
||||
Console.WriteLine(" --pull-all Pull all information from Redump (requires --credentials)");
|
||||
Console.WriteLine("-p, --path <drivepath> Physical drive path for additional checks");
|
||||
Console.WriteLine("-s, --scan Enable copy protection scan (requires --path)");
|
||||
Console.WriteLine(" --hide-drive-letters Hide drive letters from scan output (requires --scan)");
|
||||
Console.WriteLine("-l, --load-seed <path> Load a seed submission JSON for user information");
|
||||
Console.WriteLine("-x, --suffix Enable adding filename suffix");
|
||||
Console.WriteLine("-j, --json Enable submission JSON output");
|
||||
Console.WriteLine(" --include-artifacts Include artifacts in JSON (requires --json)");
|
||||
Console.WriteLine("-z, --zip Enable log file compression");
|
||||
Console.WriteLine("-d, --delete Enable unnecessary file deletion");
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load the current set of options from application arguments
|
||||
/// </summary>
|
||||
private static (CommandOptions, int) LoadFromArguments(string[] args, Frontend.Options options, int startIndex = 0)
|
||||
{
|
||||
// Create return values
|
||||
var opts = new CommandOptions();
|
||||
|
||||
// These values require multiple parts to be active
|
||||
bool scan = false, hideDriveLetters = false;
|
||||
|
||||
// If we have no arguments, just return
|
||||
if (args == null || args.Length == 0)
|
||||
return (opts, 0);
|
||||
|
||||
// If we have an invalid start index, just return
|
||||
if (startIndex < 0 || startIndex >= args.Length)
|
||||
return (opts, startIndex);
|
||||
|
||||
// Loop through the arguments and parse out values
|
||||
for (; startIndex < args.Length; startIndex++)
|
||||
{
|
||||
// Use specific program
|
||||
if (args[startIndex].StartsWith("-u=") || args[startIndex].StartsWith("--use="))
|
||||
{
|
||||
string internalProgram = args[startIndex].Split('=')[1];
|
||||
options.InternalProgram = Frontend.Options.ToInternalProgram(internalProgram);
|
||||
}
|
||||
else if (args[startIndex] == "-u" || args[startIndex] == "--use")
|
||||
{
|
||||
string internalProgram = args[startIndex + 1];
|
||||
options.InternalProgram = Frontend.Options.ToInternalProgram(internalProgram);
|
||||
startIndex++;
|
||||
}
|
||||
|
||||
// Redump login
|
||||
else if (args[startIndex].StartsWith("-c=") || args[startIndex].StartsWith("--credentials="))
|
||||
{
|
||||
string[] credentials = args[startIndex].Split('=')[1].Split(';');
|
||||
options.RedumpUsername = credentials[0];
|
||||
options.RedumpPassword = credentials[1];
|
||||
}
|
||||
else if (args[startIndex] == "-c" || args[startIndex] == "--credentials")
|
||||
{
|
||||
options.RedumpUsername = args[startIndex + 1];
|
||||
options.RedumpPassword = args[startIndex + 2];
|
||||
startIndex += 2;
|
||||
}
|
||||
|
||||
// Pull all information (requires Redump login)
|
||||
else if (args[startIndex].Equals("--pull-all"))
|
||||
{
|
||||
options.PullAllInformation = true;
|
||||
}
|
||||
|
||||
// Use a device path for physical checks
|
||||
else if (args[startIndex].StartsWith("-p=") || args[startIndex].StartsWith("--path="))
|
||||
{
|
||||
opts.DevicePath = args[startIndex].Split('=')[1];
|
||||
}
|
||||
else if (args[startIndex] == "-p" || args[startIndex] == "--path")
|
||||
{
|
||||
opts.DevicePath = args[startIndex + 1];
|
||||
startIndex++;
|
||||
}
|
||||
|
||||
// Scan for protection (requires device path)
|
||||
else if (args[startIndex].Equals("-s") || args[startIndex].Equals("--scan"))
|
||||
{
|
||||
scan = true;
|
||||
}
|
||||
|
||||
// Hide drive letters from scan output (requires --scan)
|
||||
else if (args[startIndex].Equals("--hide-drive-letters"))
|
||||
{
|
||||
hideDriveLetters = true;
|
||||
}
|
||||
|
||||
// Include seed info file
|
||||
else if (args[startIndex].StartsWith("-l=") || args[startIndex].StartsWith("--load-seed="))
|
||||
{
|
||||
string seedInfo = args[startIndex].Split('=')[1];
|
||||
opts.Seed = Builder.CreateFromFile(seedInfo);
|
||||
}
|
||||
else if (args[startIndex] == "-l" || args[startIndex] == "--load-seed")
|
||||
{
|
||||
string seedInfo = args[startIndex + 1];
|
||||
opts.Seed = Builder.CreateFromFile(seedInfo);
|
||||
startIndex++;
|
||||
}
|
||||
|
||||
// Add filename suffix
|
||||
else if (args[startIndex].Equals("-x") || args[startIndex].Equals("--suffix"))
|
||||
{
|
||||
options.AddFilenameSuffix = true;
|
||||
}
|
||||
|
||||
// Output submission JSON
|
||||
else if (args[startIndex].Equals("-j") || args[startIndex].Equals("--json"))
|
||||
{
|
||||
options.OutputSubmissionJSON = true;
|
||||
}
|
||||
|
||||
// Output submission JSON
|
||||
else if (args[startIndex].Equals("--include-artifacts"))
|
||||
{
|
||||
options.IncludeArtifacts = true;
|
||||
}
|
||||
|
||||
// Compress log and extraneous files
|
||||
else if (args[startIndex].Equals("-z") || args[startIndex].Equals("--zip"))
|
||||
{
|
||||
options.CompressLogFiles = true;
|
||||
}
|
||||
|
||||
// Delete unnecessary files files
|
||||
else if (args[startIndex].Equals("-d") || args[startIndex].Equals("--delete"))
|
||||
{
|
||||
options.DeleteUnnecessaryFiles = true;
|
||||
}
|
||||
|
||||
// Default, we fall out
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Now deal with the complex options
|
||||
options.ScanForProtection = scan && !string.IsNullOrEmpty(opts.DevicePath);
|
||||
options.HideDriveLetters = hideDriveLetters && scan && !string.IsNullOrEmpty(opts.DevicePath);
|
||||
|
||||
return (opts, startIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents commandline options
|
||||
/// </summary>
|
||||
private class CommandOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Seed submission info from an input file
|
||||
/// </summary>
|
||||
public SubmissionInfo? Seed { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Path to the device to scan
|
||||
/// </summary>
|
||||
public string? DevicePath { get; set; } = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
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";
|
||||
}
|
||||
}
|
||||
1959
MPF.ExecutionContexts/Aaru/ExecutionContext.cs
Normal file
1959
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
|
||||
}
|
||||
}
|
||||
1736
MPF.ExecutionContexts/DiscImageCreator/ExecutionContext.cs
Normal file
1736
MPF.ExecutionContexts/DiscImageCreator/ExecutionContext.cs
Normal file
File diff suppressed because it is too large
Load Diff
45
MPF.ExecutionContexts/DiscImageCreator/FlagStrings.cs
Normal file
45
MPF.ExecutionContexts/DiscImageCreator/FlagStrings.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
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 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;
|
||||
}
|
||||
}
|
||||
59
MPF.ExecutionContexts/MPF.ExecutionContexts.csproj
Normal file
59
MPF.ExecutionContexts/MPF.ExecutionContexts.csproj
Normal file
@@ -0,0 +1,59 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<!-- Assembly Properties -->
|
||||
<TargetFrameworks>net20;net35;net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0</TargetFrameworks>
|
||||
<CheckEolTargetFramework>false</CheckEolTargetFramework>
|
||||
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<VersionPrefix>3.2.1</VersionPrefix>
|
||||
<WarningsNotAsErrors>NU5104</WarningsNotAsErrors>
|
||||
|
||||
<!-- Package Properties -->
|
||||
<Authors>Matt Nadareski;ReignStumble;Jakz</Authors>
|
||||
<Description>Common code for all MPF execution contexts</Description>
|
||||
<Copyright>Copyright (c) Matt Nadareski 2019-2024</Copyright>
|
||||
<PackageProjectUrl>https://github.com/SabreTools/</PackageProjectUrl>
|
||||
<RepositoryUrl>https://github.com/SabreTools/MPF</RepositoryUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<InternalsVisibleTo Include="MPF.Test" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Support All Frameworks -->
|
||||
<PropertyGroup Condition="$(TargetFramework.StartsWith(`net2`)) OR $(TargetFramework.StartsWith(`net3`)) OR $(TargetFramework.StartsWith(`net4`))">
|
||||
<RuntimeIdentifiers>win-x86;win-x64</RuntimeIdentifiers>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="$(TargetFramework.StartsWith(`netcoreapp`)) OR $(TargetFramework.StartsWith(`net5`))">
|
||||
<RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64</RuntimeIdentifiers>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="$(TargetFramework.StartsWith(`net6`)) OR $(TargetFramework.StartsWith(`net7`)) OR $(TargetFramework.StartsWith(`net8`))">
|
||||
<RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64;osx-arm64</RuntimeIdentifiers>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="$(RuntimeIdentifier.StartsWith(`osx-arm`))">
|
||||
<TargetFrameworks>net6.0;net7.0;net8.0</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Support for old .NET versions -->
|
||||
<ItemGroup Condition="$(TargetFramework.StartsWith(`net2`)) OR $(TargetFramework.StartsWith(`net3`)) OR $(TargetFramework.StartsWith(`net40`))">
|
||||
<PackageReference Include="MinAsyncBridge" Version="0.12.4" />
|
||||
<PackageReference Include="MinTasksExtensionsBridge" Version="0.3.4" />
|
||||
<PackageReference Include="MinThreadingBridge" Version="0.11.4" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="$(TargetFramework.StartsWith(`net4`)) AND !$(TargetFramework.StartsWith(`net40`))">
|
||||
<PackageReference Include="IndexRange" Version="1.0.3" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="!$(TargetFramework.StartsWith(`net2`)) AND !$(TargetFramework.StartsWith(`net3`)) AND !$(TargetFramework.StartsWith(`net40`)) AND !$(TargetFramework.StartsWith(`net452`))">
|
||||
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="6.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SabreTools.RedumpLib" Version="1.4.1" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
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,
|
||||
}
|
||||
}
|
||||
901
MPF.ExecutionContexts/Redumper/ExecutionContext.cs
Normal file
901
MPF.ExecutionContexts/Redumper/ExecutionContext.cs
Normal file
@@ -0,0 +1,901 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using SabreTools.RedumpLib.Data;
|
||||
|
||||
namespace MPF.ExecutionContexts.Redumper
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a generic set of Redumper parameters
|
||||
/// </summary>
|
||||
public sealed class ExecutionContext : BaseExecutionContext
|
||||
{
|
||||
#region Generic Dumping Information
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string? InputPath => DriveValue;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string? OutputPath => Path.Combine(ImagePathValue?.Trim('"') ?? string.Empty, ImageNameValue?.Trim('"') ?? string.Empty) + GetDefaultExtension(this.Type);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int? Speed => SpeedValue;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Flag Values
|
||||
|
||||
/// <summary>
|
||||
/// List of all modes being run
|
||||
/// </summary>
|
||||
public List<string>? ModeValues { get; set; }
|
||||
|
||||
#region General
|
||||
|
||||
/// <summary>
|
||||
/// Drive to use, first available drive with disc, if not provided
|
||||
/// </summary>
|
||||
public string? DriveValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Drive read speed, optimal drive speed will be used if not provided
|
||||
/// </summary>
|
||||
public int? SpeedValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of sector retries in case of SCSI/C2 error (default: 0)
|
||||
/// </summary>
|
||||
public int? RetriesValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Dump files base directory
|
||||
/// </summary>
|
||||
public string? ImagePathValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Dump files prefix, autogenerated in dump mode, if not provided
|
||||
/// </summary>
|
||||
public string? ImageNameValue { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Drive Configuration
|
||||
|
||||
/// <summary>
|
||||
/// Override drive type, possible values: GENERIC, PLEXTOR, LG_ASUS
|
||||
/// </summary>
|
||||
public string? DriveTypeValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Override drive read offset
|
||||
/// </summary>
|
||||
public int? DriveReadOffsetValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Override drive C2 shift
|
||||
/// </summary>
|
||||
public int? DriveC2ShiftValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Override drive pre-gap start LBA
|
||||
/// </summary>
|
||||
public int? DrivePregapStartValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Override drive read method, possible values: BE, D8, BE_CDDA
|
||||
/// </summary>
|
||||
public string? DriveReadMethodValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Override drive sector order, possible values: DATA_C2_SUB, DATA_SUB_C2
|
||||
/// </summary>
|
||||
public string? DriveSectorOrderValue { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Offset
|
||||
|
||||
/// <summary>
|
||||
/// Override offset autodetection and use supplied value
|
||||
/// </summary>
|
||||
public int? ForceOffsetValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Maximum absolute sample value to treat it as silence (default: 32)
|
||||
/// </summary>
|
||||
public int? AudioSilenceThresholdValue { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Split
|
||||
|
||||
/// <summary>
|
||||
/// Fill byte value for skipped sectors (default: 0x55)
|
||||
/// </summary>
|
||||
public byte? SkipFillValue { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Miscellaneous
|
||||
|
||||
/// <summary>
|
||||
/// LBA to start dumping from
|
||||
/// </summary>
|
||||
public int? LBAStartValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// LBA to stop dumping at (everything before the value), useful for discs with fake TOC
|
||||
/// </summary>
|
||||
public int? LBAEndValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// LBA ranges of sectors to skip
|
||||
/// </summary>
|
||||
public string? SkipValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Write offset for dumps when reading as data
|
||||
/// </summary>
|
||||
public int? DumpWriteOffsetValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of sectors to read at once on initial dump, DVD only (Default 32)
|
||||
/// </summary>
|
||||
public int? DumpReadSizeValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Maximum number of lead-in retries per session (Default 4)
|
||||
/// </summary>
|
||||
public int? PlextorLeadinRetriesValue { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ExecutionContext(string? parameters) : base(parameters) { }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ExecutionContext(RedumpSystem? system, MediaType? type, string? drivePath, string filename, int? driveSpeed, Dictionary<string, string?> options)
|
||||
: base(system, type, drivePath, filename, driveSpeed, options)
|
||||
{
|
||||
}
|
||||
|
||||
#region BaseExecutionContext Implementations
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <remarks>Command support is irrelevant for redumper</remarks>
|
||||
public override Dictionary<string, List<string>> GetCommandSupport()
|
||||
{
|
||||
return new Dictionary<string, List<string>>()
|
||||
{
|
||||
[CommandStrings.NONE] =
|
||||
[
|
||||
// General
|
||||
FlagStrings.HelpLong,
|
||||
FlagStrings.HelpShort,
|
||||
FlagStrings.Version,
|
||||
FlagStrings.Verbose,
|
||||
FlagStrings.Debug,
|
||||
FlagStrings.Drive,
|
||||
FlagStrings.Speed,
|
||||
FlagStrings.Retries,
|
||||
FlagStrings.ImagePath,
|
||||
FlagStrings.ImageName,
|
||||
FlagStrings.Overwrite,
|
||||
|
||||
// Drive Configuration
|
||||
FlagStrings.DriveType,
|
||||
FlagStrings.DriveReadOffset,
|
||||
FlagStrings.DriveC2Shift,
|
||||
FlagStrings.DrivePregapStart,
|
||||
FlagStrings.DriveReadMethod,
|
||||
FlagStrings.DriveSectorOrder,
|
||||
|
||||
// Drive Specific
|
||||
FlagStrings.PlextorSkipLeadin,
|
||||
FlagStrings.PlextorLeadinRetries,
|
||||
FlagStrings.AsusSkipLeadout,
|
||||
|
||||
// Offset
|
||||
FlagStrings.ForceOffset,
|
||||
FlagStrings.AudioSilenceThreshold,
|
||||
FlagStrings.CorrectOffsetShift,
|
||||
FlagStrings.OffsetShiftRelocate,
|
||||
|
||||
// Split
|
||||
FlagStrings.ForceSplit,
|
||||
FlagStrings.LeaveUnchanged,
|
||||
FlagStrings.ForceQTOC,
|
||||
FlagStrings.SkipFill,
|
||||
FlagStrings.ISO9660Trim,
|
||||
|
||||
// Miscellaneous
|
||||
FlagStrings.LBAStart,
|
||||
FlagStrings.LBAEnd,
|
||||
FlagStrings.RefineSubchannel,
|
||||
FlagStrings.Skip,
|
||||
FlagStrings.DumpWriteOffset,
|
||||
FlagStrings.DumpReadSize,
|
||||
FlagStrings.OverreadLeadout,
|
||||
FlagStrings.ForceUnscrambled,
|
||||
FlagStrings.LegacySubs,
|
||||
FlagStrings.DisableCDText,
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <remarks>
|
||||
/// Redumper is unique in that the base command can be multiple
|
||||
/// modes all listed together. It is also unique in that "all
|
||||
/// flags are supported for everything" and it filters out internally
|
||||
/// </remarks>
|
||||
public override string GenerateParameters()
|
||||
{
|
||||
var parameters = new List<string>();
|
||||
|
||||
ModeValues ??= [CommandStrings.NONE];
|
||||
|
||||
// Modes
|
||||
parameters.AddRange(ModeValues);
|
||||
|
||||
#region General
|
||||
|
||||
// Help
|
||||
if (this[FlagStrings.HelpLong] == true)
|
||||
parameters.Add(FlagStrings.HelpLong);
|
||||
|
||||
// Version
|
||||
if (this[FlagStrings.Version] == true)
|
||||
parameters.Add(FlagStrings.Version);
|
||||
|
||||
// Verbose
|
||||
if (this[FlagStrings.Verbose] == true)
|
||||
parameters.Add(FlagStrings.Verbose);
|
||||
|
||||
// Debug
|
||||
if (this[FlagStrings.Debug] == true)
|
||||
parameters.Add(FlagStrings.Debug);
|
||||
|
||||
// Drive
|
||||
if (this[FlagStrings.Drive] == true)
|
||||
{
|
||||
if (DriveValue != null)
|
||||
parameters.Add($"{FlagStrings.Drive}={DriveValue}");
|
||||
}
|
||||
|
||||
// Speed
|
||||
if (this[FlagStrings.Speed] == true)
|
||||
{
|
||||
if (SpeedValue != null)
|
||||
parameters.Add($"{FlagStrings.Speed}={SpeedValue}");
|
||||
}
|
||||
|
||||
// Retries
|
||||
if (this[FlagStrings.Retries] == true)
|
||||
{
|
||||
if (RetriesValue != null)
|
||||
parameters.Add($"{FlagStrings.Retries}={RetriesValue}");
|
||||
}
|
||||
|
||||
// Image Path
|
||||
if (this[FlagStrings.ImagePath] == true)
|
||||
{
|
||||
if (ImagePathValue != null)
|
||||
parameters.Add($"{FlagStrings.ImagePath}={ImagePathValue}");
|
||||
}
|
||||
|
||||
// Image Name
|
||||
if (this[FlagStrings.ImageName] == true)
|
||||
{
|
||||
if (ImageNameValue != null)
|
||||
parameters.Add($"{FlagStrings.ImageName}={ImageNameValue}");
|
||||
}
|
||||
|
||||
// Overwrite
|
||||
if (this[FlagStrings.Overwrite] == true)
|
||||
parameters.Add(FlagStrings.Overwrite);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Drive Configuration
|
||||
|
||||
// Drive Type
|
||||
if (this[FlagStrings.DriveType] == true)
|
||||
{
|
||||
if (DriveTypeValue != null)
|
||||
parameters.Add($"{FlagStrings.DriveType}={DriveTypeValue}");
|
||||
}
|
||||
|
||||
// Drive Read Offset
|
||||
if (this[FlagStrings.DriveReadOffset] == true)
|
||||
{
|
||||
if (DriveReadOffsetValue != null)
|
||||
parameters.Add($"{FlagStrings.DriveReadOffset}={DriveReadOffsetValue}");
|
||||
}
|
||||
|
||||
// Drive C2 Shift
|
||||
if (this[FlagStrings.DriveC2Shift] == true)
|
||||
{
|
||||
if (DriveC2ShiftValue != null)
|
||||
parameters.Add($"{FlagStrings.DriveC2Shift}={DriveC2ShiftValue}");
|
||||
}
|
||||
|
||||
// Drive Pregap Start
|
||||
if (this[FlagStrings.DrivePregapStart] == true)
|
||||
{
|
||||
if (DrivePregapStartValue != null)
|
||||
parameters.Add($"{FlagStrings.DrivePregapStart}={DrivePregapStartValue}");
|
||||
}
|
||||
|
||||
// Drive Read Method
|
||||
if (this[FlagStrings.DriveReadMethod] == true)
|
||||
{
|
||||
if (DriveReadMethodValue != null)
|
||||
parameters.Add($"{FlagStrings.DriveReadMethod}={DriveReadMethodValue}");
|
||||
}
|
||||
|
||||
// Drive Sector Order
|
||||
if (this[FlagStrings.DriveSectorOrder] == true)
|
||||
{
|
||||
if (DriveSectorOrderValue != null)
|
||||
parameters.Add($"{FlagStrings.DriveSectorOrder}={DriveSectorOrderValue}");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Drive Specific
|
||||
|
||||
// Plextor Leadin Skip
|
||||
if (this[FlagStrings.PlextorSkipLeadin] == true)
|
||||
parameters.Add(FlagStrings.PlextorSkipLeadin);
|
||||
|
||||
// Plextor Leadin Retries
|
||||
if (this[FlagStrings.PlextorLeadinRetries] == true)
|
||||
{
|
||||
if (PlextorLeadinRetriesValue != null)
|
||||
parameters.Add($"{FlagStrings.PlextorLeadinRetries}={PlextorLeadinRetriesValue}");
|
||||
}
|
||||
|
||||
// Asus Skip Leadout
|
||||
if (this[FlagStrings.AsusSkipLeadout] == true)
|
||||
parameters.Add(FlagStrings.AsusSkipLeadout);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Offset
|
||||
|
||||
// Force Offset
|
||||
if (this[FlagStrings.ForceOffset] == true)
|
||||
{
|
||||
if (ForceOffsetValue != null)
|
||||
parameters.Add($"{FlagStrings.ForceOffset}={ForceOffsetValue}");
|
||||
}
|
||||
|
||||
// Audio Silence Threshold
|
||||
if (this[FlagStrings.AudioSilenceThreshold] == true)
|
||||
{
|
||||
if (AudioSilenceThresholdValue != null)
|
||||
parameters.Add($"{FlagStrings.AudioSilenceThreshold}={AudioSilenceThresholdValue}");
|
||||
}
|
||||
|
||||
// Correct Offset Shift
|
||||
if (this[FlagStrings.CorrectOffsetShift] == true)
|
||||
parameters.Add(FlagStrings.CorrectOffsetShift);
|
||||
|
||||
// Offset Shift Relocate
|
||||
if (this[FlagStrings.OffsetShiftRelocate] == true)
|
||||
parameters.Add(FlagStrings.OffsetShiftRelocate);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Split
|
||||
|
||||
// Force Split
|
||||
if (this[FlagStrings.ForceSplit] == true)
|
||||
parameters.Add(FlagStrings.ForceSplit);
|
||||
|
||||
// Leave Unchanged
|
||||
if (this[FlagStrings.LeaveUnchanged] == true)
|
||||
parameters.Add(FlagStrings.LeaveUnchanged);
|
||||
|
||||
// Force QTOC
|
||||
if (this[FlagStrings.ForceQTOC] == true)
|
||||
parameters.Add(FlagStrings.ForceQTOC);
|
||||
|
||||
// Skip Fill
|
||||
if (this[FlagStrings.SkipFill] == true)
|
||||
{
|
||||
if (SkipFillValue != null)
|
||||
parameters.Add($"{FlagStrings.SkipFill}={SkipFillValue:x}");
|
||||
}
|
||||
|
||||
// ISO9660 Trim
|
||||
if (this[FlagStrings.ISO9660Trim] == true)
|
||||
parameters.Add(FlagStrings.ISO9660Trim);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Miscellaneous
|
||||
|
||||
// LBA Start
|
||||
if (this[FlagStrings.LBAStart] == true)
|
||||
{
|
||||
if (LBAStartValue != null)
|
||||
parameters.Add($"{FlagStrings.LBAStart}={LBAStartValue}");
|
||||
}
|
||||
|
||||
// LBA End
|
||||
if (this[FlagStrings.LBAEnd] == true)
|
||||
{
|
||||
if (LBAEndValue != null)
|
||||
parameters.Add($"{FlagStrings.LBAEnd}={LBAEndValue}");
|
||||
}
|
||||
|
||||
// Refine Subchannel
|
||||
if (this[FlagStrings.RefineSubchannel] == true)
|
||||
parameters.Add(FlagStrings.RefineSubchannel);
|
||||
|
||||
// Skip
|
||||
if (this[FlagStrings.Skip] == true)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(SkipValue))
|
||||
parameters.Add($"{FlagStrings.Skip}={SkipValue}");
|
||||
}
|
||||
|
||||
// Dump Write Offset
|
||||
if (this[FlagStrings.DumpWriteOffset] == true)
|
||||
{
|
||||
if (DumpWriteOffsetValue != null)
|
||||
parameters.Add($"{FlagStrings.DumpWriteOffset}={DumpWriteOffsetValue}");
|
||||
}
|
||||
|
||||
// Dump Read Size
|
||||
if (this[FlagStrings.DumpReadSize] == true)
|
||||
{
|
||||
if (DumpReadSizeValue != null && DumpReadSizeValue > 0)
|
||||
parameters.Add($"{FlagStrings.DumpReadSize}={DumpReadSizeValue}");
|
||||
}
|
||||
|
||||
// Overread Leadout
|
||||
if (this[FlagStrings.OverreadLeadout] == true)
|
||||
parameters.Add(FlagStrings.OverreadLeadout);
|
||||
|
||||
// Force Unscrambled
|
||||
if (this[FlagStrings.ForceUnscrambled] == true)
|
||||
parameters.Add(FlagStrings.ForceUnscrambled);
|
||||
|
||||
// Legacy Subs
|
||||
if (this[FlagStrings.LegacySubs] == true)
|
||||
parameters.Add(FlagStrings.LegacySubs);
|
||||
|
||||
// Disable CD Text
|
||||
if (this[FlagStrings.DisableCDText] == true)
|
||||
parameters.Add(FlagStrings.DisableCDText);
|
||||
|
||||
#endregion
|
||||
|
||||
return string.Join(" ", [.. parameters]);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string? GetDefaultExtension(MediaType? mediaType) => Converters.Extension(mediaType);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override MediaType? GetMediaType() => null;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool IsDumpingCommand()
|
||||
{
|
||||
return this.BaseCommand == CommandStrings.NONE
|
||||
|| this.BaseCommand?.Contains(CommandStrings.CD) == true
|
||||
|| this.BaseCommand?.Contains(CommandStrings.DVD) == true
|
||||
|| this.BaseCommand?.Contains(CommandStrings.BluRay) == true
|
||||
|| this.BaseCommand?.Contains(CommandStrings.SACD) == true
|
||||
|| this.BaseCommand?.Contains(CommandStrings.New) == true
|
||||
|| this.BaseCommand?.Contains(CommandStrings.Dump) == true
|
||||
|| this.BaseCommand?.Contains(CommandStrings.DumpNew) == true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void ResetValues()
|
||||
{
|
||||
BaseCommand = CommandStrings.NONE;
|
||||
|
||||
flags = [];
|
||||
|
||||
// General
|
||||
DriveValue = null;
|
||||
SpeedValue = null;
|
||||
RetriesValue = null;
|
||||
ImagePathValue = null;
|
||||
ImageNameValue = null;
|
||||
|
||||
// Drive Configuration
|
||||
DriveTypeValue = null;
|
||||
DriveReadOffsetValue = null;
|
||||
DriveC2ShiftValue = null;
|
||||
DrivePregapStartValue = null;
|
||||
DriveReadMethodValue = null;
|
||||
DriveSectorOrderValue = null;
|
||||
|
||||
// Offset
|
||||
ForceOffsetValue = null;
|
||||
AudioSilenceThresholdValue = null;
|
||||
|
||||
// Split
|
||||
SkipFillValue = null;
|
||||
|
||||
// Miscellaneous
|
||||
LBAStartValue = null;
|
||||
LBAEndValue = null;
|
||||
SkipValue = null;
|
||||
DumpReadSizeValue = null;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void SetDefaultParameters(string? drivePath, string filename, int? driveSpeed, Dictionary<string, string?> options)
|
||||
{
|
||||
// If we don't have a CD, DVD, HD-DVD, or BD, we can't dump using redumper
|
||||
if (this.Type != MediaType.CDROM
|
||||
&& this.Type != MediaType.DVD
|
||||
&& this.Type != MediaType.HDDVD
|
||||
&& this.Type != MediaType.BluRay)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
BaseCommand = CommandStrings.NONE;
|
||||
switch (this.Type)
|
||||
{
|
||||
case MediaType.CDROM:
|
||||
ModeValues = this.System switch
|
||||
{
|
||||
RedumpSystem.SuperAudioCD => [CommandStrings.SACD],
|
||||
_ => [CommandStrings.CD],
|
||||
};
|
||||
break;
|
||||
case MediaType.DVD:
|
||||
ModeValues = [CommandStrings.DVD];
|
||||
break;
|
||||
case MediaType.HDDVD: // TODO: Keep in sync if another command string shows up
|
||||
ModeValues = [CommandStrings.DVD];
|
||||
break;
|
||||
case MediaType.BluRay:
|
||||
ModeValues = [CommandStrings.BluRay];
|
||||
break;
|
||||
default:
|
||||
BaseCommand = null;
|
||||
return;
|
||||
}
|
||||
|
||||
this[FlagStrings.Drive] = true;
|
||||
DriveValue = drivePath;
|
||||
|
||||
this[FlagStrings.Speed] = true;
|
||||
SpeedValue = driveSpeed;
|
||||
|
||||
// Set user-defined options
|
||||
if (GetBooleanSetting(options, SettingConstants.EnableVerbose, SettingConstants.EnableVerboseDefault))
|
||||
this[FlagStrings.Verbose] = true;
|
||||
if (GetBooleanSetting(options, SettingConstants.EnableDebug, SettingConstants.EnableDebugDefault))
|
||||
this[FlagStrings.Debug] = true;
|
||||
|
||||
string? readMethod = GetStringSetting(options, SettingConstants.ReadMethod, SettingConstants.ReadMethodDefault);
|
||||
|
||||
if (!string.IsNullOrEmpty(readMethod) && readMethod != ReadMethod.NONE.ToString())
|
||||
{
|
||||
this[FlagStrings.DriveReadMethod] = true;
|
||||
DriveReadMethodValue = readMethod;
|
||||
}
|
||||
|
||||
string? sectorOrder = GetStringSetting(options, SettingConstants.SectorOrder, SettingConstants.SectorOrderDefault);
|
||||
if (!string.IsNullOrEmpty(sectorOrder) && sectorOrder != SectorOrder.NONE.ToString())
|
||||
{
|
||||
this[FlagStrings.DriveSectorOrder] = true;
|
||||
DriveSectorOrderValue = sectorOrder;
|
||||
}
|
||||
|
||||
if (GetBooleanSetting(options, SettingConstants.UseGenericDriveType, SettingConstants.UseGenericDriveTypeDefault))
|
||||
{
|
||||
this[FlagStrings.DriveType] = true;
|
||||
DriveTypeValue = "GENERIC";
|
||||
}
|
||||
|
||||
// Set the output paths
|
||||
if (!string.IsNullOrEmpty(filename))
|
||||
{
|
||||
var imagePath = Path.GetDirectoryName(filename);
|
||||
if (!string.IsNullOrEmpty(imagePath))
|
||||
{
|
||||
this[FlagStrings.ImagePath] = true;
|
||||
ImagePathValue = $"\"{imagePath}\"";
|
||||
}
|
||||
|
||||
string imageName = Path.GetFileNameWithoutExtension(filename);
|
||||
if (!string.IsNullOrEmpty(imageName))
|
||||
{
|
||||
this[FlagStrings.ImageName] = true;
|
||||
ImageNameValue = $"\"{imageName}\"";
|
||||
}
|
||||
}
|
||||
|
||||
this[FlagStrings.Retries] = true;
|
||||
RetriesValue = GetInt32Setting(options, SettingConstants.RereadCount, SettingConstants.RereadCountDefault);
|
||||
|
||||
if (GetBooleanSetting(options, SettingConstants.EnableLeadinRetry, SettingConstants.EnableLeadinRetryDefault))
|
||||
{
|
||||
this[FlagStrings.PlextorLeadinRetries] = true;
|
||||
PlextorLeadinRetriesValue = GetInt32Setting(options, SettingConstants.LeadinRetryCount, SettingConstants.LeadinRetryCountDefault);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override bool ValidateAndSetParameters(string? parameters)
|
||||
{
|
||||
BaseCommand = CommandStrings.NONE;
|
||||
|
||||
// The string has to be valid by itself first
|
||||
if (string.IsNullOrEmpty(parameters))
|
||||
return false;
|
||||
|
||||
// Now split the string into parts for easier validation
|
||||
// https://stackoverflow.com/questions/14655023/split-a-string-that-has-white-spaces-unless-they-are-enclosed-within-quotes
|
||||
parameters = parameters!.Trim();
|
||||
List<string> parts = Regex.Matches(parameters, @"([a-zA-Z\-]*=)?[\""].+?[\""]|[^ ]+", RegexOptions.Compiled)
|
||||
.Cast<Match>()
|
||||
.Select(m => m.Value)
|
||||
.ToList();
|
||||
|
||||
// Setup the modes
|
||||
ModeValues = [];
|
||||
|
||||
// All modes should be cached separately
|
||||
int index = 0;
|
||||
for (; index < parts.Count; index++)
|
||||
{
|
||||
// Flag to see if we have a flag
|
||||
bool isFlag = false;
|
||||
|
||||
string part = parts[index];
|
||||
switch (part)
|
||||
{
|
||||
case CommandStrings.CD:
|
||||
case CommandStrings.DVD:
|
||||
case CommandStrings.BluRay:
|
||||
case CommandStrings.SACD:
|
||||
case CommandStrings.New: // Temporary command, to be removed later
|
||||
case CommandStrings.Rings:
|
||||
case CommandStrings.Dump:
|
||||
case CommandStrings.DumpNew: // Temporary command, to be removed later
|
||||
case CommandStrings.Refine:
|
||||
case CommandStrings.RefineNew: // Temporary command, to be removed later
|
||||
case CommandStrings.Verify:
|
||||
case CommandStrings.DVDKey:
|
||||
case CommandStrings.DVDIsoKey:
|
||||
case CommandStrings.Protection:
|
||||
case CommandStrings.Split:
|
||||
case CommandStrings.Hash:
|
||||
case CommandStrings.Info:
|
||||
case CommandStrings.Skeleton:
|
||||
ModeValues.Add(part);
|
||||
break;
|
||||
|
||||
// Default is either a flag or an invalid mode
|
||||
default:
|
||||
if (part.StartsWith("-"))
|
||||
{
|
||||
isFlag = true;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// If we had a flag, break out
|
||||
if (isFlag)
|
||||
break;
|
||||
}
|
||||
|
||||
// Loop through all auxiliary flags, if necessary
|
||||
for (int i = index; i < parts.Count; i++)
|
||||
{
|
||||
// Flag read-out values
|
||||
byte? byteValue = null;
|
||||
int? intValue = null;
|
||||
string? stringValue = null;
|
||||
|
||||
#region General
|
||||
|
||||
// Help
|
||||
ProcessFlagParameter(parts, FlagStrings.HelpShort, FlagStrings.HelpLong, ref i);
|
||||
|
||||
// Version
|
||||
ProcessFlagParameter(parts, FlagStrings.Version, ref i);
|
||||
|
||||
// Verbose
|
||||
ProcessFlagParameter(parts, FlagStrings.Verbose, ref i);
|
||||
|
||||
// Debug
|
||||
ProcessFlagParameter(parts, FlagStrings.Debug, ref i);
|
||||
|
||||
// Drive
|
||||
stringValue = ProcessStringParameter(parts, FlagStrings.Drive, ref i);
|
||||
if (!string.IsNullOrEmpty(stringValue))
|
||||
DriveValue = stringValue;
|
||||
|
||||
// Speed
|
||||
intValue = ProcessInt32Parameter(parts, FlagStrings.Speed, ref i);
|
||||
if (intValue != null && intValue != Int32.MinValue)
|
||||
SpeedValue = intValue;
|
||||
|
||||
// Retries
|
||||
intValue = ProcessInt32Parameter(parts, FlagStrings.Retries, ref i);
|
||||
if (intValue != null && intValue != Int32.MinValue)
|
||||
RetriesValue = intValue;
|
||||
|
||||
// Image Path
|
||||
stringValue = ProcessStringParameter(parts, FlagStrings.ImagePath, ref i);
|
||||
if (!string.IsNullOrEmpty(stringValue))
|
||||
ImagePathValue = $"\"{stringValue!.Trim('"')}\"";
|
||||
|
||||
// Image Name
|
||||
stringValue = ProcessStringParameter(parts, FlagStrings.ImageName, ref i);
|
||||
if (!string.IsNullOrEmpty(stringValue))
|
||||
ImageNameValue = $"\"{stringValue!.Trim('"')}\"";
|
||||
|
||||
// Overwrite
|
||||
ProcessFlagParameter(parts, FlagStrings.Overwrite, ref i);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Drive Configuration
|
||||
|
||||
// Drive Type
|
||||
stringValue = ProcessStringParameter(parts, FlagStrings.DriveType, ref i);
|
||||
if (!string.IsNullOrEmpty(stringValue))
|
||||
DriveTypeValue = stringValue;
|
||||
|
||||
// Drive Read Offset
|
||||
intValue = ProcessInt32Parameter(parts, FlagStrings.DriveReadOffset, ref i);
|
||||
if (intValue != null && intValue != Int32.MinValue)
|
||||
DriveReadOffsetValue = intValue;
|
||||
|
||||
// Drive C2 Shift
|
||||
intValue = ProcessInt32Parameter(parts, FlagStrings.DriveC2Shift, ref i);
|
||||
if (intValue != null && intValue != Int32.MinValue)
|
||||
DriveC2ShiftValue = intValue;
|
||||
|
||||
// Drive Pregap Start
|
||||
intValue = ProcessInt32Parameter(parts, FlagStrings.DrivePregapStart, ref i);
|
||||
if (intValue != null && intValue != Int32.MinValue)
|
||||
DrivePregapStartValue = intValue;
|
||||
|
||||
// Drive Read Method
|
||||
stringValue = ProcessStringParameter(parts, FlagStrings.DriveReadMethod, ref i);
|
||||
if (!string.IsNullOrEmpty(stringValue))
|
||||
DriveReadMethodValue = stringValue;
|
||||
|
||||
// Drive Sector Order
|
||||
stringValue = ProcessStringParameter(parts, FlagStrings.DriveSectorOrder, ref i);
|
||||
if (!string.IsNullOrEmpty(stringValue))
|
||||
DriveSectorOrderValue = stringValue;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Drive Specific
|
||||
|
||||
// Plextor Skip Leadin
|
||||
ProcessFlagParameter(parts, FlagStrings.PlextorSkipLeadin, ref i);
|
||||
|
||||
// Plextor Leadin Retries
|
||||
intValue = ProcessInt32Parameter(parts, FlagStrings.PlextorLeadinRetries, ref i);
|
||||
if (intValue != null && intValue != Int32.MinValue)
|
||||
PlextorLeadinRetriesValue = intValue;
|
||||
|
||||
// Asus Skip Leadout
|
||||
ProcessFlagParameter(parts, FlagStrings.AsusSkipLeadout, ref i);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Offset
|
||||
|
||||
// Force Offset
|
||||
intValue = ProcessInt32Parameter(parts, FlagStrings.ForceOffset, ref i);
|
||||
if (intValue != null && intValue != Int32.MinValue)
|
||||
ForceOffsetValue = intValue;
|
||||
|
||||
// Audio Silence Threshold
|
||||
intValue = ProcessInt32Parameter(parts, FlagStrings.AudioSilenceThreshold, ref i);
|
||||
if (intValue != null && intValue != Int32.MinValue)
|
||||
AudioSilenceThresholdValue = intValue;
|
||||
|
||||
// Correct Offset Shift
|
||||
ProcessFlagParameter(parts, FlagStrings.CorrectOffsetShift, ref i);
|
||||
|
||||
// Correct Shift Relocate
|
||||
ProcessFlagParameter(parts, FlagStrings.OffsetShiftRelocate, ref i);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Split
|
||||
|
||||
// Force Split
|
||||
ProcessFlagParameter(parts, FlagStrings.ForceSplit, ref i);
|
||||
|
||||
// Leave Unchanged
|
||||
ProcessFlagParameter(parts, FlagStrings.LeaveUnchanged, ref i);
|
||||
|
||||
// Force QTOC
|
||||
ProcessFlagParameter(parts, FlagStrings.ForceQTOC, ref i);
|
||||
|
||||
// Skip Fill
|
||||
byteValue = ProcessUInt8Parameter(parts, FlagStrings.SkipFill, ref i);
|
||||
if (byteValue != null && byteValue != Byte.MinValue)
|
||||
SkipFillValue = byteValue;
|
||||
|
||||
// ISO9660 Trim
|
||||
ProcessFlagParameter(parts, FlagStrings.ISO9660Trim, ref i);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Miscellaneous
|
||||
|
||||
// LBA Start
|
||||
intValue = ProcessInt32Parameter(parts, FlagStrings.LBAStart, ref i);
|
||||
if (intValue != null && intValue != Int32.MinValue)
|
||||
LBAStartValue = intValue;
|
||||
|
||||
// LBA End
|
||||
intValue = ProcessInt32Parameter(parts, FlagStrings.LBAEnd, ref i);
|
||||
if (intValue != null && intValue != Int32.MinValue)
|
||||
LBAEndValue = intValue;
|
||||
|
||||
// Refine Subchannel
|
||||
ProcessFlagParameter(parts, FlagStrings.RefineSubchannel, ref i);
|
||||
|
||||
// Skip
|
||||
stringValue = ProcessStringParameter(parts, FlagStrings.Skip, ref i);
|
||||
if (!string.IsNullOrEmpty(stringValue))
|
||||
SkipValue = stringValue;
|
||||
|
||||
// Dump Write Offset
|
||||
intValue = ProcessInt32Parameter(parts, FlagStrings.DumpWriteOffset, ref i);
|
||||
if (intValue != null && intValue != Int32.MinValue)
|
||||
DumpWriteOffsetValue = intValue;
|
||||
|
||||
// Dump Read Size
|
||||
intValue = ProcessInt32Parameter(parts, FlagStrings.DumpReadSize, ref i);
|
||||
if (intValue != null && intValue != Int32.MinValue)
|
||||
DumpReadSizeValue = intValue;
|
||||
|
||||
// Overread Leadout
|
||||
ProcessFlagParameter(parts, FlagStrings.OverreadLeadout, ref i);
|
||||
|
||||
// Force Unscrambled
|
||||
ProcessFlagParameter(parts, FlagStrings.ForceUnscrambled, ref i);
|
||||
|
||||
// Legacy Subs
|
||||
ProcessFlagParameter(parts, FlagStrings.LegacySubs, ref i);
|
||||
|
||||
// Disable CD Text
|
||||
ProcessFlagParameter(parts, FlagStrings.DisableCDText, ref i);
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
// If the image name was not set, set it with a default value
|
||||
if (string.IsNullOrEmpty(this.ImageNameValue))
|
||||
this.ImageNameValue = "track";
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
780
MPF.Frontend/Drive.cs
Normal file
780
MPF.Frontend/Drive.cs
Normal file
@@ -0,0 +1,780 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
#if NET462_OR_GREATER || NETCOREAPP
|
||||
using Microsoft.Management.Infrastructure;
|
||||
using Microsoft.Management.Infrastructure.Generic;
|
||||
#endif
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using MPF.Processors;
|
||||
using SabreTools.IO;
|
||||
using SabreTools.RedumpLib.Data;
|
||||
|
||||
namespace MPF.Frontend
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents information for a single drive
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// TODO: Can the Aaru models be used instead of the ones I've created here?
|
||||
/// </remarks>
|
||||
public class Drive
|
||||
{
|
||||
#region Fields
|
||||
|
||||
/// <summary>
|
||||
/// Represents drive type
|
||||
/// </summary>
|
||||
public InternalDriveType? InternalDriveType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Drive partition format
|
||||
/// </summary>
|
||||
public string? DriveFormat { get; private set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Windows drive path
|
||||
/// </summary>
|
||||
public string? Name { get; private set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Represents if Windows has marked the drive as active
|
||||
/// </summary>
|
||||
public bool MarkedActive { get; private set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the total size of the drive
|
||||
/// </summary>
|
||||
public long TotalSize { get; private set; } = default;
|
||||
|
||||
/// <summary>
|
||||
/// Media label as read by Windows
|
||||
/// </summary>
|
||||
/// <remarks>The try/catch is needed because Windows will throw an exception if the drive is not marked as active</remarks>
|
||||
public string? VolumeLabel { get; private set; } = null;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Derived Fields
|
||||
|
||||
/// <summary>
|
||||
/// Read-only access to the drive letter
|
||||
/// </summary>
|
||||
/// <remarks>Should only be used in UI applications</remarks>
|
||||
public char? Letter => Name?[0] ?? '\0';
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Protected constructor
|
||||
/// </summary>
|
||||
protected Drive() { }
|
||||
|
||||
/// <summary>
|
||||
/// Create a new Drive object from a drive type and device path
|
||||
/// </summary>
|
||||
/// <param name="driveType">InternalDriveType value representing the drive type</param>
|
||||
/// <param name="devicePath">Path to the device according to the local machine</param>
|
||||
public static Drive? Create(InternalDriveType? driveType, string devicePath)
|
||||
{
|
||||
// Create a new, empty drive object
|
||||
var drive = new Drive()
|
||||
{
|
||||
InternalDriveType = driveType,
|
||||
};
|
||||
|
||||
// If we have an invalid device path, return null
|
||||
if (string.IsNullOrEmpty(devicePath))
|
||||
return null;
|
||||
|
||||
// Sanitize a Windows-formatted long device path
|
||||
if (devicePath.StartsWith("\\\\.\\"))
|
||||
devicePath = devicePath.Substring("\\\\.\\".Length);
|
||||
|
||||
// Create and validate the drive info object
|
||||
var driveInfo = new DriveInfo(devicePath);
|
||||
if (driveInfo == null || driveInfo == default)
|
||||
return null;
|
||||
|
||||
// Fill in the rest of the data
|
||||
drive.PopulateFromDriveInfo(driveInfo);
|
||||
|
||||
return drive;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Populate all fields from a DriveInfo object
|
||||
/// </summary>
|
||||
/// <param name="driveInfo">DriveInfo object to populate from</param>
|
||||
private void PopulateFromDriveInfo(DriveInfo? driveInfo)
|
||||
{
|
||||
// If we have an invalid DriveInfo, just return
|
||||
if (driveInfo == null || driveInfo == default)
|
||||
return;
|
||||
|
||||
// Populate the data fields
|
||||
Name = driveInfo.Name;
|
||||
MarkedActive = driveInfo.IsReady;
|
||||
if (MarkedActive)
|
||||
{
|
||||
DriveFormat = driveInfo.DriveFormat;
|
||||
TotalSize = driveInfo.TotalSize;
|
||||
VolumeLabel = driveInfo.VolumeLabel;
|
||||
}
|
||||
else
|
||||
{
|
||||
DriveFormat = string.Empty;
|
||||
TotalSize = default;
|
||||
VolumeLabel = string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
#region Public Functionality
|
||||
|
||||
/// <summary>
|
||||
/// Create a list of active drives matched to their volume labels
|
||||
/// </summary>
|
||||
/// <param name="ignoreFixedDrives">True to ignore fixed drives from population, false otherwise</param>
|
||||
/// <returns>Active drives, matched to labels, if possible</returns>
|
||||
public static List<Drive> CreateListOfDrives(bool ignoreFixedDrives)
|
||||
{
|
||||
var drives = GetDriveList(ignoreFixedDrives);
|
||||
drives = [.. drives.OrderBy(i => i == null ? "\0" : i.Name)];
|
||||
return drives;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the current media type from drive letter
|
||||
/// </summary>
|
||||
/// <param name="system"></param>
|
||||
/// <returns></returns>
|
||||
public (MediaType?, string?) GetMediaType(RedumpSystem? system)
|
||||
{
|
||||
// Take care of the non-optical stuff first
|
||||
switch (InternalDriveType)
|
||||
{
|
||||
case Frontend.InternalDriveType.Floppy:
|
||||
return (MediaType.FloppyDisk, null);
|
||||
case Frontend.InternalDriveType.HardDisk:
|
||||
return (MediaType.HardDisk, null);
|
||||
case Frontend.InternalDriveType.Removable:
|
||||
return (MediaType.FlashDrive, null);
|
||||
}
|
||||
|
||||
// Some systems should default to certain media types
|
||||
switch (system)
|
||||
{
|
||||
// CD
|
||||
case RedumpSystem.Panasonic3DOInteractiveMultiplayer:
|
||||
case RedumpSystem.PhilipsCDi:
|
||||
case RedumpSystem.SegaDreamcast:
|
||||
case RedumpSystem.SegaSaturn:
|
||||
case RedumpSystem.SonyPlayStation:
|
||||
case RedumpSystem.VideoCD:
|
||||
return (MediaType.CDROM, null);
|
||||
|
||||
// DVD
|
||||
case RedumpSystem.DVDAudio:
|
||||
case RedumpSystem.DVDVideo:
|
||||
case RedumpSystem.MicrosoftXbox:
|
||||
case RedumpSystem.MicrosoftXbox360:
|
||||
return (MediaType.DVD, null);
|
||||
|
||||
// HD-DVD
|
||||
case RedumpSystem.HDDVDVideo:
|
||||
return (MediaType.HDDVD, null);
|
||||
|
||||
// Blu-ray
|
||||
case RedumpSystem.BDVideo:
|
||||
case RedumpSystem.MicrosoftXboxOne:
|
||||
case RedumpSystem.MicrosoftXboxSeriesXS:
|
||||
case RedumpSystem.SonyPlayStation3:
|
||||
case RedumpSystem.SonyPlayStation4:
|
||||
case RedumpSystem.SonyPlayStation5:
|
||||
return (MediaType.BluRay, null);
|
||||
|
||||
// GameCube
|
||||
case RedumpSystem.NintendoGameCube:
|
||||
return (MediaType.NintendoGameCubeGameDisc, null);
|
||||
|
||||
// Wii
|
||||
case RedumpSystem.NintendoWii:
|
||||
return (MediaType.NintendoWiiOpticalDisc, null);
|
||||
|
||||
// WiiU
|
||||
case RedumpSystem.NintendoWiiU:
|
||||
return (MediaType.NintendoWiiUOpticalDisc, null);
|
||||
|
||||
// PSP
|
||||
case RedumpSystem.SonyPlayStationPortable:
|
||||
return (MediaType.UMD, null);
|
||||
}
|
||||
|
||||
// Handle optical media by size and filesystem
|
||||
if (TotalSize >= 0 && TotalSize <= 800_000_000 && (DriveFormat == "CDFS" || DriveFormat == "UDF"))
|
||||
return (MediaType.CDROM, null);
|
||||
else if (TotalSize > 800_000_000 && TotalSize <= 8_540_000_000 && (DriveFormat == "CDFS" || DriveFormat == "UDF"))
|
||||
return (MediaType.DVD, null);
|
||||
else if (TotalSize > 8_540_000_000)
|
||||
return (MediaType.BluRay, null);
|
||||
|
||||
return (null, "Could not determine media type!");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Refresh the current drive information based on path
|
||||
/// </summary>
|
||||
public void RefreshDrive()
|
||||
{
|
||||
var driveInfo = DriveInfo.GetDrives().FirstOrDefault(d => d?.Name == Name);
|
||||
PopulateFromDriveInfo(driveInfo);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Information Extraction
|
||||
|
||||
/// <summary>
|
||||
/// Get the EXE name from a PlayStation disc, if possible
|
||||
/// </summary>
|
||||
/// <returns>Executable name on success, null otherwise</returns>
|
||||
public string? GetPlayStationExecutableName()
|
||||
{
|
||||
// If there's no drive path, we can't get exe name
|
||||
if (string.IsNullOrEmpty(Name))
|
||||
return null;
|
||||
|
||||
// If the folder no longer exists, we can't get exe name
|
||||
if (!Directory.Exists(Name))
|
||||
return null;
|
||||
|
||||
// Get the two paths that we will need to check
|
||||
string psxExePath = Path.Combine(Name, "PSX.EXE");
|
||||
string systemCnfPath = Path.Combine(Name, "SYSTEM.CNF");
|
||||
|
||||
// Read the CNF file as an INI file
|
||||
var systemCnf = new IniFile(systemCnfPath);
|
||||
string? bootValue = string.Empty;
|
||||
|
||||
// PlayStation uses "BOOT" as the key
|
||||
if (systemCnf.ContainsKey("BOOT"))
|
||||
bootValue = systemCnf["BOOT"];
|
||||
|
||||
// PlayStation 2 uses "BOOT2" as the key
|
||||
if (systemCnf.ContainsKey("BOOT2"))
|
||||
bootValue = systemCnf["BOOT2"];
|
||||
|
||||
// If we had any boot value, parse it and get the executable name
|
||||
if (!string.IsNullOrEmpty(bootValue))
|
||||
{
|
||||
var match = Regex.Match(bootValue, @"cdrom.?:\\?(.*)", RegexOptions.Compiled);
|
||||
if (match.Groups.Count > 1)
|
||||
{
|
||||
string? serial = match.Groups[1].Value;
|
||||
|
||||
// Some games may have the EXE in a subfolder
|
||||
serial = Path.GetFileName(serial);
|
||||
|
||||
return serial;
|
||||
}
|
||||
}
|
||||
|
||||
// If the SYSTEM.CNF value can't be found, try PSX.EXE
|
||||
if (File.Exists(psxExePath))
|
||||
return "PSX.EXE";
|
||||
|
||||
// If neither can be found, we return null
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the EXE date from a PlayStation disc, if possible
|
||||
/// </summary>
|
||||
/// <param name="serial">Internal disc serial, if possible</param>
|
||||
/// <param name="region">Output region, if possible</param>
|
||||
/// <param name="date">Output EXE date in "yyyy-mm-dd" format if possible, null on error</param>
|
||||
/// <returns>True if information could be determined, false otherwise</returns>
|
||||
public bool GetPlayStationExecutableInfo(out string? serial, out Region? region, out string? date)
|
||||
{
|
||||
serial = null; region = null; date = null;
|
||||
|
||||
// If there's no drive path, we can't do this part
|
||||
if (string.IsNullOrEmpty(Name))
|
||||
return false;
|
||||
|
||||
// If the folder no longer exists, we can't do this part
|
||||
if (!Directory.Exists(Name))
|
||||
return false;
|
||||
|
||||
// Get the executable name
|
||||
string? exeName = GetPlayStationExecutableName();
|
||||
|
||||
// If no executable found, we can't do this part
|
||||
if (exeName == null)
|
||||
return false;
|
||||
|
||||
// EXE name may have a trailing `;` after
|
||||
// EXE name should always be in all caps
|
||||
exeName = exeName
|
||||
.Split(';')[0]
|
||||
.ToUpperInvariant();
|
||||
|
||||
// Serial is most of the EXE name normalized
|
||||
serial = exeName
|
||||
.Replace('_', '-')
|
||||
.Replace(".", string.Empty);
|
||||
|
||||
// Get the region, if possible
|
||||
region = ProcessingTool.GetPlayStationRegion(exeName);
|
||||
|
||||
// Now that we have the EXE name, try to get the fileinfo for it
|
||||
string exePath = Path.Combine(Name, exeName);
|
||||
if (!File.Exists(exePath))
|
||||
return false;
|
||||
|
||||
// Fix the Y2K timestamp issue
|
||||
var fi = new FileInfo(exePath);
|
||||
var dt = new DateTime(fi.LastWriteTimeUtc.Year >= 1900 && fi.LastWriteTimeUtc.Year < 1920 ? 2000 + fi.LastWriteTimeUtc.Year % 100 : fi.LastWriteTimeUtc.Year,
|
||||
fi.LastWriteTimeUtc.Month, fi.LastWriteTimeUtc.Day);
|
||||
date = dt.ToString("yyyy-MM-dd");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the version from a PlayStation 2 disc, if possible
|
||||
/// </summary>
|
||||
/// <returns>Game version if possible, null on error</returns>
|
||||
public string? GetPlayStation2Version()
|
||||
{
|
||||
// If there's no drive path, we can't do this part
|
||||
if (string.IsNullOrEmpty(Name))
|
||||
return null;
|
||||
|
||||
// If the folder no longer exists, we can't do this part
|
||||
if (!Directory.Exists(Name))
|
||||
return null;
|
||||
|
||||
// Get the SYSTEM.CNF path to check
|
||||
string systemCnfPath = Path.Combine(Name, "SYSTEM.CNF");
|
||||
|
||||
// Try to parse the SYSTEM.CNF file
|
||||
var systemCnf = new IniFile(systemCnfPath);
|
||||
if (systemCnf.ContainsKey("VER"))
|
||||
return systemCnf["VER"];
|
||||
|
||||
// If "VER" can't be found, we can't do much
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the internal serial from a PlayStation 3 disc, if possible
|
||||
/// </summary>
|
||||
/// <returns>Internal disc serial if possible, null on error</returns>
|
||||
public string? GetPlayStation3Serial()
|
||||
{
|
||||
// If there's no drive path, we can't do this part
|
||||
if (string.IsNullOrEmpty(Name))
|
||||
return null;
|
||||
|
||||
// If the folder no longer exists, we can't do this part
|
||||
if (!Directory.Exists(Name))
|
||||
return null;
|
||||
|
||||
// Attempt to use PS3_DISC.SFB
|
||||
string sfbPath = Path.Combine(Name, "PS3_DISC.SFB");
|
||||
if (File.Exists(sfbPath))
|
||||
{
|
||||
try
|
||||
{
|
||||
using var br = new BinaryReader(File.OpenRead(sfbPath));
|
||||
br.BaseStream.Seek(0x220, SeekOrigin.Begin);
|
||||
return new string(br.ReadChars(0x10)).TrimEnd('\0');
|
||||
}
|
||||
catch
|
||||
{
|
||||
// We don't care what the error was
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt to use PARAM.SFO
|
||||
#if NET20 || NET35
|
||||
string sfoPath = Path.Combine(Path.Combine(Name, "PS3_GAME"), "PARAM.SFO");
|
||||
#else
|
||||
string sfoPath = Path.Combine(Name, "PS3_GAME", "PARAM.SFO");
|
||||
#endif
|
||||
if (File.Exists(sfoPath))
|
||||
{
|
||||
try
|
||||
{
|
||||
using var br = new BinaryReader(File.OpenRead(sfoPath));
|
||||
br.BaseStream.Seek(-0x18, SeekOrigin.End);
|
||||
return new string(br.ReadChars(9)).TrimEnd('\0').Insert(4, "-");
|
||||
}
|
||||
catch
|
||||
{
|
||||
// We don't care what the error was
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the version from a PlayStation 3 disc, if possible
|
||||
/// </summary>
|
||||
/// <returns>Game version if possible, null on error</returns>
|
||||
public string? GetPlayStation3Version()
|
||||
{
|
||||
// If there's no drive path, we can't do this part
|
||||
if (string.IsNullOrEmpty(Name))
|
||||
return null;
|
||||
|
||||
// If the folder no longer exists, we can't do this part
|
||||
if (!Directory.Exists(Name))
|
||||
return null;
|
||||
|
||||
// Attempt to use PS3_DISC.SFB
|
||||
string sfbPath = Path.Combine(Name, "PS3_DISC.SFB");
|
||||
if (File.Exists(sfbPath))
|
||||
{
|
||||
try
|
||||
{
|
||||
using var br = new BinaryReader(File.OpenRead(sfbPath));
|
||||
br.BaseStream.Seek(0x230, SeekOrigin.Begin);
|
||||
var discVersion = new string(br.ReadChars(0x10)).TrimEnd('\0');
|
||||
if (!string.IsNullOrEmpty(discVersion))
|
||||
return discVersion;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// We don't care what the error was
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt to use PARAM.SFO
|
||||
#if NET20 || NET35
|
||||
string sfoPath = Path.Combine(Path.Combine(Name, "PS3_GAME"), "PARAM.SFO");
|
||||
#else
|
||||
string sfoPath = Path.Combine(Name, "PS3_GAME", "PARAM.SFO");
|
||||
#endif
|
||||
if (File.Exists(sfoPath))
|
||||
{
|
||||
try
|
||||
{
|
||||
using var br = new BinaryReader(File.OpenRead(sfoPath));
|
||||
br.BaseStream.Seek(-0x08, SeekOrigin.End);
|
||||
return new string(br.ReadChars(5)).TrimEnd('\0');
|
||||
}
|
||||
catch
|
||||
{
|
||||
// We don't care what the error was
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the firmware version from a PlayStation 3 disc, if possible
|
||||
/// </summary>
|
||||
/// <returns>Firmware version if possible, null on error</returns>
|
||||
public string? GetPlayStation3FirmwareVersion()
|
||||
{
|
||||
// If there's no drive path, we can't do this part
|
||||
if (string.IsNullOrEmpty(Name))
|
||||
return null;
|
||||
|
||||
// If the folder no longer exists, we can't do this part
|
||||
if (!Directory.Exists(Name))
|
||||
return null;
|
||||
|
||||
// Attempt to read from /PS3_UPDATE/PS3UPDAT.PUP
|
||||
#if NET20 || NET35
|
||||
string pupPath = Path.Combine(Path.Combine(Name, "PS3_UPDATE"), "PS3UPDAT.PUP");
|
||||
#else
|
||||
string pupPath = Path.Combine(Name, "PS3_UPDATE", "PS3UPDAT.PUP");
|
||||
#endif
|
||||
if (!File.Exists(pupPath))
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
using var br = new BinaryReader(File.OpenRead(pupPath));
|
||||
br.BaseStream.Seek(0x3E, SeekOrigin.Begin);
|
||||
byte[] buf = new byte[2];
|
||||
br.Read(buf, 0, 2);
|
||||
Array.Reverse(buf);
|
||||
short location = BitConverter.ToInt16(buf, 0);
|
||||
br.BaseStream.Seek(location, SeekOrigin.Begin);
|
||||
return new string(br.ReadChars(4));
|
||||
}
|
||||
catch
|
||||
{
|
||||
// We don't care what the error was
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the internal serial from a PlayStation 4 disc, if possible
|
||||
/// </summary>
|
||||
/// <returns>Internal disc serial if possible, null on error</returns>
|
||||
public string? GetPlayStation4Serial()
|
||||
{
|
||||
// If there's no drive path, we can't do this part
|
||||
if (string.IsNullOrEmpty(Name))
|
||||
return null;
|
||||
|
||||
// If the folder no longer exists, we can't do this part
|
||||
if (!Directory.Exists(Name))
|
||||
return null;
|
||||
|
||||
// If we can't find param.sfo, we don't have a PlayStation 4 disc
|
||||
#if NET20 || NET35
|
||||
string paramSfoPath = Path.Combine(Path.Combine(Name, "bd"), "param.sfo");
|
||||
#else
|
||||
string paramSfoPath = Path.Combine(Name, "bd", "param.sfo");
|
||||
#endif
|
||||
if (!File.Exists(paramSfoPath))
|
||||
return null;
|
||||
|
||||
// Let's try reading param.sfo to find the serial at the end of the file
|
||||
try
|
||||
{
|
||||
using var br = new BinaryReader(File.OpenRead(paramSfoPath));
|
||||
br.BaseStream.Seek(-0x14, SeekOrigin.End);
|
||||
return new string(br.ReadChars(9)).Insert(4, "-");
|
||||
}
|
||||
catch
|
||||
{
|
||||
// We don't care what the error was
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the version from a PlayStation 4 disc, if possible
|
||||
/// </summary>
|
||||
/// <returns>Game version if possible, null on error</returns>
|
||||
public string? GetPlayStation4Version()
|
||||
{
|
||||
// If there's no drive path, we can't do this part
|
||||
if (string.IsNullOrEmpty(Name))
|
||||
return null;
|
||||
|
||||
// If the folder no longer exists, we can't do this part
|
||||
if (!Directory.Exists(Name))
|
||||
return null;
|
||||
|
||||
// If we can't find param.sfo, we don't have a PlayStation 4 disc
|
||||
#if NET20 || NET35
|
||||
string paramSfoPath = Path.Combine(Path.Combine(Name, "bd"), "param.sfo");
|
||||
#else
|
||||
string paramSfoPath = Path.Combine(Name, "bd", "param.sfo");
|
||||
#endif
|
||||
if (!File.Exists(paramSfoPath))
|
||||
return null;
|
||||
|
||||
// Let's try reading param.sfo to find the version at the end of the file
|
||||
try
|
||||
{
|
||||
using var br = new BinaryReader(File.OpenRead(paramSfoPath));
|
||||
br.BaseStream.Seek(-0x08, SeekOrigin.End);
|
||||
return new string(br.ReadChars(5));
|
||||
}
|
||||
catch
|
||||
{
|
||||
// We don't care what the error was
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the internal serial from a PlayStation 5 disc, if possible
|
||||
/// </summary>
|
||||
/// <returns>Internal disc serial if possible, null on error</returns>
|
||||
public string? GetPlayStation5Serial()
|
||||
{
|
||||
// Attempt to get the param.json file
|
||||
var json = GetPlayStation5ParamsJsonFromDrive();
|
||||
if (json == null)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
return json["disc"]?[0]?["masterDataId"]?.Value<string>()?.Insert(4, "-");
|
||||
}
|
||||
catch
|
||||
{
|
||||
// We don't care what the error was
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// <summary>
|
||||
/// Get the version from a PlayStation 5 disc, if possible
|
||||
/// </summary>
|
||||
/// <returns>Game version if possible, null on error</returns>
|
||||
public string? GetPlayStation5Version()
|
||||
{
|
||||
// Attempt to get the param.json file
|
||||
var json = GetPlayStation5ParamsJsonFromDrive();
|
||||
if (json == null)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
return json["masterVersion"]?.Value<string>();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// We don't care what the error was
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the params.json file from a drive path, if possible
|
||||
/// </summary>
|
||||
/// <returns>JObject representing the JSON on success, null on error</returns>
|
||||
private JObject? GetPlayStation5ParamsJsonFromDrive()
|
||||
{
|
||||
// If there's no drive path, we can't do this part
|
||||
if (string.IsNullOrEmpty(Name))
|
||||
return null;
|
||||
|
||||
// If the folder no longer exists, we can't do this part
|
||||
if (!Directory.Exists(Name))
|
||||
return null;
|
||||
|
||||
// If we can't find param.json, we don't have a PlayStation 5 disc
|
||||
#if NET20 || NET35
|
||||
string paramJsonPath = Path.Combine(Path.Combine(Name, "bd"), "param.json");
|
||||
#else
|
||||
string paramJsonPath = Path.Combine(Name, "bd", "param.json");
|
||||
#endif
|
||||
return GetPlayStation5ParamsJsonFromFile(paramJsonPath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the params.json file from a filename, if possible
|
||||
/// </summary>
|
||||
/// <param name="filename">Filename to check</param>
|
||||
/// <returns>JObject representing the JSON on success, null on error</returns>
|
||||
private static JObject? GetPlayStation5ParamsJsonFromFile(string? filename)
|
||||
{
|
||||
// If the file doesn't exist
|
||||
if (string.IsNullOrEmpty(filename) || !File.Exists(filename))
|
||||
return null;
|
||||
|
||||
// Let's try reading param.json to find the version in the unencrypted JSON
|
||||
try
|
||||
{
|
||||
using var br = new BinaryReader(File.OpenRead(filename));
|
||||
br.BaseStream.Seek(0x800, SeekOrigin.Begin);
|
||||
byte[] jsonBytes = br.ReadBytes((int)(br.BaseStream.Length - 0x800));
|
||||
return JsonConvert.DeserializeObject(Encoding.ASCII.GetString(jsonBytes)) as JObject;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// We don't care what the error was
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Helpers
|
||||
|
||||
/// <summary>
|
||||
/// Get all current attached Drives
|
||||
/// </summary>
|
||||
/// <param name="ignoreFixedDrives">True to ignore fixed drives from population, false otherwise</param>
|
||||
/// <returns>List of drives, null on error</returns>
|
||||
/// <remarks>
|
||||
/// https://stackoverflow.com/questions/3060796/how-to-distinguish-between-usb-and-floppy-devices?utm_medium=organic&utm_source=google_rich_qa&utm_campaign=google_rich_qa
|
||||
/// https://msdn.microsoft.com/en-us/library/aa394173(v=vs.85).aspx
|
||||
/// </remarks>
|
||||
private static List<Drive> GetDriveList(bool ignoreFixedDrives)
|
||||
{
|
||||
var desiredDriveTypes = new List<DriveType>() { DriveType.CDRom };
|
||||
if (!ignoreFixedDrives)
|
||||
{
|
||||
desiredDriveTypes.Add(DriveType.Fixed);
|
||||
desiredDriveTypes.Add(DriveType.Removable);
|
||||
}
|
||||
|
||||
// TODO: Reduce reliance on `DriveInfo`
|
||||
// https://github.com/aaru-dps/Aaru/blob/5164a154e2145941472f2ee0aeb2eff3338ecbb3/Aaru.Devices/Windows/ListDevices.cs#L66
|
||||
|
||||
// Create an output drive list
|
||||
var drives = new List<Drive>();
|
||||
|
||||
// Get all standard supported drive types
|
||||
try
|
||||
{
|
||||
drives = DriveInfo.GetDrives()
|
||||
.Where(d => desiredDriveTypes.Contains(d.DriveType))
|
||||
.Select(d => Create(ToInternalDriveType(d.DriveType), d.Name) ?? new Drive())
|
||||
.ToList();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return drives;
|
||||
}
|
||||
|
||||
// Find and update all floppy drives
|
||||
#if NET462_OR_GREATER || NETCOREAPP
|
||||
try
|
||||
{
|
||||
CimSession session = CimSession.Create(null);
|
||||
var collection = session.QueryInstances("root\\CIMV2", "WQL", "SELECT * FROM Win32_LogicalDisk");
|
||||
|
||||
foreach (CimInstance instance in collection)
|
||||
{
|
||||
CimKeyedCollection<CimProperty> properties = instance.CimInstanceProperties;
|
||||
uint? mediaType = properties["MediaType"]?.Value as uint?;
|
||||
if (mediaType != null && ((mediaType > 0 && mediaType < 11) || (mediaType > 12 && mediaType < 22)))
|
||||
{
|
||||
char devId = (properties["Caption"].Value as string ?? string.Empty)[0];
|
||||
drives.ForEach(d => { if (d?.Name != null && d.Name[0] == devId) { d.InternalDriveType = Frontend.InternalDriveType.Floppy; } });
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// No-op
|
||||
}
|
||||
#endif
|
||||
|
||||
return drives;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert drive type to internal version, if possible
|
||||
/// </summary>
|
||||
/// <param name="driveType">DriveType value to check</param>
|
||||
/// <returns>InternalDriveType, if possible, null on error</returns>
|
||||
internal static InternalDriveType? ToInternalDriveType(DriveType driveType)
|
||||
{
|
||||
return driveType switch
|
||||
{
|
||||
DriveType.CDRom => (InternalDriveType?)Frontend.InternalDriveType.Optical,
|
||||
DriveType.Fixed => (InternalDriveType?)Frontend.InternalDriveType.HardDisk,
|
||||
DriveType.Removable => (InternalDriveType?)Frontend.InternalDriveType.Removable,
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
839
MPF.Frontend/DumpEnvironment.cs
Normal file
839
MPF.Frontend/DumpEnvironment.cs
Normal file
@@ -0,0 +1,839 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
#if NET40
|
||||
using System.Threading;
|
||||
#endif
|
||||
using System.Threading.Tasks;
|
||||
using BinaryObjectScanner;
|
||||
using MPF.ExecutionContexts;
|
||||
using MPF.Frontend.Tools;
|
||||
using MPF.Processors;
|
||||
using Newtonsoft.Json;
|
||||
using SabreTools.RedumpLib;
|
||||
using SabreTools.RedumpLib.Data;
|
||||
using Formatting = Newtonsoft.Json.Formatting;
|
||||
|
||||
namespace MPF.Frontend
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the state of all settings to be used during dumping
|
||||
/// </summary>
|
||||
public class DumpEnvironment
|
||||
{
|
||||
#region Output paths
|
||||
|
||||
/// <summary>
|
||||
/// Base output file path to write files to
|
||||
/// </summary>
|
||||
public string OutputPath { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region UI information
|
||||
|
||||
/// <summary>
|
||||
/// Drive object representing the current drive
|
||||
/// </summary>
|
||||
private Drive? _drive;
|
||||
|
||||
/// <summary>
|
||||
/// ExecutionContext object representing how to invoke the internal program
|
||||
/// </summary>
|
||||
private BaseExecutionContext? _executionContext;
|
||||
|
||||
/// <summary>
|
||||
/// Currently selected dumping program
|
||||
/// </summary>
|
||||
private readonly InternalProgram _internalProgram;
|
||||
|
||||
/// <summary>
|
||||
/// Options object representing user-defined options
|
||||
/// </summary>
|
||||
private readonly Frontend.Options _options;
|
||||
|
||||
/// <summary>
|
||||
/// Processor object representing how to process the outputs
|
||||
/// </summary>
|
||||
private BaseProcessor? _processor;
|
||||
|
||||
/// <summary>
|
||||
/// Currently selected system
|
||||
/// </summary>
|
||||
private readonly RedumpSystem? _system;
|
||||
|
||||
/// <summary>
|
||||
/// Currently selected media type
|
||||
/// </summary>
|
||||
private readonly MediaType? _type;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Passthrough Fields
|
||||
|
||||
/// <inheritdoc cref="BaseExecutionContext.InputPath"/>
|
||||
public string? ContextInputPath => _executionContext?.InputPath;
|
||||
|
||||
/// <inheritdoc cref="BaseExecutionContext.OutputPath"/>
|
||||
public string? ContextOutputPath => _executionContext?.OutputPath;
|
||||
|
||||
/// <inheritdoc cref="Drive.MarkedActive/>
|
||||
public bool DriveMarkedActive => _drive?.MarkedActive ?? false;
|
||||
|
||||
/// <inheritdoc cref="Drive.Name/>
|
||||
public string? DriveName => _drive?.Name;
|
||||
|
||||
/// <inheritdoc cref="BaseExecutionContext.Speed"/>
|
||||
public int? Speed
|
||||
{
|
||||
get => _executionContext?.Speed;
|
||||
set
|
||||
{
|
||||
if (_executionContext != null)
|
||||
_executionContext.Speed = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Extensions.LongName(RedumpSystem?)/>
|
||||
public string? SystemName => _system.LongName();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Event Handlers
|
||||
|
||||
/// <summary>
|
||||
/// Generic way of reporting a message
|
||||
/// </summary>
|
||||
public EventHandler<StringEventArgs>? ReportStatus;
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for a full DumpEnvironment object from user information
|
||||
/// </summary>
|
||||
/// <param name="options"></param>
|
||||
/// <param name="outputPath"></param>
|
||||
/// <param name="drive"></param>
|
||||
/// <param name="system"></param>
|
||||
/// <param name="type"></param>
|
||||
/// <param name="internalProgram"></param>
|
||||
/// <param name="parameters"></param>
|
||||
public DumpEnvironment(Frontend.Options options,
|
||||
string? outputPath,
|
||||
Drive? drive,
|
||||
RedumpSystem? system,
|
||||
MediaType? type,
|
||||
InternalProgram? internalProgram,
|
||||
string? parameters)
|
||||
{
|
||||
// Set options object
|
||||
_options = options;
|
||||
|
||||
// Output paths
|
||||
OutputPath = FrontendTool.NormalizeOutputPaths(outputPath, false);
|
||||
|
||||
// UI information
|
||||
_drive = drive;
|
||||
_system = system ?? options.DefaultSystem;
|
||||
_type = type ?? MediaType.NONE;
|
||||
_internalProgram = internalProgram ?? options.InternalProgram;
|
||||
|
||||
// Dumping program
|
||||
SetExecutionContext(parameters);
|
||||
SetProcessor();
|
||||
}
|
||||
|
||||
#region Internal Program Management
|
||||
|
||||
/// <summary>
|
||||
/// Check output path for matching logs from all dumping programs
|
||||
/// </summary>
|
||||
public InternalProgram? CheckForMatchingProgram(string? outputDirectory, string outputFilename)
|
||||
{
|
||||
// If a complete dump exists from a different program
|
||||
InternalProgram? programFound = null;
|
||||
if (programFound == null && _internalProgram != InternalProgram.Aaru)
|
||||
{
|
||||
var processor = new Processors.Aaru(_system, _type);
|
||||
(bool foundOtherFiles, _) = processor.FoundAllFiles(outputDirectory, outputFilename, true);
|
||||
if (foundOtherFiles)
|
||||
programFound = InternalProgram.Aaru;
|
||||
}
|
||||
if (programFound == null && _internalProgram != InternalProgram.DiscImageCreator)
|
||||
{
|
||||
var processor = new Processors.DiscImageCreator(_system, _type);
|
||||
(bool foundOtherFiles, _) = processor.FoundAllFiles(outputDirectory, outputFilename, true);
|
||||
if (foundOtherFiles)
|
||||
programFound = InternalProgram.DiscImageCreator;
|
||||
}
|
||||
if (programFound == null && _internalProgram != InternalProgram.Redumper)
|
||||
{
|
||||
var processor = new Processors.Redumper(_system, _type);
|
||||
(bool foundOtherFiles, _) = processor.FoundAllFiles(outputDirectory, outputFilename, true);
|
||||
if (foundOtherFiles)
|
||||
programFound = InternalProgram.Redumper;
|
||||
}
|
||||
|
||||
return programFound;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the parameters object based on the internal program and parameters string
|
||||
/// </summary>
|
||||
/// <param name="parameters">String representation of the parameters</param>
|
||||
public bool SetExecutionContext(string? parameters)
|
||||
{
|
||||
_executionContext = _internalProgram switch
|
||||
{
|
||||
InternalProgram.Aaru => new ExecutionContexts.Aaru.ExecutionContext(parameters) { ExecutablePath = _options.AaruPath },
|
||||
InternalProgram.DiscImageCreator => new ExecutionContexts.DiscImageCreator.ExecutionContext(parameters) { ExecutablePath = _options.DiscImageCreatorPath },
|
||||
InternalProgram.Redumper => new ExecutionContexts.Redumper.ExecutionContext(parameters) { ExecutablePath = _options.RedumperPath },
|
||||
|
||||
// If no dumping program found, set to null
|
||||
InternalProgram.NONE => null,
|
||||
_ => null,
|
||||
};
|
||||
|
||||
// Set system, type, and drive
|
||||
if (_executionContext != null)
|
||||
{
|
||||
_executionContext.System = _system;
|
||||
_executionContext.Type = _type;
|
||||
|
||||
// Set some parameters, if not already set
|
||||
OutputPath ??= _executionContext.OutputPath!;
|
||||
_drive ??= Drive.Create(InternalDriveType.Optical, _executionContext.InputPath!);
|
||||
}
|
||||
|
||||
return _executionContext != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the processor object based on the internal program
|
||||
/// </summary>
|
||||
public bool SetProcessor()
|
||||
{
|
||||
_processor = _internalProgram switch
|
||||
{
|
||||
InternalProgram.Aaru => new Processors.Aaru(_system, _type),
|
||||
InternalProgram.CleanRip => new CleanRip(_system, _type),
|
||||
InternalProgram.DiscImageCreator => new DiscImageCreator(_system, _type),
|
||||
InternalProgram.PS3CFW => new PS3CFW(_system, _type),
|
||||
InternalProgram.Redumper => new Redumper(_system, _type),
|
||||
InternalProgram.UmdImageCreator => new UmdImageCreator(_system, _type),
|
||||
InternalProgram.XboxBackupCreator => new XboxBackupCreator(_system, _type),
|
||||
|
||||
// If no dumping program found, set to null
|
||||
InternalProgram.NONE => null,
|
||||
_ => null,
|
||||
};
|
||||
|
||||
return _processor != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the full parameter string for either DiscImageCreator or Aaru
|
||||
/// </summary>
|
||||
/// <param name="driveSpeed">Nullable int representing the drive speed</param>
|
||||
/// <returns>String representing the params, null on error</returns>
|
||||
public string? GetFullParameters(int? driveSpeed)
|
||||
{
|
||||
// Populate with the correct params for inputs (if we're not on the default option)
|
||||
if (_system != null && _type != MediaType.NONE)
|
||||
{
|
||||
// If drive letter is invalid, skip this
|
||||
if (_drive == null)
|
||||
return null;
|
||||
|
||||
// Set the proper parameters
|
||||
_executionContext = _internalProgram switch
|
||||
{
|
||||
InternalProgram.Aaru => new ExecutionContexts.Aaru.ExecutionContext(_system, _type, _drive.Name, OutputPath, driveSpeed, _options.Settings),
|
||||
InternalProgram.DiscImageCreator => new ExecutionContexts.DiscImageCreator.ExecutionContext(_system, _type, _drive.Name, OutputPath, driveSpeed, _options.Settings),
|
||||
InternalProgram.Redumper => new ExecutionContexts.Redumper.ExecutionContext(_system, _type, _drive.Name, OutputPath, driveSpeed, _options.Settings),
|
||||
|
||||
// If no dumping program found, set to null
|
||||
InternalProgram.NONE => null,
|
||||
_ => null,
|
||||
};
|
||||
|
||||
// Generate and return the param string
|
||||
return _executionContext?.GenerateParameters();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Passthrough Functionality
|
||||
|
||||
/// <inheritdoc cref="Extensions.DetectedByWindows(RedumpSystem?)"/>
|
||||
public bool DetectedByWindows() => _system.DetectedByWindows();
|
||||
|
||||
/// <summary>
|
||||
/// Determine if the media supports drive speeds
|
||||
/// </summary>
|
||||
/// <param name="type">MediaType value to check</param>
|
||||
/// <returns>True if the media has variable dumping speeds, false otherwise</returns>
|
||||
public bool DoesSupportDriveSpeed()
|
||||
{
|
||||
return _type switch
|
||||
{
|
||||
MediaType.CDROM
|
||||
or MediaType.DVD
|
||||
or MediaType.GDROM
|
||||
or MediaType.HDDVD
|
||||
or MediaType.BluRay
|
||||
or MediaType.NintendoGameCubeGameDisc
|
||||
or MediaType.NintendoWiiOpticalDisc => true,
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="BaseProcessor.FoundAllFiles(string?, string, bool)"/>
|
||||
public bool FoundAllFiles(string? outputDirectory, string outputFilename, bool preCheck)
|
||||
{
|
||||
if (_processor == null)
|
||||
return false;
|
||||
|
||||
return _processor.FoundAllFiles(outputDirectory, outputFilename, preCheck).Item1;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="BaseExecutionContext.GetDefaultExtension(MediaType?)"/>
|
||||
public string? GetDefaultExtension(MediaType? mediaType)
|
||||
{
|
||||
if (_executionContext == null)
|
||||
return null;
|
||||
|
||||
return _executionContext.GetDefaultExtension(mediaType);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="BaseExecutionContext.GetMediaType()"/>
|
||||
public MediaType? GetMediaType()
|
||||
{
|
||||
if (_executionContext == null)
|
||||
return null;
|
||||
|
||||
return _executionContext.GetMediaType();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify that, given a system and a media type, they are correct
|
||||
/// </summary>
|
||||
public ResultEventArgs GetSupportStatus()
|
||||
{
|
||||
// No system chosen, update status
|
||||
if (_system == null)
|
||||
return ResultEventArgs.Failure("Please select a valid system");
|
||||
|
||||
// If we're on an unsupported type, update the status accordingly
|
||||
return _type switch
|
||||
{
|
||||
// Fully supported types
|
||||
MediaType.BluRay
|
||||
or MediaType.CDROM
|
||||
or MediaType.DVD
|
||||
or MediaType.FloppyDisk
|
||||
or MediaType.HardDisk
|
||||
or MediaType.CompactFlash
|
||||
or MediaType.SDCard
|
||||
or MediaType.FlashDrive
|
||||
or MediaType.HDDVD => ResultEventArgs.Success($"{_type.LongName()} ready to dump"),
|
||||
|
||||
// Partially supported types
|
||||
MediaType.GDROM
|
||||
or MediaType.NintendoGameCubeGameDisc
|
||||
or MediaType.NintendoWiiOpticalDisc => ResultEventArgs.Success($"{_type.LongName()} partially supported for dumping"),
|
||||
|
||||
// Special case for other supported tools
|
||||
MediaType.UMD => ResultEventArgs.Failure($"{_type.LongName()} supported for submission info parsing"),
|
||||
|
||||
// Specifically unknown type
|
||||
MediaType.NONE => ResultEventArgs.Failure($"Please select a valid media type"),
|
||||
|
||||
// Undumpable but recognized types
|
||||
_ => ResultEventArgs.Failure($"{_type.LongName()} media are not supported for dumping"),
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="BaseExecutionContext.IsDumpingCommand()"/>
|
||||
public bool IsDumpingCommand()
|
||||
{
|
||||
if (_executionContext == null)
|
||||
return false;
|
||||
|
||||
return _executionContext.IsDumpingCommand();
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Drive.RefreshDrive"/>
|
||||
public void RefreshDrive() => _drive?.RefreshDrive();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Dumping
|
||||
|
||||
/// <summary>
|
||||
/// Cancel an in-progress dumping process
|
||||
/// </summary>
|
||||
public void CancelDumping() => _executionContext?.KillInternalProgram();
|
||||
|
||||
/// <summary>
|
||||
/// Execute the initial invocation of the dumping programs
|
||||
/// </summary>
|
||||
/// <param name="progress">Optional result progress callback</param>
|
||||
public async Task<ResultEventArgs> Run(IProgress<ResultEventArgs>? progress = null)
|
||||
{
|
||||
// If we don't have parameters
|
||||
if (_executionContext == null)
|
||||
return ResultEventArgs.Failure("Error! Current configuration is not supported!");
|
||||
|
||||
// Check that we have the basics for dumping
|
||||
ResultEventArgs result = IsValidForDump();
|
||||
if (!result)
|
||||
return result;
|
||||
|
||||
// Execute internal tool
|
||||
progress?.Report(ResultEventArgs.Success($"Executing {_internalProgram}... please wait!"));
|
||||
|
||||
var directoryName = Path.GetDirectoryName(OutputPath);
|
||||
if (!string.IsNullOrEmpty(directoryName))
|
||||
Directory.CreateDirectory(directoryName);
|
||||
|
||||
#if NET40
|
||||
await Task.Factory.StartNew(() => { _executionContext.ExecuteInternalProgram(); return true; });
|
||||
#else
|
||||
await Task.Run(_executionContext.ExecuteInternalProgram);
|
||||
#endif
|
||||
progress?.Report(ResultEventArgs.Success($"{_internalProgram} has finished!"));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify that the current environment has a complete dump and create submission info is possible
|
||||
/// </summary>
|
||||
/// <param name="resultProgress">Optional result progress callback</param>
|
||||
/// <param name="protectionProgress">Optional protection progress callback</param>
|
||||
/// <param name="processUserInfo">Optional user prompt to deal with submission information</param>
|
||||
/// <param name="seedInfo">A seed SubmissionInfo object that contains user data</param>
|
||||
/// <returns>Result instance with the outcome</returns>
|
||||
public async Task<ResultEventArgs> VerifyAndSaveDumpOutput(
|
||||
IProgress<ResultEventArgs>? resultProgress = null,
|
||||
IProgress<ProtectionProgress>? protectionProgress = null,
|
||||
Func<SubmissionInfo?, (bool?, SubmissionInfo?)>? processUserInfo = null,
|
||||
SubmissionInfo? seedInfo = null)
|
||||
{
|
||||
if (_processor == null)
|
||||
return ResultEventArgs.Failure("Error! Current configuration is not supported!");
|
||||
|
||||
resultProgress?.Report(ResultEventArgs.Success("Gathering submission information... please wait!"));
|
||||
|
||||
// Get the output directory and filename separately
|
||||
var outputDirectory = Path.GetDirectoryName(OutputPath);
|
||||
var outputFilename = Path.GetFileName(OutputPath);
|
||||
|
||||
// Check to make sure that the output had all the correct files
|
||||
(bool foundFiles, List<string> missingFiles) = _processor.FoundAllFiles(outputDirectory, outputFilename, false);
|
||||
if (!foundFiles)
|
||||
{
|
||||
resultProgress?.Report(ResultEventArgs.Failure($"There were files missing from the output:\n{string.Join("\n", [.. missingFiles])}"));
|
||||
return ResultEventArgs.Failure("Error! Please check output directory as dump may be incomplete!");
|
||||
}
|
||||
|
||||
// Extract the information from the output files
|
||||
resultProgress?.Report(ResultEventArgs.Success("Extracting output information from output files..."));
|
||||
var submissionInfo = await SubmissionGenerator.ExtractOutputInformation(
|
||||
OutputPath,
|
||||
_drive,
|
||||
_system,
|
||||
_type,
|
||||
_options,
|
||||
_processor,
|
||||
resultProgress,
|
||||
protectionProgress);
|
||||
resultProgress?.Report(ResultEventArgs.Success("Extracting information complete!"));
|
||||
|
||||
// Inject seed submission info data, if necessary
|
||||
if (seedInfo != null)
|
||||
{
|
||||
resultProgress?.Report(ResultEventArgs.Success("Injecting user-supplied information..."));
|
||||
Builder.InjectSubmissionInformation(submissionInfo, seedInfo);
|
||||
resultProgress?.Report(ResultEventArgs.Success("Information injection complete!"));
|
||||
}
|
||||
|
||||
// Get user-modifiable information if confugured to
|
||||
if (_options.PromptForDiscInformation && processUserInfo != null)
|
||||
{
|
||||
resultProgress?.Report(ResultEventArgs.Success("Waiting for additional disc information..."));
|
||||
|
||||
bool? filledInfo;
|
||||
(filledInfo, submissionInfo) = processUserInfo(submissionInfo);
|
||||
|
||||
if (filledInfo == true)
|
||||
resultProgress?.Report(ResultEventArgs.Success("Additional disc information added!"));
|
||||
else
|
||||
resultProgress?.Report(ResultEventArgs.Success("Disc information skipped!"));
|
||||
}
|
||||
|
||||
// Process special fields for site codes
|
||||
resultProgress?.Report(ResultEventArgs.Success("Processing site codes..."));
|
||||
Formatter.ProcessSpecialFields(submissionInfo);
|
||||
resultProgress?.Report(ResultEventArgs.Success("Processing complete!"));
|
||||
|
||||
// Format the information for the text output
|
||||
resultProgress?.Report(ResultEventArgs.Success("Formatting information..."));
|
||||
(var formattedValues, var formatResult) = Formatter.FormatOutputData(submissionInfo, _options.EnableRedumpCompatibility);
|
||||
if (formattedValues == null)
|
||||
resultProgress?.Report(ResultEventArgs.Failure(formatResult));
|
||||
else
|
||||
resultProgress?.Report(ResultEventArgs.Success(formatResult));
|
||||
|
||||
// Get the filename suffix for auto-generated files
|
||||
var filenameSuffix = _options.AddFilenameSuffix ? Path.GetFileNameWithoutExtension(outputFilename) : null;
|
||||
|
||||
// Write the text output
|
||||
resultProgress?.Report(ResultEventArgs.Success("Writing submission information file..."));
|
||||
(bool txtSuccess, string txtResult) = WriteOutputData(outputDirectory, filenameSuffix, formattedValues);
|
||||
if (txtSuccess)
|
||||
resultProgress?.Report(ResultEventArgs.Success(txtResult));
|
||||
else
|
||||
resultProgress?.Report(ResultEventArgs.Failure(txtResult));
|
||||
|
||||
// Write the copy protection output
|
||||
if (submissionInfo?.CopyProtection?.FullProtections != null && submissionInfo.CopyProtection.FullProtections.Any())
|
||||
{
|
||||
if (_options.ScanForProtection)
|
||||
{
|
||||
resultProgress?.Report(ResultEventArgs.Success("Writing protection information file..."));
|
||||
bool scanSuccess = WriteProtectionData(outputDirectory, filenameSuffix, submissionInfo, _options.HideDriveLetters);
|
||||
if (scanSuccess)
|
||||
resultProgress?.Report(ResultEventArgs.Success("Writing complete!"));
|
||||
else
|
||||
resultProgress?.Report(ResultEventArgs.Failure("Writing could not complete!"));
|
||||
}
|
||||
}
|
||||
|
||||
// Write the JSON output, if required
|
||||
if (_options.OutputSubmissionJSON)
|
||||
{
|
||||
resultProgress?.Report(ResultEventArgs.Success($"Writing submission information JSON file{(_options.IncludeArtifacts ? " with artifacts" : string.Empty)}..."));
|
||||
bool jsonSuccess = WriteOutputData(outputDirectory, filenameSuffix, submissionInfo, _options.IncludeArtifacts);
|
||||
if (jsonSuccess)
|
||||
resultProgress?.Report(ResultEventArgs.Success("Writing complete!"));
|
||||
else
|
||||
resultProgress?.Report(ResultEventArgs.Failure("Writing could not complete!"));
|
||||
}
|
||||
|
||||
// Compress the logs, if required
|
||||
if (_options.CompressLogFiles)
|
||||
{
|
||||
resultProgress?.Report(ResultEventArgs.Success("Compressing log files..."));
|
||||
(bool compressSuccess, string compressResult) = _processor?.CompressLogFiles(outputDirectory, filenameSuffix, outputFilename) ?? (false, "No processor provided!");
|
||||
if (compressSuccess)
|
||||
resultProgress?.Report(ResultEventArgs.Success(compressResult));
|
||||
else
|
||||
resultProgress?.Report(ResultEventArgs.Failure(compressResult));
|
||||
}
|
||||
|
||||
// Delete unnecessary files, if required
|
||||
if (_options.DeleteUnnecessaryFiles)
|
||||
{
|
||||
resultProgress?.Report(ResultEventArgs.Success("Deleting unnecessary files..."));
|
||||
(bool deleteSuccess, string deleteResult) = _processor?.DeleteUnnecessaryFiles(outputDirectory, outputFilename) ?? (false, "No processor provided!");
|
||||
if (deleteSuccess)
|
||||
resultProgress?.Report(ResultEventArgs.Success(deleteResult));
|
||||
else
|
||||
resultProgress?.Report(ResultEventArgs.Failure(deleteResult));
|
||||
}
|
||||
|
||||
// Create PS3 IRD, if required
|
||||
if (_options.CreateIRDAfterDumping && _system == RedumpSystem.SonyPlayStation3 && _type == MediaType.BluRay)
|
||||
{
|
||||
resultProgress?.Report(ResultEventArgs.Success("Creating IRD... please wait!"));
|
||||
(bool deleteSuccess, string deleteResult) = await WriteIRD(OutputPath, submissionInfo?.Extras?.DiscKey, submissionInfo?.Extras?.DiscID, submissionInfo?.Extras?.PIC, submissionInfo?.SizeAndChecksums?.Layerbreak, submissionInfo?.SizeAndChecksums?.CRC32);
|
||||
if (deleteSuccess)
|
||||
resultProgress?.Report(ResultEventArgs.Success(deleteResult));
|
||||
else
|
||||
resultProgress?.Report(ResultEventArgs.Failure(deleteResult));
|
||||
}
|
||||
|
||||
resultProgress?.Report(ResultEventArgs.Success("Submission information process complete!"));
|
||||
return ResultEventArgs.Success();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the parameters are valid
|
||||
/// </summary>
|
||||
/// <returns>True if the configuration is valid, false otherwise</returns>
|
||||
internal bool ParametersValid()
|
||||
{
|
||||
// Missing drive means it can never be valid
|
||||
if (_drive == null)
|
||||
return false;
|
||||
|
||||
bool parametersValid = _executionContext?.IsValid() ?? false;
|
||||
bool floppyValid = !(_drive.InternalDriveType == InternalDriveType.Floppy ^ _type == MediaType.FloppyDisk);
|
||||
|
||||
// TODO: HardDisk being in the Removable category is a hack, fix this later
|
||||
bool removableDiskValid = !((_drive.InternalDriveType == InternalDriveType.Removable || _drive.InternalDriveType == InternalDriveType.HardDisk)
|
||||
^ (_type == MediaType.CompactFlash || _type == MediaType.SDCard || _type == MediaType.FlashDrive || _type == MediaType.HardDisk));
|
||||
|
||||
return parametersValid && floppyValid && removableDiskValid;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validate the current environment is ready for a dump
|
||||
/// </summary>
|
||||
/// <returns>Result instance with the outcome</returns>
|
||||
private ResultEventArgs IsValidForDump()
|
||||
{
|
||||
// Validate that everything is good
|
||||
if (_executionContext == null || !ParametersValid())
|
||||
return ResultEventArgs.Failure("Error! Current configuration is not supported!");
|
||||
|
||||
// Fix the output paths, just in case
|
||||
OutputPath = FrontendTool.NormalizeOutputPaths(OutputPath, false);
|
||||
|
||||
// Validate that the output path isn't on the dumping drive
|
||||
if (_drive?.Name != null && OutputPath.StartsWith(_drive.Name))
|
||||
return ResultEventArgs.Failure("Error! Cannot output to same drive that is being dumped!");
|
||||
|
||||
// Validate that the required program exists
|
||||
if (!File.Exists(_executionContext.ExecutablePath))
|
||||
return ResultEventArgs.Failure($"Error! {_executionContext.ExecutablePath} does not exist!");
|
||||
|
||||
// Validate that the dumping drive doesn't contain the executable
|
||||
string fullExecutablePath = Path.GetFullPath(_executionContext.ExecutablePath!);
|
||||
if (_drive?.Name != null && fullExecutablePath.StartsWith(_drive.Name))
|
||||
return ResultEventArgs.Failure("Error! Cannot dump same drive that executable resides on!");
|
||||
|
||||
// Validate that the current configuration is supported
|
||||
return GetSupportStatus();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Information Output
|
||||
|
||||
/// <summary>
|
||||
/// Write the data to the output folder
|
||||
/// </summary>
|
||||
/// <param name="outputDirectory">Output folder to write to</param>
|
||||
/// <param name="filenameSuffix">Optional suffix to append to the filename</param>
|
||||
/// <param name="lines">Preformatted list of lines to write out to the file</param>
|
||||
/// <returns>True on success, false on error</returns>
|
||||
private static (bool, string) WriteOutputData(string? outputDirectory, string? filenameSuffix, List<string>? lines)
|
||||
{
|
||||
// Check to see if the inputs are valid
|
||||
if (lines == null)
|
||||
return (false, "No formatted data found to write!");
|
||||
|
||||
// Now write out to a generic file
|
||||
try
|
||||
{
|
||||
// Get the file path
|
||||
var path = string.Empty;
|
||||
if (string.IsNullOrEmpty(outputDirectory) && string.IsNullOrEmpty(filenameSuffix))
|
||||
path = "!submissionInfo.txt";
|
||||
else if (string.IsNullOrEmpty(outputDirectory) && !string.IsNullOrEmpty(filenameSuffix))
|
||||
path = $"!submissionInfo_{filenameSuffix}.txt";
|
||||
else if (!string.IsNullOrEmpty(outputDirectory) && string.IsNullOrEmpty(filenameSuffix))
|
||||
path = Path.Combine(outputDirectory, "!submissionInfo.txt");
|
||||
else if (!string.IsNullOrEmpty(outputDirectory) && !string.IsNullOrEmpty(filenameSuffix))
|
||||
path = Path.Combine(outputDirectory, $"!submissionInfo_{filenameSuffix}.txt");
|
||||
|
||||
using var sw = new StreamWriter(File.Open(path, FileMode.Create, FileAccess.Write), Encoding.UTF8);
|
||||
foreach (string line in lines)
|
||||
{
|
||||
sw.WriteLine(line);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return (false, $"Writing could not complete: {ex}");
|
||||
}
|
||||
|
||||
return (true, "Writing complete!");
|
||||
}
|
||||
|
||||
// MOVE TO REDUMPLIB
|
||||
/// <summary>
|
||||
/// Write the data to the output folder
|
||||
/// </summary>
|
||||
/// <param name="outputDirectory">Output folder to write to</param>
|
||||
/// <param name="filenameSuffix">Optional suffix to append to the filename</param>
|
||||
/// <param name="info">SubmissionInfo object representing the JSON to write out to the file</param>
|
||||
/// <param name="includedArtifacts">True if artifacts were included, false otherwise</param>
|
||||
/// <returns>True on success, false on error</returns>
|
||||
private static bool WriteOutputData(string? outputDirectory, string? filenameSuffix, SubmissionInfo? info, bool includedArtifacts)
|
||||
{
|
||||
// Check to see if the input is valid
|
||||
if (info == null)
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
// Serialize the JSON and get it writable
|
||||
string json = JsonConvert.SerializeObject(info, Formatting.Indented);
|
||||
byte[] jsonBytes = Encoding.UTF8.GetBytes(json);
|
||||
|
||||
// If we included artifacts, write to a GZip-compressed file
|
||||
if (includedArtifacts)
|
||||
{
|
||||
var path = string.Empty;
|
||||
if (string.IsNullOrEmpty(outputDirectory) && string.IsNullOrEmpty(filenameSuffix))
|
||||
path = "!submissionInfo.json.gz";
|
||||
else if (string.IsNullOrEmpty(outputDirectory) && !string.IsNullOrEmpty(filenameSuffix))
|
||||
path = $"!submissionInfo_{filenameSuffix}.json.gz";
|
||||
else if (!string.IsNullOrEmpty(outputDirectory) && string.IsNullOrEmpty(filenameSuffix))
|
||||
path = Path.Combine(outputDirectory, "!submissionInfo.json.gz");
|
||||
else if (!string.IsNullOrEmpty(outputDirectory) && !string.IsNullOrEmpty(filenameSuffix))
|
||||
path = Path.Combine(outputDirectory, $"!submissionInfo_{filenameSuffix}.json.gz");
|
||||
|
||||
using var fs = File.Create(path);
|
||||
using var gs = new GZipStream(fs, CompressionMode.Compress);
|
||||
gs.Write(jsonBytes, 0, jsonBytes.Length);
|
||||
}
|
||||
|
||||
// Otherwise, write out to a normal JSON
|
||||
else
|
||||
{
|
||||
var path = string.Empty;
|
||||
if (string.IsNullOrEmpty(outputDirectory) && string.IsNullOrEmpty(filenameSuffix))
|
||||
path = "!submissionInfo.json";
|
||||
else if (string.IsNullOrEmpty(outputDirectory) && !string.IsNullOrEmpty(filenameSuffix))
|
||||
path = $"!submissionInfo_{filenameSuffix}.json";
|
||||
else if (!string.IsNullOrEmpty(outputDirectory) && string.IsNullOrEmpty(filenameSuffix))
|
||||
path = Path.Combine(outputDirectory, "!submissionInfo.json");
|
||||
else if (!string.IsNullOrEmpty(outputDirectory) && !string.IsNullOrEmpty(filenameSuffix))
|
||||
path = Path.Combine(outputDirectory, $"!submissionInfo_{filenameSuffix}.json");
|
||||
|
||||
using var fs = File.Create(path);
|
||||
fs.Write(jsonBytes, 0, jsonBytes.Length);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// We don't care what the error is right now
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// MOVE TO REDUMPLIB
|
||||
/// <summary>
|
||||
/// Write the protection data to the output folder
|
||||
/// </summary>
|
||||
/// <param name="outputDirectory">Output folder to write to</param>
|
||||
/// <param name="filenameSuffix">Optional suffix to append to the filename</param>
|
||||
/// <param name="info">SubmissionInfo object containing the protection information</param>
|
||||
/// <param name="hideDriveLetters">True if drive letters are to be removed from output, false otherwise</param>
|
||||
/// <returns>True on success, false on error</returns>
|
||||
private static bool WriteProtectionData(string? outputDirectory, string? filenameSuffix, SubmissionInfo? info, bool hideDriveLetters)
|
||||
{
|
||||
// Check to see if the inputs are valid
|
||||
if (info?.CopyProtection?.FullProtections == null || !info.CopyProtection.FullProtections.Any())
|
||||
return true;
|
||||
|
||||
// Now write out to a generic file
|
||||
try
|
||||
{
|
||||
var path = string.Empty;
|
||||
if (string.IsNullOrEmpty(outputDirectory) && string.IsNullOrEmpty(filenameSuffix))
|
||||
path = "!protectionInfo.txt";
|
||||
else if (string.IsNullOrEmpty(outputDirectory) && !string.IsNullOrEmpty(filenameSuffix))
|
||||
path = $"!protectionInfo{filenameSuffix}.txt";
|
||||
else if (!string.IsNullOrEmpty(outputDirectory) && string.IsNullOrEmpty(filenameSuffix))
|
||||
path = Path.Combine(outputDirectory, "!protectionInfo.txt");
|
||||
else if (!string.IsNullOrEmpty(outputDirectory) && !string.IsNullOrEmpty(filenameSuffix))
|
||||
path = Path.Combine(outputDirectory, $"!protectionInfo{filenameSuffix}.txt");
|
||||
|
||||
using var sw = new StreamWriter(File.Open(path, FileMode.Create, FileAccess.Write), Encoding.UTF8);
|
||||
|
||||
List<string> sortedKeys = [.. info.CopyProtection.FullProtections.Keys.OrderBy(k => k)];
|
||||
foreach (string key in sortedKeys)
|
||||
{
|
||||
string scanPath = key;
|
||||
if (hideDriveLetters)
|
||||
scanPath = Path.DirectorySeparatorChar + key.Substring((Path.GetPathRoot(key) ?? String.Empty).Length);
|
||||
|
||||
List<string>? scanResult = info.CopyProtection.FullProtections[key];
|
||||
|
||||
if (scanResult == null)
|
||||
sw.WriteLine($"{scanPath}: None");
|
||||
else
|
||||
sw.WriteLine($"{scanPath}: {string.Join(", ", [.. scanResult])}");
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// We don't care what the error is right now
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create an IRD and write it to the specified output directory with optional filename suffix
|
||||
/// </summary>
|
||||
/// <param name="outputDirectory">Output folder to write to</param>
|
||||
/// <param name="filenameSuffix">Optional suffix to append to the filename</param>
|
||||
/// <param name="outputFilename">Output filename to use as the base path</param>
|
||||
/// <returns>True on success, false on error</returns>
|
||||
private static async Task<(bool, string)> WriteIRD(string isoPath, string? discKeyString, string? discIDString, string? picString, long? layerbreak, string? crc32)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Output IRD file path
|
||||
string irdPath = Path.ChangeExtension(isoPath, ".ird");
|
||||
|
||||
// Parse disc key from submission info (Required)
|
||||
byte[]? discKey = ProcessingTool.ParseHexKey(discKeyString);
|
||||
if (discKey == null)
|
||||
return (false, "Failed to create IRD: No key provided");
|
||||
|
||||
// Parse Disc ID from submission info (Optional)
|
||||
byte[]? discID = ProcessingTool.ParseDiscID(discIDString);
|
||||
|
||||
// Parse PIC from submission info (Optional)
|
||||
byte[]? pic = ProcessingTool.ParsePIC(picString);
|
||||
|
||||
// Parse CRC32 strings into ISO hash for Unique ID field (Optional)
|
||||
uint? uid = ProcessingTool.ParseCRC32(crc32);
|
||||
|
||||
// Ensure layerbreak value is valid (Optional)
|
||||
layerbreak = ProcessingTool.ParseLayerbreak(layerbreak);
|
||||
|
||||
// Create Redump-style reproducible IRD
|
||||
#if NET40
|
||||
LibIRD.ReIRD ird = await Task.Factory.StartNew(() =>
|
||||
#else
|
||||
LibIRD.ReIRD ird = await Task.Run(() =>
|
||||
#endif
|
||||
new LibIRD.ReIRD(isoPath, discKey, layerbreak, uid));
|
||||
if (pic != null)
|
||||
ird.PIC = pic;
|
||||
if (discID != null && ird.DiscID[15] != 0x00)
|
||||
ird.DiscID = discID;
|
||||
|
||||
// Write IRD to file
|
||||
ird.Write(irdPath);
|
||||
|
||||
return (true, "IRD created!");
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// We don't care what the error is
|
||||
return (false, "Failed to create IRD");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
187
MPF.Frontend/EnumExtensions.cs
Normal file
187
MPF.Frontend/EnumExtensions.cs
Normal file
@@ -0,0 +1,187 @@
|
||||
using System;
|
||||
#if NET20 || NET35
|
||||
using System.Collections.Generic;
|
||||
#else
|
||||
using System.Collections.Concurrent;
|
||||
#endif
|
||||
using System.Reflection;
|
||||
using SabreTools.RedumpLib.Data;
|
||||
using RedumperReadMethod = MPF.ExecutionContexts.Redumper.ReadMethod;
|
||||
using RedumperSectorOrder = MPF.ExecutionContexts.Redumper.SectorOrder;
|
||||
|
||||
namespace MPF.Frontend
|
||||
{
|
||||
public static class EnumExtensions
|
||||
{
|
||||
#region Convert to Long Name
|
||||
|
||||
/// <summary>
|
||||
/// Long name method cache
|
||||
/// </summary>
|
||||
#if NET20 || NET35
|
||||
private static readonly Dictionary<Type, MethodInfo?> LongNameMethods = [];
|
||||
#else
|
||||
private static readonly ConcurrentDictionary<Type, MethodInfo?> LongNameMethods = [];
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Get the string representation of a generic enumerable value
|
||||
/// </summary>
|
||||
/// <param name="value">Enum value to convert</param>
|
||||
/// <returns>String representation of that value if possible, empty string on error</returns>
|
||||
public static string GetLongName(Enum value)
|
||||
{
|
||||
try
|
||||
{
|
||||
var sourceType = value.GetType();
|
||||
sourceType = Nullable.GetUnderlyingType(sourceType) ?? sourceType;
|
||||
|
||||
if (!LongNameMethods.TryGetValue(sourceType, out var method))
|
||||
{
|
||||
method = typeof(Extensions).GetMethod("LongName", [typeof(Nullable<>).MakeGenericType(sourceType)]);
|
||||
method ??= typeof(EnumExtensions).GetMethod("LongName", [typeof(Nullable<>).MakeGenericType(sourceType)]);
|
||||
|
||||
#if NET20 || NET35
|
||||
LongNameMethods[sourceType] = method;
|
||||
#else
|
||||
LongNameMethods.TryAdd(sourceType, method);
|
||||
#endif
|
||||
}
|
||||
|
||||
if (method != null)
|
||||
return method.Invoke(null, new[] { value }) as string ?? string.Empty;
|
||||
else
|
||||
return string.Empty;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Converter is not implemented for the given type
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the string representation of the InternalProgram enum values
|
||||
/// </summary>
|
||||
/// <param name="prog">InternalProgram value to convert</param>
|
||||
/// <returns>String representing the value, if possible</returns>
|
||||
public static string LongName(this InternalProgram? prog)
|
||||
{
|
||||
return (prog) switch
|
||||
{
|
||||
#region Dumping support
|
||||
|
||||
InternalProgram.Aaru => "Aaru",
|
||||
InternalProgram.DiscImageCreator => "DiscImageCreator",
|
||||
InternalProgram.Redumper => "Redumper",
|
||||
|
||||
#endregion
|
||||
|
||||
#region Verification support only
|
||||
|
||||
InternalProgram.CleanRip => "CleanRip",
|
||||
InternalProgram.PS3CFW => "PS3 CFW",
|
||||
InternalProgram.UmdImageCreator => "UmdImageCreator",
|
||||
InternalProgram.XboxBackupCreator => "XboxBackupCreator",
|
||||
|
||||
#endregion
|
||||
|
||||
InternalProgram.NONE => "Unknown",
|
||||
_ => "Unknown",
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the string representation of the RedumperReadMethod enum values
|
||||
/// </summary>
|
||||
/// <param name="method">RedumperReadMethod value to convert</param>
|
||||
/// <returns>String representing the value, if possible</returns>
|
||||
public static string LongName(this RedumperReadMethod? method)
|
||||
{
|
||||
return (method) switch
|
||||
{
|
||||
RedumperReadMethod.D8 => "D8",
|
||||
RedumperReadMethod.BE => "BE",
|
||||
RedumperReadMethod.BE_CDDA => "BE_CDDA",
|
||||
|
||||
RedumperReadMethod.NONE => "Default",
|
||||
_ => "Unknown",
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the string representation of the RedumperSectorOrder enum values
|
||||
/// </summary>
|
||||
/// <param name="order">RedumperSectorOrder value to convert</param>
|
||||
/// <returns>String representing the value, if possible</returns>
|
||||
public static string LongName(this RedumperSectorOrder? order)
|
||||
{
|
||||
return (order) switch
|
||||
{
|
||||
RedumperSectorOrder.DATA_C2_SUB => "DATA_C2_SUB",
|
||||
RedumperSectorOrder.DATA_SUB_C2 => "DATA_SUB_C2",
|
||||
RedumperSectorOrder.DATA_SUB => "DATA_SUB",
|
||||
RedumperSectorOrder.DATA_C2 => "DATA_C2",
|
||||
|
||||
RedumperSectorOrder.NONE => "Default",
|
||||
_ => "Unknown",
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Convert from String
|
||||
|
||||
/// <summary>
|
||||
/// Get the RedumperReadMethod enum value for a given string
|
||||
/// </summary>
|
||||
/// <param name="method">String value to convert</param>
|
||||
/// <returns>RedumperReadMethod represented by the string, if possible</returns>
|
||||
public static RedumperReadMethod ToRedumperReadMethod(this string? method)
|
||||
{
|
||||
return (method?.ToLowerInvariant()) switch
|
||||
{
|
||||
"d8" => RedumperReadMethod.D8,
|
||||
"be" => RedumperReadMethod.BE,
|
||||
"be_cdda"
|
||||
or "be cdda"
|
||||
or "be-cdda"
|
||||
or "becdda" => RedumperReadMethod.BE_CDDA,
|
||||
|
||||
_ => RedumperReadMethod.NONE,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the RedumperSectorOrder enum value for a given string
|
||||
/// </summary>
|
||||
/// <param name="order">String value to convert</param>
|
||||
/// <returns>RedumperSectorOrder represented by the string, if possible</returns>
|
||||
public static RedumperSectorOrder ToRedumperSectorOrder(this string? order)
|
||||
{
|
||||
return (order?.ToLowerInvariant()) switch
|
||||
{
|
||||
"data_c2_sub"
|
||||
or "data c2 sub"
|
||||
or "data-c2-sub"
|
||||
or "datac2sub" => RedumperSectorOrder.DATA_C2_SUB,
|
||||
"data_sub_c2"
|
||||
or "data sub c2"
|
||||
or "data-sub-c2"
|
||||
or "datasubc2" => RedumperSectorOrder.DATA_SUB_C2,
|
||||
"data_sub"
|
||||
or "data sub"
|
||||
or "data-sub"
|
||||
or "datasub" => RedumperSectorOrder.DATA_SUB,
|
||||
"data_c2"
|
||||
or "data c2"
|
||||
or "data-c2"
|
||||
or "datac2" => RedumperSectorOrder.DATA_C2,
|
||||
|
||||
_ => RedumperSectorOrder.NONE,
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
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,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
73
MPF.Frontend/MPF.Frontend.csproj
Normal file
73
MPF.Frontend/MPF.Frontend.csproj
Normal file
@@ -0,0 +1,73 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<!-- Assembly Properties -->
|
||||
<TargetFrameworks>net20;net35;net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0</TargetFrameworks>
|
||||
<CheckEolTargetFramework>false</CheckEolTargetFramework>
|
||||
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<VersionPrefix>3.2.1</VersionPrefix>
|
||||
|
||||
<!-- Package Properties -->
|
||||
<Authors>Matt Nadareski;ReignStumble;Jakz</Authors>
|
||||
<Description>Common code for all MPF frontend implementations</Description>
|
||||
<Copyright>Copyright (c) Matt Nadareski 2019-2024</Copyright>
|
||||
<PackageProjectUrl>https://github.com/SabreTools/</PackageProjectUrl>
|
||||
<RepositoryUrl>https://github.com/SabreTools/MPF</RepositoryUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<InternalsVisibleTo Include="MPF.Test" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Support All Frameworks -->
|
||||
<PropertyGroup Condition="$(TargetFramework.StartsWith(`net2`)) OR $(TargetFramework.StartsWith(`net3`)) OR $(TargetFramework.StartsWith(`net4`))">
|
||||
<RuntimeIdentifiers>win-x86;win-x64</RuntimeIdentifiers>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="$(TargetFramework.StartsWith(`netcoreapp`)) OR $(TargetFramework.StartsWith(`net5`))">
|
||||
<RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64</RuntimeIdentifiers>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="$(TargetFramework.StartsWith(`net6`)) OR $(TargetFramework.StartsWith(`net7`)) OR $(TargetFramework.StartsWith(`net8`))">
|
||||
<RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64;osx-arm64</RuntimeIdentifiers>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="$(RuntimeIdentifier.StartsWith(`osx-arm`))">
|
||||
<TargetFrameworks>net6.0;net7.0;net8.0</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MPF.ExecutionContexts\MPF.ExecutionContexts.csproj" />
|
||||
<ProjectReference Include="..\MPF.Processors\MPF.Processors.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Support for old .NET versions -->
|
||||
<ItemGroup Condition="$(TargetFramework.StartsWith(`net2`)) OR $(TargetFramework.StartsWith(`net3`)) OR $(TargetFramework.StartsWith(`net40`))">
|
||||
<PackageReference Include="MinAsyncBridge" Version="0.12.4" />
|
||||
<PackageReference Include="MinTasksExtensionsBridge" Version="0.3.4" />
|
||||
<PackageReference Include="MinThreadingBridge" Version="0.11.4" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="$(TargetFramework.StartsWith(`net4`)) AND !$(TargetFramework.StartsWith(`net40`))">
|
||||
<PackageReference Include="IndexRange" Version="1.0.3" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="$(TargetFramework.StartsWith(`net452`))">
|
||||
<PackageReference Include="Microsoft.Net.Http" Version="2.2.29" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="!$(TargetFramework.StartsWith(`net2`)) AND !$(TargetFramework.StartsWith(`net3`)) AND !$(TargetFramework.StartsWith(`net40`)) AND !$(TargetFramework.StartsWith(`net452`))">
|
||||
<PackageReference Include="Microsoft.Management.Infrastructure" Version="3.0.0" />
|
||||
<PackageReference Include="System.Net.Http" Version="4.3.4" />
|
||||
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="6.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BinaryObjectScanner" PrivateAssets="build; analyzers" ExcludeAssets="contentFiles" Version="3.1.13" GeneratePathProperty="true">
|
||||
<IncludeAssets>runtime; compile; build; native; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="LibIRD" Version="0.9.1" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="SabreTools.RedumpLib" Version="1.4.1" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
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
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
649
MPF.Frontend/Tools/FrontendTool.cs
Normal file
649
MPF.Frontend/Tools/FrontendTool.cs
Normal file
@@ -0,0 +1,649 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using SabreTools.RedumpLib.Data;
|
||||
|
||||
namespace MPF.Frontend.Tools
|
||||
{
|
||||
public static class FrontendTool
|
||||
{
|
||||
#region Information Extraction
|
||||
|
||||
/// <summary>
|
||||
/// Get the default speed for a given media type from the supplied options
|
||||
/// </summary>
|
||||
public static int GetDefaultSpeedForMediaType(MediaType? mediaType, Options options)
|
||||
{
|
||||
return mediaType switch
|
||||
{
|
||||
// CD dump speed
|
||||
MediaType.CDROM => options.PreferredDumpSpeedCD,
|
||||
MediaType.GDROM => options.PreferredDumpSpeedCD,
|
||||
|
||||
// DVD dump speed
|
||||
MediaType.DVD => options.PreferredDumpSpeedDVD,
|
||||
MediaType.NintendoGameCubeGameDisc => options.PreferredDumpSpeedDVD,
|
||||
MediaType.NintendoWiiOpticalDisc => options.PreferredDumpSpeedDVD,
|
||||
|
||||
// HD-DVD dump speed
|
||||
MediaType.HDDVD => options.PreferredDumpSpeedHDDVD,
|
||||
|
||||
// BD dump speed
|
||||
MediaType.BluRay => options.PreferredDumpSpeedBD,
|
||||
|
||||
// Default
|
||||
_ => options.PreferredDumpSpeedCD,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the current system from the drive volume label
|
||||
/// </summary>
|
||||
/// <returns>The system based on volume label, null if none detected</returns>
|
||||
public static RedumpSystem? GetRedumpSystemFromVolumeLabel(string? volumeLabel)
|
||||
{
|
||||
// If the volume label is empty, we can't do anything
|
||||
if (string.IsNullOrEmpty(volumeLabel))
|
||||
return null;
|
||||
|
||||
// Audio CD
|
||||
if (volumeLabel!.Equals("Audio CD", StringComparison.OrdinalIgnoreCase))
|
||||
return RedumpSystem.AudioCD;
|
||||
|
||||
// Microsoft Xbox
|
||||
if (volumeLabel.Equals("SEP13011042", StringComparison.OrdinalIgnoreCase))
|
||||
return RedumpSystem.MicrosoftXbox;
|
||||
else if (volumeLabel.Equals("SEP13011042072", StringComparison.OrdinalIgnoreCase))
|
||||
return RedumpSystem.MicrosoftXbox;
|
||||
|
||||
// Microsoft Xbox 360
|
||||
if (volumeLabel.Equals("XBOX360", StringComparison.OrdinalIgnoreCase))
|
||||
return RedumpSystem.MicrosoftXbox360;
|
||||
else if (volumeLabel.Equals("XGD2DVD_NTSC", StringComparison.OrdinalIgnoreCase))
|
||||
return RedumpSystem.MicrosoftXbox360;
|
||||
|
||||
// Microsoft Xbox 360 - Too overly broad even if a lot of discs use this
|
||||
//if (volumeLabel.Equals("CD_ROM", StringComparison.OrdinalIgnoreCase))
|
||||
// return RedumpSystem.MicrosoftXbox360; // Also for Xbox One?
|
||||
//if (volumeLabel.Equals("DVD_ROM", StringComparison.OrdinalIgnoreCase))
|
||||
// return RedumpSystem.MicrosoftXbox360;
|
||||
|
||||
// Sega Mega-CD / Sega-CD
|
||||
if (volumeLabel.Equals("Sega_CD", StringComparison.OrdinalIgnoreCase))
|
||||
return RedumpSystem.SegaMegaCDSegaCD;
|
||||
|
||||
// Sony PlayStation 3
|
||||
if (volumeLabel.Equals("PS3VOLUME", StringComparison.OrdinalIgnoreCase))
|
||||
return RedumpSystem.SonyPlayStation3;
|
||||
|
||||
// Sony PlayStation 4
|
||||
if (volumeLabel.Equals("PS4VOLUME", StringComparison.OrdinalIgnoreCase))
|
||||
return RedumpSystem.SonyPlayStation4;
|
||||
|
||||
// Sony PlayStation 5
|
||||
if (volumeLabel.Equals("PS5VOLUME", StringComparison.OrdinalIgnoreCase))
|
||||
return RedumpSystem.SonyPlayStation5;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Normalization
|
||||
|
||||
/// <summary>
|
||||
/// Adjust a disc title so that it will be processed correctly by Redump
|
||||
/// </summary>
|
||||
/// <param name="title">Existing title to potentially reformat</param>
|
||||
/// <param name="languages">Array of languages to use for assuming articles</param>
|
||||
/// <returns>The reformatted title</returns>
|
||||
public static string? NormalizeDiscTitle(string? title, Language?[]? languages)
|
||||
{
|
||||
// If we have no set languages, then assume English
|
||||
if (languages == null || languages.Length == 0)
|
||||
languages = [Language.English];
|
||||
|
||||
// Loop through all of the given languages
|
||||
foreach (var language in languages)
|
||||
{
|
||||
// If the new title is different, assume it was normalized and return it
|
||||
string? newTitle = NormalizeDiscTitle(title, language);
|
||||
if (newTitle != title)
|
||||
return newTitle;
|
||||
}
|
||||
|
||||
// If we didn't already try English, try it now
|
||||
if (!languages.Contains(Language.English))
|
||||
return NormalizeDiscTitle(title, Language.English);
|
||||
|
||||
// If all fails, then the title didn't need normalization
|
||||
return title;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adjust a disc title so that it will be processed correctly by Redump
|
||||
/// </summary>
|
||||
/// <param name="title">Existing title to potentially reformat</param>
|
||||
/// <param name="language">Language to use for assuming articles</param>
|
||||
/// <returns>The reformatted title</returns>
|
||||
/// <remarks>
|
||||
/// If the language of the title is unknown or if it's multilingual,
|
||||
/// pass in Language.English for standardized coverage.
|
||||
/// </remarks>
|
||||
public static string? NormalizeDiscTitle(string? title, Language? language)
|
||||
{
|
||||
// If we have an invalid title, just return it as-is
|
||||
if (string.IsNullOrEmpty(title))
|
||||
return title;
|
||||
|
||||
// If we have an invalid language, assume Language.English
|
||||
if (language == null)
|
||||
language = Language.English;
|
||||
|
||||
// Get the title split into parts
|
||||
string[] splitTitle = title!.Split(' ').Where(s => !string.IsNullOrEmpty(s)).ToArray();
|
||||
|
||||
// If we only have one part, we can't do anything
|
||||
if (splitTitle.Length <= 1)
|
||||
return title;
|
||||
|
||||
// Determine if we have a definite or indefinite article as the first item
|
||||
string firstItem = splitTitle[0];
|
||||
switch (firstItem.ToLowerInvariant())
|
||||
{
|
||||
// Latin script articles
|
||||
case "'n"
|
||||
when language is Language.Manx:
|
||||
case "a"
|
||||
when language is Language.English
|
||||
|| language is Language.Hungarian
|
||||
|| language is Language.Portuguese
|
||||
|| language is Language.Scots:
|
||||
case "a'"
|
||||
when language is Language.English
|
||||
|| language is Language.Hungarian
|
||||
|| language is Language.Irish
|
||||
|| language is Language.Gaelic: // Scottish Gaelic
|
||||
case "al"
|
||||
when language is Language.Breton:
|
||||
case "am"
|
||||
when language is Language.Gaelic: // Scottish Gaelic
|
||||
case "an"
|
||||
when language is Language.Breton
|
||||
|| language is Language.Cornish
|
||||
|| language is Language.English
|
||||
|| language is Language.Irish
|
||||
|| language is Language.Gaelic: // Scottish Gaelic
|
||||
case "anek"
|
||||
when language is Language.Nepali:
|
||||
case "ar"
|
||||
when language is Language.Breton:
|
||||
case "az"
|
||||
when language is Language.Hungarian:
|
||||
case "ān"
|
||||
when language is Language.Persian:
|
||||
case "as"
|
||||
when language is Language.Portuguese:
|
||||
case "d'"
|
||||
when language is Language.Luxembourgish:
|
||||
case "das"
|
||||
when language is Language.German:
|
||||
case "dat"
|
||||
when language is Language.Luxembourgish:
|
||||
case "de"
|
||||
when language is Language.Dutch:
|
||||
case "déi"
|
||||
when language is Language.Luxembourgish:
|
||||
case "dem"
|
||||
when language is Language.German
|
||||
|| language is Language.Luxembourgish:
|
||||
case "den"
|
||||
when language is Language.Dutch
|
||||
|| language is Language.German
|
||||
|| language is Language.Luxembourgish:
|
||||
case "der"
|
||||
when language is Language.Dutch
|
||||
|| language is Language.German
|
||||
|| language is Language.Luxembourgish:
|
||||
case "des"
|
||||
when language is Language.Dutch
|
||||
|| language is Language.French
|
||||
|| language is Language.German:
|
||||
case "die"
|
||||
when language is Language.Afrikaans
|
||||
|| language is Language.German:
|
||||
case "du"
|
||||
when language is Language.French:
|
||||
case "e"
|
||||
when language is Language.Papiamento:
|
||||
case "een"
|
||||
when language is Language.Dutch:
|
||||
case "egy"
|
||||
when language is Language.Hungarian:
|
||||
case "ei"
|
||||
when language is Language.Norwegian:
|
||||
case "ein"
|
||||
when language is Language.German
|
||||
|| language is Language.Norwegian:
|
||||
case "eine"
|
||||
when language is Language.German:
|
||||
case "einem"
|
||||
when language is Language.German:
|
||||
case "einen"
|
||||
when language is Language.German:
|
||||
case "einer"
|
||||
when language is Language.German:
|
||||
case "eines"
|
||||
when language is Language.German:
|
||||
case "eit"
|
||||
when language is Language.Norwegian:
|
||||
case "ek"
|
||||
when language is Language.Nepali:
|
||||
case "el"
|
||||
when language is Language.Arabic
|
||||
|| language is Language.Catalan
|
||||
|| language is Language.Spanish:
|
||||
case "els"
|
||||
when language is Language.Catalan:
|
||||
case "en"
|
||||
when language is Language.Danish
|
||||
|| language is Language.Luxembourgish
|
||||
|| language is Language.Norwegian
|
||||
|| language is Language.Swedish:
|
||||
case "eng"
|
||||
when language is Language.Luxembourgish:
|
||||
case "engem"
|
||||
when language is Language.Luxembourgish:
|
||||
case "enger"
|
||||
when language is Language.Luxembourgish:
|
||||
case "es"
|
||||
when language is Language.Catalan:
|
||||
case "et"
|
||||
when language is Language.Danish
|
||||
|| language is Language.Norwegian:
|
||||
case "ett"
|
||||
when language is Language.Swedish:
|
||||
case "euta"
|
||||
when language is Language.Nepali:
|
||||
case "euti"
|
||||
when language is Language.Nepali:
|
||||
case "gli"
|
||||
when language is Language.Italian:
|
||||
case "he"
|
||||
when language is Language.Hawaiian
|
||||
|| language is Language.Maori:
|
||||
case "het"
|
||||
when language is Language.Dutch:
|
||||
case "i"
|
||||
when language is Language.Italian
|
||||
|| language is Language.Khasi:
|
||||
case "il"
|
||||
when language is Language.Italian:
|
||||
case "in"
|
||||
when language is Language.Persian:
|
||||
case "ka"
|
||||
when language is Language.Hawaiian
|
||||
|| language is Language.Khasi:
|
||||
case "ke"
|
||||
when language is Language.Hawaiian:
|
||||
case "ki"
|
||||
when language is Language.Khasi:
|
||||
case "kunai"
|
||||
when language is Language.Nepali:
|
||||
case "l'"
|
||||
when language is Language.Catalan
|
||||
|| language is Language.French
|
||||
|| language is Language.Italian:
|
||||
case "la"
|
||||
when language is Language.Catalan
|
||||
|| language is Language.Esperanto
|
||||
|| language is Language.French
|
||||
|| language is Language.Italian
|
||||
|| language is Language.Spanish:
|
||||
case "las"
|
||||
when language is Language.Spanish:
|
||||
case "le"
|
||||
when language is Language.French
|
||||
|| language is Language.Interlingua
|
||||
|| language is Language.Italian:
|
||||
case "les"
|
||||
when language is Language.Catalan
|
||||
|| language is Language.French:
|
||||
case "lo"
|
||||
when language is Language.Catalan
|
||||
|| language is Language.Italian
|
||||
|| language is Language.Spanish:
|
||||
case "los"
|
||||
when language is Language.Catalan
|
||||
|| language is Language.Spanish:
|
||||
case "na"
|
||||
when language is Language.Irish
|
||||
|| language is Language.Gaelic: // Scottish Gaelic
|
||||
case "nam"
|
||||
when language is Language.Gaelic: // Scottish Gaelic
|
||||
case "nan"
|
||||
when language is Language.Gaelic: // Scottish Gaelic
|
||||
case "nā"
|
||||
when language is Language.Hawaiian:
|
||||
case "ngā"
|
||||
when language is Language.Maori:
|
||||
case "niște"
|
||||
when language is Language.Romanian:
|
||||
case "ny"
|
||||
when language is Language.Manx:
|
||||
case "o"
|
||||
when language is Language.Portuguese
|
||||
|| language is Language.Romanian:
|
||||
case "os"
|
||||
when language is Language.Portuguese:
|
||||
case "sa"
|
||||
when language is Language.Catalan:
|
||||
case "sang"
|
||||
when language is Language.Malay:
|
||||
case "se"
|
||||
when language is Language.Finnish:
|
||||
case "ses"
|
||||
when language is Language.Catalan:
|
||||
case "si"
|
||||
when language is Language.Malay:
|
||||
case "te"
|
||||
when language is Language.Maori:
|
||||
case "the"
|
||||
when language is Language.English
|
||||
|| language is Language.Scots:
|
||||
case "u"
|
||||
when language is Language.Khasi:
|
||||
case "ul"
|
||||
when language is Language.Breton:
|
||||
case "um"
|
||||
when language is Language.Portuguese:
|
||||
case "uma"
|
||||
when language is Language.Portuguese:
|
||||
case "umas"
|
||||
when language is Language.Portuguese:
|
||||
case "un"
|
||||
when language is Language.Breton
|
||||
|| language is Language.Catalan
|
||||
|| language is Language.French
|
||||
|| language is Language.Interlingua
|
||||
|| language is Language.Italian
|
||||
|| language is Language.Papiamento
|
||||
|| language is Language.Romanian
|
||||
|| language is Language.Spanish:
|
||||
case "un'"
|
||||
when language is Language.Italian:
|
||||
case "una"
|
||||
when language is Language.Catalan
|
||||
|| language is Language.Italian:
|
||||
case "unas"
|
||||
when language is Language.Spanish:
|
||||
case "une"
|
||||
when language is Language.French:
|
||||
case "uno"
|
||||
when language is Language.Italian:
|
||||
case "unos"
|
||||
when language is Language.Spanish:
|
||||
case "uns"
|
||||
when language is Language.Catalan
|
||||
|| language is Language.Portuguese:
|
||||
case "unei"
|
||||
when language is Language.Romanian:
|
||||
case "unes"
|
||||
when language is Language.Catalan:
|
||||
case "unor"
|
||||
when language is Language.Romanian:
|
||||
case "unui"
|
||||
when language is Language.Romanian:
|
||||
case "ur"
|
||||
when language is Language.Breton:
|
||||
case "y"
|
||||
when language is Language.Manx
|
||||
|| language is Language.Welsh:
|
||||
case "ye"
|
||||
when language is Language.Persian:
|
||||
case "yek"
|
||||
when language is Language.Persian:
|
||||
case "yn"
|
||||
when language is Language.Manx:
|
||||
case "yr"
|
||||
when language is Language.Welsh:
|
||||
|
||||
// Non-latin script articles
|
||||
case "ο"
|
||||
when language is Language.Greek:
|
||||
case "η"
|
||||
when language is Language.Greek:
|
||||
case "το"
|
||||
when language is Language.Greek:
|
||||
case "οι"
|
||||
when language is Language.Greek:
|
||||
case "τα"
|
||||
when language is Language.Greek:
|
||||
case "ένας"
|
||||
when language is Language.Greek:
|
||||
case "μια"
|
||||
when language is Language.Greek:
|
||||
case "ένα"
|
||||
when language is Language.Greek:
|
||||
case "еден"
|
||||
when language is Language.Macedonian:
|
||||
case "една"
|
||||
when language is Language.Macedonian:
|
||||
case "едно"
|
||||
when language is Language.Macedonian:
|
||||
case "едни"
|
||||
when language is Language.Macedonian:
|
||||
case "एउटा"
|
||||
when language is Language.Nepali:
|
||||
case "एउटी"
|
||||
when language is Language.Nepali:
|
||||
case "एक"
|
||||
when language is Language.Nepali:
|
||||
case "अनेक"
|
||||
when language is Language.Nepali:
|
||||
case "कुनै"
|
||||
when language is Language.Nepali:
|
||||
case "דער"
|
||||
when language is Language.Yiddish:
|
||||
case "די"
|
||||
when language is Language.Yiddish:
|
||||
case "דאָס"
|
||||
when language is Language.Yiddish:
|
||||
case "דעם"
|
||||
when language is Language.Yiddish:
|
||||
case "אַ"
|
||||
when language is Language.Yiddish:
|
||||
case "אַן"
|
||||
when language is Language.Yiddish:
|
||||
|
||||
break;
|
||||
|
||||
// Otherwise, just return it as-is
|
||||
default:
|
||||
return title;
|
||||
}
|
||||
|
||||
// Insert the first item if we have a `:` or `-`
|
||||
bool itemInserted = false;
|
||||
var newTitleBuilder = new StringBuilder();
|
||||
for (int i = 1; i < splitTitle.Length; i++)
|
||||
{
|
||||
string segment = splitTitle[i];
|
||||
if (!itemInserted && segment == ":")
|
||||
{
|
||||
itemInserted = true;
|
||||
newTitleBuilder.Append($", {firstItem} :");
|
||||
}
|
||||
else if (!itemInserted && segment == "-")
|
||||
{
|
||||
itemInserted = true;
|
||||
newTitleBuilder.Append($", {firstItem} -");
|
||||
}
|
||||
else if (!itemInserted && segment.EndsWith(":"))
|
||||
{
|
||||
itemInserted = true;
|
||||
newTitleBuilder.Append($" {segment.Substring(0, segment.Length - 1)}, {firstItem}:");
|
||||
}
|
||||
else if (!itemInserted && segment.EndsWith("-"))
|
||||
{
|
||||
itemInserted = true;
|
||||
newTitleBuilder.Append($" {segment.Substring(0, segment.Length - 1)}, {firstItem}-");
|
||||
}
|
||||
else
|
||||
{
|
||||
newTitleBuilder.Append($" {segment}");
|
||||
}
|
||||
}
|
||||
|
||||
// If we didn't insert the item yet, add it to the end
|
||||
string newTitle = newTitleBuilder.ToString().Trim();
|
||||
if (!itemInserted)
|
||||
newTitle = $"{newTitle}, {firstItem}";
|
||||
|
||||
return newTitle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Normalize a split set of paths
|
||||
/// </summary>
|
||||
/// <param name="path">Path value to normalize</param>
|
||||
public static string NormalizeOutputPaths(string? path, bool getFullPath)
|
||||
{
|
||||
// The easy way
|
||||
try
|
||||
{
|
||||
// If we have an invalid path
|
||||
if (string.IsNullOrEmpty(path))
|
||||
return string.Empty;
|
||||
|
||||
// Remove quotes and angle brackets from path
|
||||
path = path!.Replace("\"", string.Empty);
|
||||
path = path!.Replace("<", string.Empty);
|
||||
path = path!.Replace(">", string.Empty);
|
||||
|
||||
// Try getting the combined path and returning that directly
|
||||
string fullPath = getFullPath ? Path.GetFullPath(path) : path;
|
||||
var fullDirectory = Path.GetDirectoryName(fullPath);
|
||||
string fullFile = Path.GetFileName(fullPath);
|
||||
|
||||
// Remove invalid path characters
|
||||
if (fullDirectory != null)
|
||||
{
|
||||
foreach (char c in Path.GetInvalidPathChars())
|
||||
fullDirectory = fullDirectory.Replace(c, '_');
|
||||
}
|
||||
|
||||
// Remove invalid filename characters
|
||||
foreach (char c in Path.GetInvalidFileNameChars())
|
||||
fullFile = fullFile.Replace(c, '_');
|
||||
|
||||
if (string.IsNullOrEmpty(fullDirectory))
|
||||
return fullFile;
|
||||
else
|
||||
return Path.Combine(fullDirectory, fullFile);
|
||||
}
|
||||
catch { }
|
||||
|
||||
return path ?? string.Empty;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Versioning
|
||||
|
||||
/// <summary>
|
||||
/// Check for a new MPF version
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// Bool representing if the values are different.
|
||||
/// String representing the message to display the the user.
|
||||
/// String representing the new release URL.
|
||||
/// </returns>
|
||||
public static (bool different, string message, string? url) CheckForNewVersion()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Get current assembly version
|
||||
var assemblyVersion = Assembly.GetEntryAssembly()?.GetName()?.Version;
|
||||
if (assemblyVersion == null)
|
||||
return (false, "Assembly version could not be determined", null);
|
||||
|
||||
string version = $"{assemblyVersion.Major}.{assemblyVersion.Minor}.{assemblyVersion.Build}";
|
||||
|
||||
// Get the latest tag from GitHub
|
||||
var (tag, url) = GetRemoteVersionAndUrl();
|
||||
bool different = version != tag && tag != null;
|
||||
|
||||
string message = $"Local version: {version}"
|
||||
+ $"{Environment.NewLine}Remote version: {tag}"
|
||||
+ (different
|
||||
? $"{Environment.NewLine}The update URL has been added copied to your clipboard"
|
||||
: $"{Environment.NewLine}You have the newest version!");
|
||||
|
||||
return (different, message, url);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return (false, ex.ToString(), null);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the current informational version formatted as a string
|
||||
/// </summary>
|
||||
public static string? GetCurrentVersion()
|
||||
{
|
||||
try
|
||||
{
|
||||
var assembly = Assembly.GetEntryAssembly();
|
||||
if (assembly == null)
|
||||
return null;
|
||||
|
||||
var assemblyVersion = Attribute.GetCustomAttribute(assembly, typeof(AssemblyInformationalVersionAttribute)) as AssemblyInformationalVersionAttribute;
|
||||
return assemblyVersion?.InformationalVersion;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return ex.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the latest version of MPF from GitHub and the release URL
|
||||
/// </summary>
|
||||
private static (string? tag, string? url) GetRemoteVersionAndUrl()
|
||||
{
|
||||
#if NET20 || NET35 || NET40
|
||||
// Not supported in .NET Frameworks 2.0, 3.5, or 4.0
|
||||
return (null, null);
|
||||
#else
|
||||
using var hc = new System.Net.Http.HttpClient();
|
||||
#if NET452
|
||||
System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Tls12;
|
||||
#endif
|
||||
|
||||
// TODO: Figure out a better way than having this hardcoded...
|
||||
string url = "https://api.github.com/repos/SabreTools/MPF/releases/latest";
|
||||
var message = new System.Net.Http.HttpRequestMessage(System.Net.Http.HttpMethod.Get, url);
|
||||
message.Headers.Add("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:64.0) Gecko/20100101 Firefox/64.0");
|
||||
var latestReleaseJsonString = hc.SendAsync(message)?.ConfigureAwait(false).GetAwaiter().GetResult()
|
||||
.Content?.ReadAsStringAsync().ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
if (latestReleaseJsonString == null)
|
||||
return (null, null);
|
||||
|
||||
var latestReleaseJson = Newtonsoft.Json.Linq.JObject.Parse(latestReleaseJsonString);
|
||||
if (latestReleaseJson == null)
|
||||
return (null, null);
|
||||
|
||||
var latestTag = latestReleaseJson["tag_name"]?.ToString();
|
||||
var releaseUrl = latestReleaseJson["html_url"]?.ToString();
|
||||
|
||||
return (latestTag, releaseUrl);
|
||||
#endif
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
314
MPF.Frontend/Tools/OptionsLoader.cs
Normal file
314
MPF.Frontend/Tools/OptionsLoader.cs
Normal file
@@ -0,0 +1,314 @@
|
||||
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
|
||||
{
|
||||
private const string ConfigurationPath = "config.json";
|
||||
|
||||
#region Arguments
|
||||
|
||||
/// <summary>
|
||||
/// Process any standalone arguments for the program
|
||||
/// </summary>
|
||||
/// <returns>True if one of the arguments was processed, false otherwise</returns>
|
||||
public static bool? ProcessStandaloneArguments(string[] args)
|
||||
{
|
||||
// Help options
|
||||
if (args.Length == 0 || args[0] == "-h" || args[0] == "-?")
|
||||
return false;
|
||||
|
||||
// List options
|
||||
if (args[0] == "-lc" || args[0] == "--listcodes")
|
||||
{
|
||||
Console.WriteLine("Supported Site Codes:");
|
||||
foreach (string siteCode in Extensions.ListSiteCodes())
|
||||
{
|
||||
Console.WriteLine(siteCode);
|
||||
}
|
||||
Console.ReadLine();
|
||||
return true;
|
||||
}
|
||||
else if (args[0] == "-lm" || args[0] == "--listmedia")
|
||||
{
|
||||
Console.WriteLine("Supported Media Types:");
|
||||
foreach (string mediaType in Extensions.ListMediaTypes())
|
||||
{
|
||||
Console.WriteLine(mediaType);
|
||||
}
|
||||
Console.ReadLine();
|
||||
return true;
|
||||
}
|
||||
else if (args[0] == "-lp" || args[0] == "--listprograms")
|
||||
{
|
||||
Console.WriteLine("Supported Programs:");
|
||||
foreach (string program in ListPrograms())
|
||||
{
|
||||
Console.WriteLine(program);
|
||||
}
|
||||
Console.ReadLine();
|
||||
return true;
|
||||
}
|
||||
else if (args[0] == "-ls" || args[0] == "--listsystems")
|
||||
{
|
||||
Console.WriteLine("Supported Systems:");
|
||||
foreach (string system in Extensions.ListSystems())
|
||||
{
|
||||
Console.WriteLine(system);
|
||||
}
|
||||
Console.ReadLine();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process common arguments for all functionality
|
||||
/// </summary>
|
||||
/// <returns>True if all arguments pass, false otherwise</returns>
|
||||
public static (bool, MediaType, RedumpSystem?, string?) ProcessCommonArguments(string[] args)
|
||||
{
|
||||
// All other use requires at least 3 arguments
|
||||
if (args.Length < 3)
|
||||
return (false, MediaType.NONE, null, "Invalid number of arguments");
|
||||
|
||||
// Check the MediaType
|
||||
var mediaType = ToMediaType(args[0].Trim('"'));
|
||||
if (mediaType == MediaType.NONE)
|
||||
return (false, MediaType.NONE, null, $"{args[0]} is not a recognized media type");
|
||||
|
||||
// Check the RedumpSystem
|
||||
var knownSystem = Extensions.ToRedumpSystem(args[1].Trim('"'));
|
||||
if (knownSystem == null)
|
||||
return (false, MediaType.NONE, null, $"{args[1]} is not a recognized system");
|
||||
|
||||
return (true, mediaType, knownSystem, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
}
|
||||
}
|
||||
406
MPF.Frontend/Tools/ProtectionTool.cs
Normal file
406
MPF.Frontend/Tools/ProtectionTool.cs
Normal file
@@ -0,0 +1,406 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using BinaryObjectScanner;
|
||||
|
||||
#pragma warning disable SYSLIB1045 // Convert to 'GeneratedRegexAttribute'.
|
||||
|
||||
namespace MPF.Frontend.Tools
|
||||
{
|
||||
public static class ProtectionTool
|
||||
{
|
||||
/// <summary>
|
||||
/// Get the current detected copy protection(s), if possible
|
||||
/// </summary>
|
||||
/// <param name="drive">Drive object representing the current drive</param>
|
||||
/// <param name="options">Options object that determines what to scan</param>
|
||||
/// <param name="progress">Optional progress callback</param>
|
||||
/// <returns>Detected copy protection(s) if possible, null on error</returns>
|
||||
public static async Task<(string?, Dictionary<string, List<string>>?)> GetCopyProtection(Drive? drive,
|
||||
Frontend.Options options,
|
||||
IProgress<ProtectionProgress>? progress = null)
|
||||
{
|
||||
if (options.ScanForProtection && drive?.Name != null)
|
||||
{
|
||||
(var protection, _) = await RunProtectionScanOnPath(drive.Name, options, progress);
|
||||
return (FormatProtections(protection), protection);
|
||||
}
|
||||
|
||||
return ("(CHECK WITH PROTECTIONID)", null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Run protection scan on a given path
|
||||
/// </summary>
|
||||
/// <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<(Dictionary<string, List<string>>?, string?)> RunProtectionScanOnPath(string path,
|
||||
Frontend.Options options,
|
||||
IProgress<ProtectionProgress>? progress = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
#if NET40
|
||||
var found = await Task.Factory.StartNew(() =>
|
||||
{
|
||||
var scanner = new 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);
|
||||
});
|
||||
#else
|
||||
var found = await Task.Run(() =>
|
||||
{
|
||||
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);
|
||||
});
|
||||
#endif
|
||||
|
||||
// If nothing was returned, return
|
||||
#if NET20 || NET35
|
||||
if (found == null || found.Count == 0)
|
||||
#else
|
||||
if (found == null || found.IsEmpty)
|
||||
#endif
|
||||
return (null, null);
|
||||
|
||||
// Filter out any empty protections
|
||||
var filteredProtections = found
|
||||
#if NET20 || NET35
|
||||
.Where(kvp => kvp.Value != null && kvp.Value.Count > 0)
|
||||
#else
|
||||
.Where(kvp => kvp.Value != null && !kvp.Value.IsEmpty)
|
||||
#endif
|
||||
.ToDictionary(
|
||||
kvp => kvp.Key,
|
||||
kvp => kvp.Value.OrderBy(s => s).ToList());
|
||||
|
||||
// Return the filtered set of protections
|
||||
return (filteredProtections, null);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return (null, ex.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Format found protections to a deduplicated, ordered string
|
||||
/// </summary>
|
||||
/// <param name="protections">Dictionary of file to list of protection mappings</param>
|
||||
/// <returns>Detected protections, if any</returns>
|
||||
public static string? FormatProtections(Dictionary<string, List<string>>? protections)
|
||||
{
|
||||
// If the filtered list is empty in some way, return
|
||||
if (protections == null || !protections.Any())
|
||||
return "None found [OMIT FROM SUBMISSION]";
|
||||
|
||||
// Get an ordered list of distinct found protections
|
||||
var orderedDistinctProtections = protections
|
||||
.SelectMany(kvp => kvp.Value)
|
||||
.Distinct()
|
||||
.OrderBy(p => p);
|
||||
|
||||
// Sanitize and join protections for writing
|
||||
string protectionString = SanitizeFoundProtections(orderedDistinctProtections);
|
||||
if (string.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")))
|
||||
{
|
||||
if (foundProtections.Any(p => Regex.IsMatch(p, @"SafeDisc [0-9]\.[0-9]{2}\.[0-9]{3}", RegexOptions.Compiled) && !p.StartsWith("Macrovision Protection File")))
|
||||
{
|
||||
foundProtections = foundProtections.Where(p => !p.StartsWith("Macrovision Protected Application"))
|
||||
.Where(p => !p.StartsWith("Macrovision Protection File"))
|
||||
.Where(p => !p.StartsWith("Macrovision Security Driver"))
|
||||
.Where(p => p != "SafeDisc")
|
||||
.Where(p => !(Regex.IsMatch(p, @"SafeDisc [0-9]\.[0-9]{2}\.[0-9]{3}-[0-9]\.[0-9]{2}\.[0-9]{3}", RegexOptions.Compiled)))
|
||||
.Where(p => !(Regex.IsMatch(p, @"SafeDisc [0-9]\.[0-9]{2}\.[0-9]{3}/+", RegexOptions.Compiled)))
|
||||
.Where(p => p != "SafeDisc 1/Lite")
|
||||
.Where(p => p != "SafeDisc 2+");
|
||||
}
|
||||
else if (foundProtections.Any(p => Regex.IsMatch(p, @"SafeDisc [0-9]\.[0-9]{2}\.[0-9]{3}-[0-9]\.[0-9]{2}\.[0-9]{3}", RegexOptions.Compiled) && !p.StartsWith("Macrovision Protection File")))
|
||||
{
|
||||
foundProtections = foundProtections.Where(p => !p.StartsWith("Macrovision Protected Application"))
|
||||
.Where(p => !p.StartsWith("Macrovision Protection File"))
|
||||
.Where(p => !p.StartsWith("Macrovision Security Driver"))
|
||||
.Where(p => p != "SafeDisc")
|
||||
.Where(p => !(Regex.IsMatch(p, @"SafeDisc [0-9]\.[0-9]{2}\.[0-9]{3}/+", RegexOptions.Compiled)))
|
||||
.Where(p => p != "SafeDisc 1/Lite")
|
||||
.Where(p => p != "SafeDisc 2+");
|
||||
}
|
||||
else if (foundProtections.Any(p => Regex.IsMatch(p, @"SafeDisc [0-9]\.[0-9]{2}\.[0-9]{3}/+", RegexOptions.Compiled) && !p.StartsWith("Macrovision Protection File")))
|
||||
{
|
||||
foundProtections = foundProtections.Where(p => !p.StartsWith("Macrovision Protected Application"))
|
||||
.Where(p => !p.StartsWith("Macrovision Protection File"))
|
||||
.Where(p => !p.StartsWith("Macrovision Security Driver"))
|
||||
.Where(p => p != "SafeDisc")
|
||||
.Where(p => p != "SafeDisc 1/Lite")
|
||||
.Where(p => p != "SafeDisc 2+");
|
||||
}
|
||||
else if (foundProtections.Any(p => p.StartsWith("Macrovision Security Driver")))
|
||||
{
|
||||
foundProtections = foundProtections.Where(p => !p.StartsWith("Macrovision Protected Application"))
|
||||
.Where(p => !p.StartsWith("Macrovision Protection File"))
|
||||
.Where(p => p != "SafeDisc")
|
||||
.Where(p => p != "SafeDisc 1/Lite")
|
||||
.Where(p => p != "SafeDisc 2+");
|
||||
}
|
||||
else if (foundProtections.Any(p => p == "SafeDisc 2+"))
|
||||
{
|
||||
foundProtections = foundProtections.Where(p => !p.StartsWith("Macrovision Protected Application"))
|
||||
.Where(p => !p.StartsWith("Macrovision Protection File"))
|
||||
.Where(p => p != "SafeDisc");
|
||||
}
|
||||
else if (foundProtections.Any(p => p == "SafeDisc 1/Lite"))
|
||||
{
|
||||
foundProtections = foundProtections.Where(p => !p.StartsWith("Macrovision Protected Application"))
|
||||
.Where(p => !p.StartsWith("Macrovision Protection File"))
|
||||
.Where(p => p != "SafeDisc");
|
||||
}
|
||||
}
|
||||
|
||||
// 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
920
MPF.Frontend/Tools/SubmissionGenerator.cs
Normal file
920
MPF.Frontend/Tools/SubmissionGenerator.cs
Normal file
@@ -0,0 +1,920 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BinaryObjectScanner;
|
||||
using MPF.Processors;
|
||||
using SabreTools.RedumpLib;
|
||||
using SabreTools.RedumpLib.Data;
|
||||
using SabreTools.RedumpLib.Web;
|
||||
|
||||
namespace MPF.Frontend.Tools
|
||||
{
|
||||
/// <summary>
|
||||
/// Generator for SubmissionInfo objects
|
||||
/// </summary>
|
||||
internal static class SubmissionGenerator
|
||||
{
|
||||
#region Constants
|
||||
|
||||
private const string RequiredValue = "(REQUIRED)";
|
||||
private const string RequiredIfExistsValue = "(REQUIRED, IF EXISTS)";
|
||||
private const string OptionalValue = "(OPTIONAL)";
|
||||
|
||||
#endregion
|
||||
|
||||
#region Extraction and Filling
|
||||
|
||||
/// <summary>
|
||||
/// Extract all of the possible information from a given input combination
|
||||
/// </summary>
|
||||
/// <param name="outputPath">Output path to write to</param>
|
||||
/// <param name="drive">Drive object representing the current drive</param>
|
||||
/// <param name="system">Currently selected system</param>
|
||||
/// <param name="mediaType">Currently selected media type</param>
|
||||
/// <param name="options">Options object representing user-defined options</param>
|
||||
/// <param name="processor">Processor object representing how to process the outputs</param>
|
||||
/// <param name="resultProgress">Optional result progress callback</param>
|
||||
/// <param name="protectionProgress">Optional protection progress callback</param>
|
||||
/// <returns>SubmissionInfo populated based on outputs, null on error</returns>
|
||||
public static async Task<SubmissionInfo?> ExtractOutputInformation(
|
||||
string outputPath,
|
||||
Drive? drive,
|
||||
RedumpSystem? system,
|
||||
MediaType? mediaType,
|
||||
Frontend.Options options,
|
||||
BaseProcessor processor,
|
||||
IProgress<ResultEventArgs>? resultProgress = null,
|
||||
IProgress<ProtectionProgress>? protectionProgress = null)
|
||||
{
|
||||
// Ensure the current disc combination should exist
|
||||
if (!system.MediaTypes().Contains(mediaType))
|
||||
return null;
|
||||
|
||||
// Split the output path for easier use
|
||||
var outputDirectory = Path.GetDirectoryName(outputPath);
|
||||
string outputFilename = Path.GetFileName(outputPath);
|
||||
|
||||
// Check that all of the relevant files are there
|
||||
(bool foundFiles, List<string> missingFiles) = processor.FoundAllFiles(outputDirectory, outputFilename, false);
|
||||
if (!foundFiles)
|
||||
{
|
||||
resultProgress?.Report(ResultEventArgs.Failure($"There were files missing from the output:\n{string.Join("\n", [.. missingFiles])}"));
|
||||
resultProgress?.Report(ResultEventArgs.Failure($"This may indicate an issue with the hardware or media, including unsupported devices.\nPlease see dumping program documentation for more details."));
|
||||
return null;
|
||||
}
|
||||
|
||||
// Sanitize the output filename to strip off any potential extension
|
||||
outputFilename = Path.GetFileNameWithoutExtension(outputFilename);
|
||||
|
||||
// Create the SubmissionInfo object with all user-inputted values by default
|
||||
string combinedBase;
|
||||
if (string.IsNullOrEmpty(outputDirectory))
|
||||
combinedBase = outputFilename;
|
||||
else
|
||||
combinedBase = Path.Combine(outputDirectory, outputFilename);
|
||||
|
||||
// Create the default submission info
|
||||
SubmissionInfo info = CreateDefaultSubmissionInfo(processor, system, mediaType, options.AddPlaceholders);
|
||||
|
||||
// Get specific tool output handling
|
||||
processor?.GenerateSubmissionInfo(info, combinedBase, options.EnableRedumpCompatibility);
|
||||
if (options.IncludeArtifacts)
|
||||
processor?.GenerateArtifacts(info, combinedBase);
|
||||
|
||||
// Get a list of matching IDs for each line in the DAT
|
||||
if (!string.IsNullOrEmpty(info.TracksAndWriteOffsets!.ClrMameProData) && options.HasRedumpLogin)
|
||||
#if NET40
|
||||
_ = FillFromRedump(options, info, resultProgress);
|
||||
#else
|
||||
_ = await FillFromRedump(options, info, resultProgress);
|
||||
#endif
|
||||
|
||||
// If we have both ClrMamePro and Size and Checksums data, remove the ClrMamePro
|
||||
if (!string.IsNullOrEmpty(info.SizeAndChecksums?.CRC32))
|
||||
info.TracksAndWriteOffsets.ClrMameProData = null;
|
||||
|
||||
// Add the volume label to comments, if possible or necessary
|
||||
string? volLabels = FormatVolumeLabels(drive?.VolumeLabel, processor?.VolumeLabels);
|
||||
if (volLabels != null)
|
||||
info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.VolumeLabel] = volLabels;
|
||||
|
||||
// Extract info based generically on MediaType
|
||||
ProcessMediaType(info, mediaType, options.AddPlaceholders);
|
||||
|
||||
// Extract info based specifically on RedumpSystem
|
||||
ProcessSystem(info, system, drive, options.AddPlaceholders, processor is DiscImageCreator, combinedBase);
|
||||
|
||||
// Run anti-modchip check, if necessary
|
||||
if (drive != null && SupportsAntiModchipScans(system) && info.CopyProtection!.AntiModchip == YesNo.NULL)
|
||||
{
|
||||
resultProgress?.Report(ResultEventArgs.Success("Checking for anti-modchip strings... this might take a while!"));
|
||||
info.CopyProtection.AntiModchip = await ProtectionTool.GetPlayStationAntiModchipDetected(drive?.Name) ? YesNo.Yes : YesNo.No;
|
||||
resultProgress?.Report(ResultEventArgs.Success("Anti-modchip string scan complete!"));
|
||||
}
|
||||
|
||||
// Run copy protection, if possible or necessary
|
||||
if (SupportsCopyProtectionScans(system))
|
||||
{
|
||||
resultProgress?.Report(ResultEventArgs.Success("Running copy protection scan... this might take a while!"));
|
||||
var (protectionString, fullProtections) = await ProtectionTool.GetCopyProtection(drive, options, protectionProgress);
|
||||
|
||||
info.CopyProtection!.Protection += protectionString;
|
||||
info.CopyProtection.FullProtections = fullProtections as Dictionary<string, List<string>?> ?? [];
|
||||
resultProgress?.Report(ResultEventArgs.Success("Copy protection scan complete!"));
|
||||
}
|
||||
|
||||
// Set fields that may have automatic filling otherwise
|
||||
info.CommonDiscInfo!.Category ??= DiscCategory.Games;
|
||||
info.VersionAndEditions!.Version ??= options.AddPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
|
||||
// Comments and contents have odd handling
|
||||
if (string.IsNullOrEmpty(info.CommonDiscInfo.Comments))
|
||||
info.CommonDiscInfo.Comments = options.AddPlaceholders ? OptionalValue : string.Empty;
|
||||
if (string.IsNullOrEmpty(info.CommonDiscInfo.Contents))
|
||||
info.CommonDiscInfo.Contents = options.AddPlaceholders ? OptionalValue : string.Empty;
|
||||
|
||||
// Normalize the disc type with all current information
|
||||
Validator.NormalizeDiscType(info);
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fill in a SubmissionInfo object from Redump, if possible
|
||||
/// </summary>
|
||||
/// <param name="options">Options object representing user-defined options</param>
|
||||
/// <param name="info">Existing SubmissionInfo object to fill</param>
|
||||
/// <param name="resultProgress">Optional result progress callback</param>
|
||||
public async static Task<bool> FillFromRedump(Frontend.Options options, SubmissionInfo info, IProgress<ResultEventArgs>? resultProgress = null)
|
||||
{
|
||||
// If no username is provided
|
||||
if (string.IsNullOrEmpty(options.RedumpUsername) || string.IsNullOrEmpty(options.RedumpPassword))
|
||||
return false;
|
||||
|
||||
// Set the current dumper based on username
|
||||
info.DumpersAndStatus ??= new DumpersAndStatusSection();
|
||||
info.DumpersAndStatus.Dumpers = [options.RedumpUsername!];
|
||||
info.PartiallyMatchedIDs = [];
|
||||
|
||||
// Login to Redump
|
||||
var wc = new RedumpClient();
|
||||
bool? loggedIn = await wc.Login(options.RedumpUsername ?? string.Empty, options.RedumpPassword ?? string.Empty);
|
||||
if (loggedIn == null)
|
||||
{
|
||||
resultProgress?.Report(ResultEventArgs.Failure("There was an unknown error connecting to Redump"));
|
||||
return false;
|
||||
}
|
||||
else if (loggedIn == false)
|
||||
{
|
||||
// Don't log the as a failure or error
|
||||
return false;
|
||||
}
|
||||
|
||||
// Setup the checks
|
||||
bool allFound = true;
|
||||
List<int[]> foundIdSets = [];
|
||||
|
||||
// Loop through all of the hashdata to find matching IDs
|
||||
resultProgress?.Report(ResultEventArgs.Success("Finding disc matches on Redump..."));
|
||||
var splitData = info.TracksAndWriteOffsets?.ClrMameProData?.TrimEnd('\n')?.Split('\n');
|
||||
int trackCount = splitData?.Length ?? 0;
|
||||
foreach (string hashData in splitData ?? [])
|
||||
{
|
||||
// Catch any errant blank lines
|
||||
if (string.IsNullOrEmpty(hashData))
|
||||
{
|
||||
trackCount--;
|
||||
resultProgress?.Report(ResultEventArgs.Success("Blank line found, skipping!"));
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the line ends in a known extra track names, skip them for checking
|
||||
if (hashData.Contains("(Track 0).bin")
|
||||
|| hashData.Contains("(Track 0.2).bin")
|
||||
|| hashData.Contains("(Track 00).bin")
|
||||
|| hashData.Contains("(Track 00.2).bin")
|
||||
|| hashData.Contains("(Track A).bin")
|
||||
|| hashData.Contains("(Track A.2).bin")
|
||||
|| hashData.Contains("(Track AA).bin")
|
||||
|| hashData.Contains("(Track AA.2).bin"))
|
||||
{
|
||||
trackCount--;
|
||||
resultProgress?.Report(ResultEventArgs.Success("Extra track found, skipping!"));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get the SHA-1 hash
|
||||
if (!ProcessingTool.GetISOHashValues(hashData, out _, out _, out _, out string? sha1))
|
||||
{
|
||||
resultProgress?.Report(ResultEventArgs.Failure($"Line could not be parsed: {hashData}"));
|
||||
continue;
|
||||
}
|
||||
|
||||
(bool singleFound, var foundIds, string? result) = await Validator.ValidateSingleTrack(wc, info, sha1);
|
||||
if (singleFound)
|
||||
resultProgress?.Report(ResultEventArgs.Success(result));
|
||||
else
|
||||
resultProgress?.Report(ResultEventArgs.Failure(result));
|
||||
|
||||
// Add the found IDs to the map
|
||||
foundIdSets.Add(foundIds?.ToArray() ?? []);
|
||||
|
||||
// Ensure that all tracks are found
|
||||
allFound &= singleFound;
|
||||
}
|
||||
|
||||
// If all tracks were found, check if there are any fully-matched IDs
|
||||
List<int>? fullyMatchedIDs = null;
|
||||
if (allFound)
|
||||
{
|
||||
fullyMatchedIDs = null;
|
||||
foreach (var set in foundIdSets)
|
||||
{
|
||||
// First track is always all IDs
|
||||
if (fullyMatchedIDs == null)
|
||||
{
|
||||
fullyMatchedIDs = [.. set];
|
||||
continue;
|
||||
}
|
||||
|
||||
// Try to intersect with all known IDs
|
||||
fullyMatchedIDs = fullyMatchedIDs.Intersect(set).ToList();
|
||||
if (!fullyMatchedIDs.Any())
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If we don't have any matches but we have a universal hash
|
||||
if (!info.PartiallyMatchedIDs.Any() && info.CommonDiscInfo?.CommentsSpecialFields?.ContainsKey(SiteCode.UniversalHash) == true)
|
||||
{
|
||||
#if NET40
|
||||
var validateTask = Validator.ValidateUniversalHash(wc, info);
|
||||
validateTask.Wait();
|
||||
(bool singleFound, var foundIds, string? result) = validateTask.Result;
|
||||
#else
|
||||
(bool singleFound, var foundIds, string? result) = await Validator.ValidateUniversalHash(wc, info);
|
||||
#endif
|
||||
if (singleFound)
|
||||
resultProgress?.Report(ResultEventArgs.Success(result));
|
||||
else
|
||||
resultProgress?.Report(ResultEventArgs.Failure(result));
|
||||
|
||||
// Ensure that the hash is found
|
||||
allFound = singleFound;
|
||||
|
||||
// If we found a match, then the disc is a match
|
||||
if (singleFound && foundIds != null)
|
||||
fullyMatchedIDs = foundIds;
|
||||
else
|
||||
fullyMatchedIDs = [];
|
||||
}
|
||||
|
||||
// Make sure we only have unique IDs
|
||||
info.PartiallyMatchedIDs = [.. info.PartiallyMatchedIDs.Distinct().OrderBy(id => id)];
|
||||
|
||||
resultProgress?.Report(ResultEventArgs.Success("Match finding complete! " + (fullyMatchedIDs != null && fullyMatchedIDs.Count > 0
|
||||
? "Fully Matched IDs: " + string.Join(",", fullyMatchedIDs.Select(i => i.ToString()).ToArray())
|
||||
: "No matches found")));
|
||||
|
||||
// Exit early if one failed or there are no matched IDs
|
||||
if (!allFound || fullyMatchedIDs == null || fullyMatchedIDs.Count == 0)
|
||||
return false;
|
||||
|
||||
// Find the first matched ID where the track count matches, we can grab a bunch of info from it
|
||||
int totalMatchedIDsCount = fullyMatchedIDs.Count;
|
||||
for (int i = 0; i < totalMatchedIDsCount; i++)
|
||||
{
|
||||
// Skip if the track count doesn't match
|
||||
#if NET40
|
||||
var validateTask = Validator.ValidateTrackCount(wc, fullyMatchedIDs[i], trackCount);
|
||||
validateTask.Wait();
|
||||
if (!validateTask.Result)
|
||||
#else
|
||||
if (!await Validator.ValidateTrackCount(wc, fullyMatchedIDs[i], trackCount))
|
||||
#endif
|
||||
continue;
|
||||
|
||||
// Fill in the fields from the existing ID
|
||||
resultProgress?.Report(ResultEventArgs.Success($"Filling fields from existing ID {fullyMatchedIDs[i]}..."));
|
||||
#if NET40
|
||||
var fillTask = Task.Factory.StartNew(() => Builder.FillFromId(wc, info, fullyMatchedIDs[i], options.PullAllInformation));
|
||||
fillTask.Wait();
|
||||
_ = fillTask.Result;
|
||||
#else
|
||||
_ = await Builder.FillFromId(wc, info, fullyMatchedIDs[i], options.PullAllInformation);
|
||||
#endif
|
||||
resultProgress?.Report(ResultEventArgs.Success("Information filling complete!"));
|
||||
|
||||
// Set the fully matched ID to the current
|
||||
info.FullyMatchedID = fullyMatchedIDs[i];
|
||||
break;
|
||||
}
|
||||
|
||||
// Clear out fully matched IDs from the partial list
|
||||
if (info.FullyMatchedID.HasValue)
|
||||
{
|
||||
if (info.PartiallyMatchedIDs.Count == 1)
|
||||
info.PartiallyMatchedIDs = null;
|
||||
else
|
||||
info.PartiallyMatchedIDs.Remove(info.FullyMatchedID.Value);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Helper Functions
|
||||
|
||||
/// <summary>
|
||||
/// Creates a default SubmissionInfo object based on the current system and media type
|
||||
/// </summary>
|
||||
private static SubmissionInfo CreateDefaultSubmissionInfo(BaseProcessor processor, RedumpSystem? system, MediaType? mediaType, bool addPlaceholders)
|
||||
{
|
||||
// Create the template object
|
||||
var info = new SubmissionInfo()
|
||||
{
|
||||
CommonDiscInfo = new CommonDiscInfoSection()
|
||||
{
|
||||
System = system,
|
||||
Media = mediaType.ToDiscType(),
|
||||
Title = addPlaceholders ? RequiredValue : string.Empty,
|
||||
ForeignTitleNonLatin = addPlaceholders ? OptionalValue : string.Empty,
|
||||
DiscNumberLetter = addPlaceholders ? OptionalValue : string.Empty,
|
||||
DiscTitle = addPlaceholders ? OptionalValue : string.Empty,
|
||||
Category = null,
|
||||
Region = null,
|
||||
Languages = null,
|
||||
Serial = addPlaceholders ? RequiredIfExistsValue : string.Empty,
|
||||
Barcode = addPlaceholders ? OptionalValue : string.Empty,
|
||||
Contents = string.Empty,
|
||||
},
|
||||
VersionAndEditions = new VersionAndEditionsSection()
|
||||
{
|
||||
OtherEditions = addPlaceholders ? "(VERIFY THIS) Original" : string.Empty,
|
||||
},
|
||||
DumpingInfo = new DumpingInfoSection()
|
||||
{
|
||||
FrontendVersion = FrontendTool.GetCurrentVersion(),
|
||||
DumpingProgram = GetDumpingProgramFromProcessor(processor),
|
||||
},
|
||||
};
|
||||
|
||||
// Ensure that required sections exist
|
||||
info = Builder.EnsureAllSections(info);
|
||||
return info;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the dumping program name from the processor
|
||||
/// </summary>
|
||||
/// <param name="processor"></param>
|
||||
/// <returns></returns>
|
||||
private static string? GetDumpingProgramFromProcessor(BaseProcessor processor)
|
||||
{
|
||||
// Map to the internal program
|
||||
InternalProgram? internalProgram = processor switch
|
||||
{
|
||||
Processors.Aaru => InternalProgram.Aaru,
|
||||
CleanRip => InternalProgram.CleanRip,
|
||||
DiscImageCreator => InternalProgram.DiscImageCreator,
|
||||
PS3CFW => InternalProgram.PS3CFW,
|
||||
Redumper => InternalProgram.Redumper,
|
||||
UmdImageCreator => InternalProgram.UmdImageCreator,
|
||||
XboxBackupCreator => InternalProgram.XboxBackupCreator,
|
||||
_ => null,
|
||||
};
|
||||
|
||||
// Use the internal program to map to the name
|
||||
return internalProgram.LongName();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formats a list of volume labels and their corresponding filesystems
|
||||
/// </summary>
|
||||
/// <param name="labels">Dictionary of volume labels and their filesystems</param>
|
||||
/// <returns>Formatted string of volume labels and their filesystems</returns>
|
||||
private static string? FormatVolumeLabels(string? driveLabel, Dictionary<string, List<string>>? labels)
|
||||
{
|
||||
// Must have at least one label to format
|
||||
if (driveLabel == null && (labels == null || labels.Count == 0))
|
||||
return null;
|
||||
|
||||
// If no labels given, use drive label
|
||||
if (labels == null || labels.Count == 0)
|
||||
{
|
||||
// Ignore common volume labels
|
||||
if (FrontendTool.GetRedumpSystemFromVolumeLabel(driveLabel) != null)
|
||||
return null;
|
||||
|
||||
return driveLabel;
|
||||
}
|
||||
|
||||
// If only one label, don't mention fs
|
||||
string firstLabel = labels.First().Key;
|
||||
if (labels.Count == 1 && (firstLabel == driveLabel || driveLabel == null))
|
||||
{
|
||||
// Ignore common volume labels
|
||||
if (FrontendTool.GetRedumpSystemFromVolumeLabel(firstLabel) != null)
|
||||
return null;
|
||||
|
||||
return firstLabel;
|
||||
}
|
||||
|
||||
// Otherwise, state filesystem for each label
|
||||
List<string> volLabels = [];
|
||||
|
||||
// Begin formatted output with the label from Windows, if it is unique and not a common volume label
|
||||
if (driveLabel != null && !labels.TryGetValue(driveLabel, out List<string>? value) && FrontendTool.GetRedumpSystemFromVolumeLabel(driveLabel) == null)
|
||||
volLabels.Add(driveLabel);
|
||||
|
||||
// Add remaining labels with their corresponding filesystems
|
||||
foreach (KeyValuePair<string, List<string>> label in labels)
|
||||
{
|
||||
// Ignore common volume labels
|
||||
if (FrontendTool.GetRedumpSystemFromVolumeLabel(label.Key) == null)
|
||||
volLabels.Add($"{label.Key} ({string.Join(", ", [.. label.Value])})");
|
||||
}
|
||||
|
||||
// Ensure that no labels are empty
|
||||
volLabels = volLabels.Where(l => !string.IsNullOrEmpty(l?.Trim())).ToList();
|
||||
|
||||
// Print each label separated by a comma and a space
|
||||
if (volLabels.Count == 0)
|
||||
return null;
|
||||
|
||||
return string.Join(", ", [.. volLabels]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes default data based on media type
|
||||
/// </summary>
|
||||
private static bool ProcessMediaType(SubmissionInfo info, MediaType? mediaType, bool addPlaceholders)
|
||||
{
|
||||
switch (mediaType)
|
||||
{
|
||||
case MediaType.CDROM:
|
||||
case MediaType.GDROM:
|
||||
info.CommonDiscInfo!.Layer0MasteringRing = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer0MasteringSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer0ToolstampMasteringCode = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer0MouldSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer1MouldSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer0AdditionalMould = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
break;
|
||||
|
||||
case MediaType.DVD:
|
||||
case MediaType.HDDVD:
|
||||
case MediaType.BluRay:
|
||||
|
||||
// If we have a single-layer disc
|
||||
if (info.SizeAndChecksums!.Layerbreak == default)
|
||||
{
|
||||
info.CommonDiscInfo!.Layer0MasteringRing = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer0MasteringSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer0ToolstampMasteringCode = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer0MouldSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer1MouldSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer0AdditionalMould = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
}
|
||||
// If we have a dual-layer disc
|
||||
else if (info.SizeAndChecksums!.Layerbreak2 == default)
|
||||
{
|
||||
info.CommonDiscInfo!.Layer0MasteringRing = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer0MasteringSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer0ToolstampMasteringCode = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer0MouldSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer0AdditionalMould = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
|
||||
info.CommonDiscInfo.Layer1MasteringRing = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer1MasteringSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer1ToolstampMasteringCode = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer1MouldSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
}
|
||||
// If we have a triple-layer disc
|
||||
else if (info.SizeAndChecksums!.Layerbreak3 == default)
|
||||
{
|
||||
info.CommonDiscInfo!.Layer0MasteringRing = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer0MasteringSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer0ToolstampMasteringCode = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer0MouldSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer0AdditionalMould = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
|
||||
info.CommonDiscInfo.Layer1MasteringRing = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer1MasteringSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer1ToolstampMasteringCode = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer1MouldSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
|
||||
info.CommonDiscInfo.Layer2MasteringRing = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer2MasteringSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer2ToolstampMasteringCode = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
}
|
||||
// If we have a quad-layer disc
|
||||
else
|
||||
{
|
||||
info.CommonDiscInfo!.Layer0MasteringRing = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer0MasteringSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer0ToolstampMasteringCode = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer0MouldSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer0AdditionalMould = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
|
||||
info.CommonDiscInfo.Layer1MasteringRing = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer1MasteringSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer1ToolstampMasteringCode = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer1MouldSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
|
||||
info.CommonDiscInfo.Layer2MasteringRing = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer2MasteringSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer2ToolstampMasteringCode = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
|
||||
info.CommonDiscInfo.Layer3MasteringRing = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer3MasteringSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer3ToolstampMasteringCode = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case MediaType.NintendoGameCubeGameDisc:
|
||||
info.CommonDiscInfo!.Layer0MasteringRing = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer0MasteringSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer0ToolstampMasteringCode = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer0MouldSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer1MouldSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer0AdditionalMould = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.Extras!.BCA ??= (addPlaceholders ? RequiredValue : string.Empty);
|
||||
break;
|
||||
|
||||
case MediaType.NintendoWiiOpticalDisc:
|
||||
|
||||
// If we have a single-layer disc
|
||||
if (info.SizeAndChecksums!.Layerbreak == default)
|
||||
{
|
||||
info.CommonDiscInfo!.Layer0MasteringRing = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer0MasteringSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer0ToolstampMasteringCode = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer0MouldSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer1MouldSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer0AdditionalMould = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
}
|
||||
// If we have a dual-layer disc
|
||||
else
|
||||
{
|
||||
info.CommonDiscInfo!.Layer0MasteringRing = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer0MasteringSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer0ToolstampMasteringCode = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer0MouldSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer0AdditionalMould = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
|
||||
info.CommonDiscInfo.Layer1MasteringRing = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer1MasteringSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer1ToolstampMasteringCode = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer1MouldSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
}
|
||||
|
||||
info.Extras!.DiscKey = addPlaceholders ? RequiredValue : string.Empty;
|
||||
info.Extras.BCA = info.Extras.BCA ?? (addPlaceholders ? RequiredValue : string.Empty);
|
||||
|
||||
break;
|
||||
|
||||
case MediaType.UMD:
|
||||
// Both single- and dual-layer discs have two "layers" for the ring
|
||||
info.CommonDiscInfo!.Layer0MasteringRing = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer0MasteringSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer0ToolstampMasteringCode = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer0MouldSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
|
||||
info.CommonDiscInfo.Layer1MasteringRing = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer1MasteringSID = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
info.CommonDiscInfo.Layer1ToolstampMasteringCode = addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
|
||||
info.SizeAndChecksums!.CRC32 ??= (addPlaceholders ? RequiredValue + " [Not automatically generated for UMD]" : string.Empty);
|
||||
info.SizeAndChecksums.MD5 ??= (addPlaceholders ? RequiredValue + " [Not automatically generated for UMD]" : string.Empty);
|
||||
info.SizeAndChecksums.SHA1 ??= (addPlaceholders ? RequiredValue + " [Not automatically generated for UMD]" : string.Empty);
|
||||
info.TracksAndWriteOffsets!.ClrMameProData = null;
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes default data based on system type
|
||||
/// </summary>
|
||||
private static bool ProcessSystem(SubmissionInfo info, RedumpSystem? system, Drive? drive, bool addPlaceholders, bool isDiscImageCreator, string basePath)
|
||||
{
|
||||
// Extract info based specifically on RedumpSystem
|
||||
switch (system)
|
||||
{
|
||||
case RedumpSystem.AcornArchimedes:
|
||||
info.CommonDiscInfo!.Region ??= Region.UnitedKingdom;
|
||||
break;
|
||||
|
||||
case RedumpSystem.AudioCD:
|
||||
case RedumpSystem.DVDAudio:
|
||||
case RedumpSystem.EnhancedCD:
|
||||
case RedumpSystem.SuperAudioCD:
|
||||
info.CommonDiscInfo!.Category ??= DiscCategory.Audio;
|
||||
break;
|
||||
|
||||
case RedumpSystem.BandaiPlaydiaQuickInteractiveSystem:
|
||||
info.CommonDiscInfo!.EXEDateBuildDate ??= addPlaceholders ? RequiredValue : string.Empty;
|
||||
info.CommonDiscInfo.Region ??= info.CommonDiscInfo.Region ?? Region.Japan;
|
||||
break;
|
||||
|
||||
case RedumpSystem.BDVideo:
|
||||
case RedumpSystem.DVDVideo:
|
||||
case RedumpSystem.HDDVDVideo:
|
||||
info.CommonDiscInfo!.Category ??= DiscCategory.Video;
|
||||
info.CopyProtection!.Protection ??= addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
break;
|
||||
|
||||
case RedumpSystem.CommodoreAmigaCD:
|
||||
info.CommonDiscInfo!.EXEDateBuildDate ??= addPlaceholders ? RequiredValue : string.Empty;
|
||||
break;
|
||||
|
||||
case RedumpSystem.CommodoreAmigaCD32:
|
||||
info.CommonDiscInfo!.EXEDateBuildDate ??= addPlaceholders ? RequiredValue : string.Empty;
|
||||
info.CommonDiscInfo.Region ??= Region.Europe;
|
||||
break;
|
||||
|
||||
case RedumpSystem.CommodoreAmigaCDTV:
|
||||
info.CommonDiscInfo!.EXEDateBuildDate ??= addPlaceholders ? RequiredValue : string.Empty;
|
||||
info.CommonDiscInfo.Region ??= Region.Europe;
|
||||
break;
|
||||
|
||||
case RedumpSystem.FujitsuFMTownsseries:
|
||||
info.CommonDiscInfo!.EXEDateBuildDate ??= addPlaceholders ? RequiredValue : string.Empty;
|
||||
info.CommonDiscInfo.Region ??= Region.Japan;
|
||||
break;
|
||||
|
||||
case RedumpSystem.FujitsuFMTownsMarty:
|
||||
info.CommonDiscInfo!.Region ??= Region.Japan;
|
||||
break;
|
||||
|
||||
case RedumpSystem.HasbroVideoNow:
|
||||
case RedumpSystem.HasbroVideoNowColor:
|
||||
case RedumpSystem.HasbroVideoNowJr:
|
||||
case RedumpSystem.VideoCD:
|
||||
info.CommonDiscInfo!.Category ??= DiscCategory.Video;
|
||||
break;
|
||||
|
||||
case RedumpSystem.HasbroVideoNowXP:
|
||||
case RedumpSystem.PhotoCD:
|
||||
case RedumpSystem.SonyElectronicBook:
|
||||
info.CommonDiscInfo!.Category ??= DiscCategory.Multimedia;
|
||||
break;
|
||||
|
||||
case RedumpSystem.IncredibleTechnologiesEagle:
|
||||
info.CommonDiscInfo!.EXEDateBuildDate ??= addPlaceholders ? RequiredValue : string.Empty;
|
||||
break;
|
||||
|
||||
case RedumpSystem.KonamieAmusement:
|
||||
info.CommonDiscInfo!.EXEDateBuildDate ??= addPlaceholders ? RequiredValue : string.Empty;
|
||||
break;
|
||||
|
||||
case RedumpSystem.KonamiFireBeat:
|
||||
info.CommonDiscInfo!.EXEDateBuildDate ??= addPlaceholders ? RequiredValue : string.Empty;
|
||||
break;
|
||||
|
||||
case RedumpSystem.KonamiPython2:
|
||||
// TODO: Remove this hack when DIC supports build date output
|
||||
if (isDiscImageCreator)
|
||||
info.CommonDiscInfo!.EXEDateBuildDate = DiscImageCreator.GetPlayStationEXEDate($"{basePath}_volDesc.txt", drive?.GetPlayStationExecutableName());
|
||||
|
||||
if (info.CommonDiscInfo!.CommentsSpecialFields!.TryGetValue(SiteCode.InternalSerialName, out string? kp2Exe) && !string.IsNullOrEmpty(kp2Exe))
|
||||
info.CommonDiscInfo.Region = ProcessingTool.GetPlayStationRegion(kp2Exe);
|
||||
|
||||
if (drive?.GetPlayStationExecutableInfo(out var kp2Serial, out Region? kp2Region, out var kp2Date) == true)
|
||||
{
|
||||
if (!info.CommonDiscInfo!.CommentsSpecialFields!.TryGetValue(SiteCode.InternalSerialName, out string? value) || string.IsNullOrEmpty(value))
|
||||
info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.InternalSerialName] = kp2Serial ?? string.Empty;
|
||||
|
||||
info.CommonDiscInfo.Region ??= kp2Region;
|
||||
info.CommonDiscInfo.EXEDateBuildDate ??= kp2Date;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(info.VersionAndEditions!.Version))
|
||||
info.VersionAndEditions!.Version = drive?.GetPlayStation2Version() ?? string.Empty;
|
||||
|
||||
break;
|
||||
|
||||
case RedumpSystem.KonamiSystemGV:
|
||||
info.CommonDiscInfo!.EXEDateBuildDate ??= addPlaceholders ? RequiredValue : string.Empty;
|
||||
break;
|
||||
|
||||
case RedumpSystem.KonamiSystem573:
|
||||
info.CommonDiscInfo!.EXEDateBuildDate ??= addPlaceholders ? RequiredValue : string.Empty;
|
||||
break;
|
||||
|
||||
case RedumpSystem.KonamiTwinkle:
|
||||
info.CommonDiscInfo!.EXEDateBuildDate ??= addPlaceholders ? RequiredValue : string.Empty;
|
||||
break;
|
||||
|
||||
case RedumpSystem.MattelHyperScan:
|
||||
info.CommonDiscInfo!.EXEDateBuildDate ??= addPlaceholders ? RequiredValue : string.Empty;
|
||||
break;
|
||||
|
||||
case RedumpSystem.MicrosoftXboxOne:
|
||||
if (drive?.Name != null)
|
||||
{
|
||||
string xboxOneMsxcPath = Path.Combine(drive.Name, "MSXC");
|
||||
if (drive != null && Directory.Exists(xboxOneMsxcPath))
|
||||
{
|
||||
info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.Filename] = string.Join("\n",
|
||||
Directory.GetFiles(xboxOneMsxcPath, "*", SearchOption.TopDirectoryOnly).Select(Path.GetFileName).ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case RedumpSystem.MicrosoftXboxSeriesXS:
|
||||
if (drive?.Name != null)
|
||||
{
|
||||
string xboxSeriesXMsxcPath = Path.Combine(drive.Name, "MSXC");
|
||||
if (drive != null && Directory.Exists(xboxSeriesXMsxcPath))
|
||||
{
|
||||
info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.Filename] = string.Join("\n",
|
||||
Directory.GetFiles(xboxSeriesXMsxcPath, "*", SearchOption.TopDirectoryOnly).Select(Path.GetFileName).ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case RedumpSystem.NamcoSegaNintendoTriforce:
|
||||
info.CommonDiscInfo!.EXEDateBuildDate ??= addPlaceholders ? RequiredValue : string.Empty;
|
||||
break;
|
||||
|
||||
case RedumpSystem.NavisoftNaviken21:
|
||||
info.CommonDiscInfo!.EXEDateBuildDate = addPlaceholders ? RequiredValue : string.Empty;
|
||||
info.CommonDiscInfo.Region ??= Region.Japan;
|
||||
break;
|
||||
|
||||
case RedumpSystem.NECPC88series:
|
||||
info.CommonDiscInfo!.Region ??= Region.Japan;
|
||||
break;
|
||||
|
||||
case RedumpSystem.NECPC98series:
|
||||
info.CommonDiscInfo!.EXEDateBuildDate = addPlaceholders ? RequiredValue : string.Empty;
|
||||
info.CommonDiscInfo!.Region ??= Region.Japan;
|
||||
break;
|
||||
|
||||
case RedumpSystem.NECPCFXPCFXGA:
|
||||
info.CommonDiscInfo!.Region ??= Region.Japan;
|
||||
break;
|
||||
|
||||
case RedumpSystem.SegaChihiro:
|
||||
info.CommonDiscInfo!.EXEDateBuildDate ??= addPlaceholders ? RequiredValue : string.Empty;
|
||||
break;
|
||||
|
||||
case RedumpSystem.SegaDreamcast:
|
||||
info.CommonDiscInfo!.EXEDateBuildDate ??= addPlaceholders ? RequiredValue : string.Empty;
|
||||
break;
|
||||
|
||||
case RedumpSystem.SegaNaomi:
|
||||
info.CommonDiscInfo!.EXEDateBuildDate ??= addPlaceholders ? RequiredValue : string.Empty;
|
||||
break;
|
||||
|
||||
case RedumpSystem.SegaNaomi2:
|
||||
info.CommonDiscInfo!.EXEDateBuildDate ??= addPlaceholders ? RequiredValue : string.Empty;
|
||||
break;
|
||||
|
||||
case RedumpSystem.SegaTitanVideo:
|
||||
info.CommonDiscInfo!.EXEDateBuildDate ??= addPlaceholders ? RequiredValue : string.Empty;
|
||||
break;
|
||||
|
||||
case RedumpSystem.SharpX68000:
|
||||
info.CommonDiscInfo!.Region ??= Region.Japan;
|
||||
break;
|
||||
|
||||
case RedumpSystem.SNKNeoGeoCD:
|
||||
info.CommonDiscInfo!.EXEDateBuildDate ??= addPlaceholders ? RequiredValue : string.Empty;
|
||||
break;
|
||||
|
||||
case RedumpSystem.SonyPlayStation:
|
||||
// TODO: Remove this hack when DIC supports build date output
|
||||
if (isDiscImageCreator)
|
||||
info.CommonDiscInfo!.EXEDateBuildDate = DiscImageCreator.GetPlayStationEXEDate($"{basePath}_volDesc.txt", drive?.GetPlayStationExecutableName(), psx: true);
|
||||
|
||||
if (info.CommonDiscInfo!.CommentsSpecialFields!.TryGetValue(SiteCode.InternalSerialName, out string? psxExe) && !string.IsNullOrEmpty(psxExe))
|
||||
info.CommonDiscInfo.Region = ProcessingTool.GetPlayStationRegion(psxExe);
|
||||
|
||||
if (drive?.GetPlayStationExecutableInfo(out var psxSerial, out Region? psxRegion, out var psxDate) == true)
|
||||
{
|
||||
if (!info.CommonDiscInfo!.CommentsSpecialFields!.TryGetValue(SiteCode.InternalSerialName, out string? value) || string.IsNullOrEmpty(value))
|
||||
info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.InternalSerialName] = psxSerial ?? string.Empty;
|
||||
|
||||
info.CommonDiscInfo.Region ??= psxRegion;
|
||||
info.CommonDiscInfo.EXEDateBuildDate ??= psxDate;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case RedumpSystem.SonyPlayStation2:
|
||||
info.CommonDiscInfo!.LanguageSelection ??= [];
|
||||
|
||||
// TODO: Remove this hack when DIC supports build date output
|
||||
if (isDiscImageCreator)
|
||||
info.CommonDiscInfo!.EXEDateBuildDate = DiscImageCreator.GetPlayStationEXEDate($"{basePath}_volDesc.txt", drive?.GetPlayStationExecutableName());
|
||||
|
||||
if (info.CommonDiscInfo!.CommentsSpecialFields!.TryGetValue(SiteCode.InternalSerialName, out string? ps2Exe) && !string.IsNullOrEmpty(ps2Exe))
|
||||
info.CommonDiscInfo.Region = ProcessingTool.GetPlayStationRegion(ps2Exe);
|
||||
|
||||
if (drive?.GetPlayStationExecutableInfo(out var ps2Serial, out Region? ps2Region, out var ps2Date) == true)
|
||||
{
|
||||
if (!info.CommonDiscInfo!.CommentsSpecialFields!.TryGetValue(SiteCode.InternalSerialName, out string? value) || string.IsNullOrEmpty(value))
|
||||
info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.InternalSerialName] = ps2Serial ?? string.Empty;
|
||||
|
||||
info.CommonDiscInfo.Region ??= ps2Region;
|
||||
info.CommonDiscInfo.EXEDateBuildDate ??= ps2Date;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(info.VersionAndEditions!.Version))
|
||||
info.VersionAndEditions!.Version = drive?.GetPlayStation2Version() ?? string.Empty;
|
||||
|
||||
break;
|
||||
|
||||
case RedumpSystem.SonyPlayStation3:
|
||||
info.Extras!.DiscKey ??= addPlaceholders ? RequiredValue : string.Empty;
|
||||
info.Extras.DiscID ??= addPlaceholders ? RequiredValue : string.Empty;
|
||||
|
||||
if (!info.CommonDiscInfo!.CommentsSpecialFields!.TryGetValue(SiteCode.InternalSerialName, out string? ps3Serial) || string.IsNullOrEmpty(ps3Serial))
|
||||
info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.InternalSerialName] = drive?.GetPlayStation3Serial() ?? string.Empty;
|
||||
|
||||
if (string.IsNullOrEmpty(info.VersionAndEditions!.Version))
|
||||
info.VersionAndEditions!.Version = drive?.GetPlayStation3Version() ?? string.Empty;
|
||||
|
||||
if (!info.CommonDiscInfo!.CommentsSpecialFields!.TryGetValue(SiteCode.Patches, out string? ps3Firmware) || string.IsNullOrEmpty(ps3Firmware))
|
||||
{
|
||||
string? firmwareVersion = drive?.GetPlayStation3FirmwareVersion();
|
||||
if (firmwareVersion != null)
|
||||
info.CommonDiscInfo!.ContentsSpecialFields![SiteCode.Patches] = $"PS3 Firmware {firmwareVersion}";
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case RedumpSystem.SonyPlayStation4:
|
||||
if (!info.CommonDiscInfo!.CommentsSpecialFields!.TryGetValue(SiteCode.InternalSerialName, out string? ps4Serial) || string.IsNullOrEmpty(ps4Serial))
|
||||
info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.InternalSerialName] = drive?.GetPlayStation4Serial() ?? string.Empty;
|
||||
|
||||
if (string.IsNullOrEmpty(info.VersionAndEditions!.Version))
|
||||
info.VersionAndEditions!.Version = drive?.GetPlayStation4Version() ?? string.Empty;
|
||||
|
||||
break;
|
||||
|
||||
case RedumpSystem.SonyPlayStation5:
|
||||
if (!info.CommonDiscInfo!.CommentsSpecialFields!.TryGetValue(SiteCode.InternalSerialName, out string? ps5Serial) || string.IsNullOrEmpty(ps5Serial))
|
||||
info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.InternalSerialName] = drive?.GetPlayStation5Serial() ?? string.Empty;
|
||||
|
||||
if (string.IsNullOrEmpty(info.VersionAndEditions!.Version))
|
||||
info.VersionAndEditions!.Version = drive?.GetPlayStation5Version() ?? string.Empty;
|
||||
|
||||
break;
|
||||
|
||||
case RedumpSystem.TomyKissSite:
|
||||
info.CommonDiscInfo!.Category ??= DiscCategory.Video;
|
||||
info.CommonDiscInfo!.Region ??= Region.Japan;
|
||||
break;
|
||||
|
||||
case RedumpSystem.ZAPiTGamesGameWaveFamilyEntertainmentSystem:
|
||||
info.CopyProtection!.Protection ??= addPlaceholders ? RequiredIfExistsValue : string.Empty;
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper to determine if a system requires an anti-modchip scan
|
||||
/// </summary>
|
||||
private static bool SupportsAntiModchipScans(RedumpSystem? system)
|
||||
{
|
||||
return system switch
|
||||
{
|
||||
RedumpSystem.SonyPlayStation => true,
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper to determine if a system requires a copy protection scan
|
||||
/// </summary>
|
||||
private static bool SupportsCopyProtectionScans(RedumpSystem? system)
|
||||
{
|
||||
return system switch
|
||||
{
|
||||
RedumpSystem.AppleMacintosh => true,
|
||||
RedumpSystem.EnhancedCD => true,
|
||||
RedumpSystem.IBMPCcompatible => true,
|
||||
RedumpSystem.PalmOS => true,
|
||||
RedumpSystem.PocketPC => true,
|
||||
RedumpSystem.RainbowDisc => true,
|
||||
RedumpSystem.SonyElectronicBook => true,
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
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(Func<SubmissionInfo?, (bool?, SubmissionInfo?)> processUserInfo)
|
||||
{
|
||||
if (string.IsNullOrEmpty(InputPath))
|
||||
return "Invalid Input path";
|
||||
|
||||
if (!File.Exists(this.InputPath!.Trim('"')))
|
||||
return "Input Path is not a valid file";
|
||||
|
||||
// Disable UI while Check is running
|
||||
DisableUIElements();
|
||||
bool cachedCanExecuteSelectionChanged = CanExecuteSelectionChanged;
|
||||
DisableEventHandlers();
|
||||
|
||||
// Populate an environment
|
||||
var env = new DumpEnvironment(Options, Path.GetFullPath(this.InputPath.Trim('"')), null, this.CurrentSystem, this.CurrentMediaType, this.CurrentProgram, parameters: null);
|
||||
|
||||
// Make new Progress objects
|
||||
var resultProgress = new Progress<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
|
||||
}
|
||||
}
|
||||
2328
MPF.Frontend/ViewModels/MainViewModel.cs
Normal file
2328
MPF.Frontend/ViewModels/MainViewModel.cs
Normal file
File diff suppressed because it is too large
Load Diff
155
MPF.Frontend/ViewModels/OptionsViewModel.cs
Normal file
155
MPF.Frontend/ViewModels/OptionsViewModel.cs
Normal file
@@ -0,0 +1,155 @@
|
||||
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>
|
||||
/// Test Redump login credentials
|
||||
/// </summary>
|
||||
public static async Task<(bool?, string?)> TestRedumpLogin(string username, string password)
|
||||
{
|
||||
return await RedumpClient.ValidateCredentials(username, password);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reset Redumper non-redump options (Read Method, Sector Order, Drive Type)
|
||||
/// </summary>
|
||||
public void NonRedumpModeUnChecked()
|
||||
{
|
||||
Options.RedumperReadMethod = RedumperReadMethod.NONE;
|
||||
Options.RedumperSectorOrder = RedumperSectorOrder.NONE;
|
||||
Options.RedumperUseGenericDriveType = false;
|
||||
TriggerPropertyChanged(nameof(Options));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#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
|
||||
}
|
||||
}
|
||||
1311
MPF.Processors/Aaru.cs
Normal file
1311
MPF.Processors/Aaru.cs
Normal file
File diff suppressed because it is too large
Load Diff
397
MPF.Processors/BaseProcessor.cs
Normal file
397
MPF.Processors/BaseProcessor.cs
Normal file
@@ -0,0 +1,397 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
#if NET452_OR_GREATER || NETCOREAPP
|
||||
using System.IO.Compression;
|
||||
#endif
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using SabreTools.RedumpLib.Data;
|
||||
|
||||
namespace MPF.Processors
|
||||
{
|
||||
public abstract class BaseProcessor
|
||||
{
|
||||
/// <summary>
|
||||
/// All found volume labels and their corresponding file systems
|
||||
/// </summary>
|
||||
public Dictionary<string, List<string>>? VolumeLabels;
|
||||
|
||||
#region Metadata
|
||||
|
||||
/// <summary>
|
||||
/// Currently represented system
|
||||
/// </summary>
|
||||
public RedumpSystem? System { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Currently represented media type
|
||||
/// </summary>
|
||||
public MediaType? Type { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Generate processor for a system and media type combination
|
||||
/// </summary>
|
||||
/// <param name="system">RedumpSystem value to use</param>
|
||||
/// <param name="type">MediaType value to use</param>
|
||||
public BaseProcessor(RedumpSystem? system, MediaType? type)
|
||||
{
|
||||
System = system;
|
||||
Type = type;
|
||||
}
|
||||
|
||||
#region Abstract Methods
|
||||
|
||||
/// <summary>
|
||||
/// Validate if all required output files exist
|
||||
/// </summary>
|
||||
/// <param name="basePath">Base filename and path to use for checking</param>
|
||||
/// <param name="preCheck">True if this is a check done before a dump, false if done after</param>
|
||||
/// <returns>Tuple of true if all required files exist, false otherwise and a list representing missing files</returns>
|
||||
public abstract (bool, List<string>) CheckAllOutputFilesExist(string basePath, bool preCheck);
|
||||
|
||||
/// <summary>
|
||||
/// Generate artifacts and add to the SubmissionInfo
|
||||
/// </summary>
|
||||
/// <param name="submissionInfo">Base submission info to fill in specifics for</param>
|
||||
/// <param name="basePath">Base filename and path to use for checking</param>
|
||||
public abstract void GenerateArtifacts(SubmissionInfo submissionInfo, string basePath);
|
||||
|
||||
/// <summary>
|
||||
/// Generate a SubmissionInfo for the output files
|
||||
/// </summary>
|
||||
/// <param name="submissionInfo">Base submission info to fill in specifics for</param>
|
||||
/// <param name="basePath">Base filename and path to use for checking</param>
|
||||
/// <param name="redumpCompat">Determines if outputs are processed according to Redump specifications</param>
|
||||
public abstract void GenerateSubmissionInfo(SubmissionInfo submissionInfo, string basePath, bool redumpCompat);
|
||||
|
||||
/// <summary>
|
||||
/// Generate a list of all log files generated
|
||||
/// </summary>
|
||||
/// <param name="basePath">Base filename and path to use for checking</param>
|
||||
/// <returns>List of all log file paths, empty otherwise</returns>
|
||||
public abstract List<string> GetLogFilePaths(string basePath);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Virtual Methods
|
||||
|
||||
/// <summary>
|
||||
/// Generate a list of all deleteable files generated
|
||||
/// </summary>
|
||||
/// <param name="basePath">Base filename and path to use for checking</param>
|
||||
/// <returns>List of all deleteable file paths, empty otherwise</returns>
|
||||
public virtual List<string> GetDeleteableFilePaths(string basePath) => [];
|
||||
|
||||
#endregion
|
||||
|
||||
#region Shared Methods
|
||||
|
||||
/// <summary>
|
||||
/// Compress log files to save space
|
||||
/// </summary>
|
||||
/// <param name="outputDirectory">Output folder to write to</param>
|
||||
/// <param name="filenameSuffix">Output filename to use as the base path</param>
|
||||
/// <param name="outputFilename">Output filename to use as the base path</param>
|
||||
/// <param name="processor">Processor object representing how to process the outputs</param>
|
||||
/// <returns>True if the process succeeded, false otherwise</returns>
|
||||
public (bool, string) CompressLogFiles(string? outputDirectory, string? filenameSuffix, string outputFilename)
|
||||
{
|
||||
#if NET20 || NET35 || NET40
|
||||
return (false, "Log compression is not available for this framework version");
|
||||
#else
|
||||
|
||||
// Prepare the necessary paths
|
||||
outputFilename = Path.GetFileNameWithoutExtension(outputFilename);
|
||||
string combinedBase;
|
||||
if (string.IsNullOrEmpty(outputDirectory))
|
||||
combinedBase = outputFilename;
|
||||
else
|
||||
combinedBase = Path.Combine(outputDirectory, outputFilename);
|
||||
|
||||
string archiveName = combinedBase + "_logs.zip";
|
||||
|
||||
// Get the list of log files from the parameters object
|
||||
var files = GetLogFilePaths(combinedBase);
|
||||
|
||||
// Add on generated log files if they exist
|
||||
var mpfFiles = GetGeneratedFilePaths(outputDirectory, filenameSuffix);
|
||||
files.AddRange(mpfFiles);
|
||||
|
||||
if (!files.Any())
|
||||
return (true, "No files to compress!");
|
||||
|
||||
// If the file already exists, we want to delete the old one
|
||||
try
|
||||
{
|
||||
if (File.Exists(archiveName))
|
||||
File.Delete(archiveName);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return (false, "Could not delete old archive!");
|
||||
}
|
||||
|
||||
// Add the log files to the archive and delete the uncompressed file after
|
||||
ZipArchive? zf = null;
|
||||
try
|
||||
{
|
||||
zf = ZipFile.Open(archiveName, ZipArchiveMode.Create);
|
||||
foreach (string file in files)
|
||||
{
|
||||
if (string.IsNullOrEmpty(outputDirectory))
|
||||
{
|
||||
zf.CreateEntryFromFile(file, file, CompressionLevel.Optimal);
|
||||
}
|
||||
else
|
||||
{
|
||||
string entryName = file[outputDirectory!.Length..].TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
|
||||
|
||||
#if NETFRAMEWORK || NETCOREAPP3_1 || NET5_0
|
||||
zf.CreateEntryFromFile(file, entryName, CompressionLevel.Optimal);
|
||||
#else
|
||||
zf.CreateEntryFromFile(file, entryName, CompressionLevel.SmallestSize);
|
||||
#endif
|
||||
}
|
||||
|
||||
// If the file is MPF-specific, don't delete
|
||||
if (mpfFiles.Contains(file))
|
||||
continue;
|
||||
|
||||
try
|
||||
{
|
||||
File.Delete(file);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
return (true, "Compression complete!");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return (false, $"Compression could not complete: {ex}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
zf?.Dispose();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compress log files to save space
|
||||
/// </summary>
|
||||
/// <param name="outputDirectory">Output folder to write to</param>
|
||||
/// <param name="outputFilename">Output filename to use as the base path</param>
|
||||
/// <param name="processor">Processor object representing how to process the outputs</param>
|
||||
/// <returns>True if the process succeeded, false otherwise</returns>
|
||||
public (bool, string) DeleteUnnecessaryFiles(string? outputDirectory, string outputFilename)
|
||||
{
|
||||
// Prepare the necessary paths
|
||||
outputFilename = Path.GetFileNameWithoutExtension(outputFilename);
|
||||
string combinedBase;
|
||||
if (string.IsNullOrEmpty(outputDirectory))
|
||||
combinedBase = outputFilename;
|
||||
else
|
||||
combinedBase = Path.Combine(outputDirectory, outputFilename);
|
||||
|
||||
// Get the list of deleteable files from the parameters object
|
||||
var files = GetDeleteableFilePaths(combinedBase);
|
||||
|
||||
if (!files.Any())
|
||||
return (true, "No files to delete!");
|
||||
|
||||
// Attempt to delete all of the files
|
||||
try
|
||||
{
|
||||
foreach (string file in files)
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Delete(file);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
return (true, "Deletion complete!");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return (false, $"Deletion could not complete: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that all required output files have been created
|
||||
/// </summary>
|
||||
/// <param name="outputDirectory">Output folder to write to</param>
|
||||
/// <param name="outputFilename">Output filename to use as the base path</param>
|
||||
/// <param name="processor">Processor object representing how to process the outputs</param>
|
||||
/// <param name="preCheck">True if this is a check done before a dump, false if done after</param>
|
||||
/// <returns>Tuple of true if all required files exist, false otherwise and a list representing missing files</returns>
|
||||
public (bool, List<string>) FoundAllFiles(string? outputDirectory, string outputFilename, bool preCheck)
|
||||
{
|
||||
// First, sanitized the output filename to strip off any potential extension
|
||||
outputFilename = Path.GetFileNameWithoutExtension(outputFilename);
|
||||
|
||||
// Then get the base path for all checking
|
||||
string basePath;
|
||||
if (string.IsNullOrEmpty(outputDirectory))
|
||||
basePath = outputFilename;
|
||||
else
|
||||
basePath = Path.Combine(outputDirectory, outputFilename);
|
||||
|
||||
// Finally, let the parameters say if all files exist
|
||||
return CheckAllOutputFilesExist(basePath, preCheck);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the hex contents of the PIC file
|
||||
/// </summary>
|
||||
/// <param name="picPath">Path to the PIC.bin file associated with the dump</param>
|
||||
/// <param name="trimLength">Number of characters to trim the PIC to, if -1, ignored</param>
|
||||
/// <returns>PIC data as a hex string if possible, null on error</returns>
|
||||
/// <remarks>https://stackoverflow.com/questions/9932096/add-separator-to-string-at-every-n-characters</remarks>
|
||||
protected static string? GetPIC(string picPath, int trimLength = -1)
|
||||
{
|
||||
// If the file doesn't exist, we can't get the info
|
||||
if (!File.Exists(picPath))
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
var hex = ProcessingTool.GetFullFile(picPath, true);
|
||||
if (hex == null)
|
||||
return null;
|
||||
|
||||
if (trimLength > -1)
|
||||
hex = hex.Substring(0, trimLength);
|
||||
|
||||
// TODO: Check for non-zero values in discarded PIC
|
||||
|
||||
return Regex.Replace(hex, ".{32}", "$0\n", RegexOptions.Compiled);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// We don't care what the error was right now
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a isobuster-formatted PVD from a 2048 byte-per-sector image, if possible
|
||||
/// </summary>
|
||||
/// <param name="isoPath">Path to ISO file</param>
|
||||
/// <param name="pvd">Formatted PVD string, otherwise null</param>
|
||||
/// <returns>True if PVD was successfully parsed, otherwise false</returns>
|
||||
protected static bool GetPVD(string isoPath, out string? pvd)
|
||||
{
|
||||
pvd = null;
|
||||
try
|
||||
{
|
||||
// Get PVD bytes from ISO file
|
||||
var buf = new byte[96];
|
||||
using (FileStream iso = File.OpenRead(isoPath))
|
||||
{
|
||||
// TODO: Don't hardcode 0x8320
|
||||
iso.Seek(0x8320, SeekOrigin.Begin);
|
||||
|
||||
int offset = 0;
|
||||
while (offset < 96)
|
||||
{
|
||||
int read = iso.Read(buf, offset, buf.Length - offset);
|
||||
if (read == 0)
|
||||
throw new EndOfStreamException();
|
||||
offset += read;
|
||||
}
|
||||
}
|
||||
|
||||
// Format PVD to isobuster standard
|
||||
char[] pvdCharArray = new char[96];
|
||||
for (int i = 0; i < 96; i++)
|
||||
{
|
||||
if (buf[i] >= 0x20 && buf[i] <= 0x7E)
|
||||
pvdCharArray[i] = (char)buf[i];
|
||||
else
|
||||
pvdCharArray[i] = '.';
|
||||
}
|
||||
string pvdASCII = new string(pvdCharArray, 0, 96);
|
||||
pvd = string.Empty;
|
||||
for (int i = 0; i < 96; i += 16)
|
||||
{
|
||||
pvd += $"{(0x0320 + i):X4} : {buf[i]:X2} {buf[i + 1]:X2} {buf[i + 2]:X2} {buf[i + 3]:X2} {buf[i + 4]:X2} {buf[i + 5]:X2} {buf[i + 6]:X2} {buf[i + 7]:X2} " +
|
||||
$"{buf[i + 8]:X2} {buf[i + 9]:X2} {buf[i + 10]:X2} {buf[i + 11]:X2} {buf[i + 12]:X2} {buf[i + 13]:X2} {buf[i + 14]:X2} {buf[i + 15]:X2} {pvdASCII.Substring(i, 16)}\n";
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// We don't care what the error is
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a list of all MPF-specific log files generated
|
||||
/// </summary>
|
||||
/// <param name="outputDirectory">Output folder to write to</param>
|
||||
/// <param name="filenameSuffix">Optional suffix to append to the filename</param>
|
||||
/// <returns>List of all log file paths, empty otherwise</returns>
|
||||
private static List<string> GetGeneratedFilePaths(string? outputDirectory, string? filenameSuffix)
|
||||
{
|
||||
var files = new List<string>();
|
||||
|
||||
if (string.IsNullOrEmpty(outputDirectory) && string.IsNullOrEmpty(filenameSuffix))
|
||||
{
|
||||
if (File.Exists("!submissionInfo.txt"))
|
||||
files.Add("!submissionInfo.txt");
|
||||
if (File.Exists("!submissionInfo.json"))
|
||||
files.Add("!submissionInfo.json");
|
||||
if (File.Exists("!submissionInfo.json.gz"))
|
||||
files.Add("!submissionInfo.json.gz");
|
||||
if (File.Exists("!protectionInfo.txt"))
|
||||
files.Add("!protectionInfo.txt");
|
||||
}
|
||||
else if (string.IsNullOrEmpty(outputDirectory) && !string.IsNullOrEmpty(filenameSuffix))
|
||||
{
|
||||
if (File.Exists($"!submissionInfo_{filenameSuffix}.txt"))
|
||||
files.Add($"!submissionInfo_{filenameSuffix}.txt");
|
||||
if (File.Exists($"!submissionInfo_{filenameSuffix}.json"))
|
||||
files.Add($"!submissionInfo_{filenameSuffix}.json");
|
||||
if (File.Exists($"!submissionInfo_{filenameSuffix}.json.gz"))
|
||||
files.Add($"!submissionInfo_{filenameSuffix}.json.gz");
|
||||
if (File.Exists($"!protectionInfo_{filenameSuffix}.txt"))
|
||||
files.Add($"!protectionInfo_{filenameSuffix}.txt");
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(outputDirectory) && string.IsNullOrEmpty(filenameSuffix))
|
||||
{
|
||||
if (File.Exists(Path.Combine(outputDirectory, "!submissionInfo.txt")))
|
||||
files.Add(Path.Combine(outputDirectory, "!submissionInfo.txt"));
|
||||
if (File.Exists(Path.Combine(outputDirectory, "!submissionInfo.json")))
|
||||
files.Add(Path.Combine(outputDirectory, "!submissionInfo.json"));
|
||||
if (File.Exists(Path.Combine(outputDirectory, "!submissionInfo.json.gz")))
|
||||
files.Add(Path.Combine(outputDirectory, "!submissionInfo.json.gz"));
|
||||
if (File.Exists(Path.Combine(outputDirectory, "!protectionInfo.txt")))
|
||||
files.Add(Path.Combine(outputDirectory, "!protectionInfo.txt"));
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(outputDirectory) && !string.IsNullOrEmpty(filenameSuffix))
|
||||
{
|
||||
if (File.Exists(Path.Combine(outputDirectory, $"!submissionInfo_{filenameSuffix}.txt")))
|
||||
files.Add(Path.Combine(outputDirectory, $"!submissionInfo_{filenameSuffix}.txt"));
|
||||
if (File.Exists(Path.Combine(outputDirectory, $"!submissionInfo_{filenameSuffix}.json")))
|
||||
files.Add(Path.Combine(outputDirectory, $"!submissionInfo_{filenameSuffix}.json"));
|
||||
if (File.Exists(Path.Combine(outputDirectory, $"!submissionInfo_{filenameSuffix}.json.gz")))
|
||||
files.Add(Path.Combine(outputDirectory, $"!submissionInfo_{filenameSuffix}.json.gz"));
|
||||
if (File.Exists(Path.Combine(outputDirectory, $"!protectionInfo_{filenameSuffix}.txt")))
|
||||
files.Add(Path.Combine(outputDirectory, $"!protectionInfo_{filenameSuffix}.txt"));
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
334
MPF.Processors/CleanRip.cs
Normal file
334
MPF.Processors/CleanRip.cs
Normal file
@@ -0,0 +1,334 @@
|
||||
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 CleanRip outputs
|
||||
/// </summary>
|
||||
public sealed class CleanRip : BaseProcessor
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public CleanRip(RedumpSystem? system, MediaType? type) : base(system, type) { }
|
||||
|
||||
#region BaseProcessor Implementations
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override (bool, List<string>) CheckAllOutputFilesExist(string basePath, bool preCheck)
|
||||
{
|
||||
var missingFiles = new List<string>();
|
||||
switch (Type)
|
||||
{
|
||||
case MediaType.DVD: // Only added here to help users; not strictly correct
|
||||
case MediaType.NintendoGameCubeGameDisc:
|
||||
case MediaType.NintendoWiiOpticalDisc:
|
||||
if (!File.Exists($"{basePath}_logs.zip") || !preCheck)
|
||||
{
|
||||
if (!File.Exists($"{basePath}-dumpinfo.txt"))
|
||||
missingFiles.Add($"{basePath}-dumpinfo.txt");
|
||||
if (!File.Exists($"{basePath}.bca"))
|
||||
missingFiles.Add($"{basePath}.bca");
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
missingFiles.Add("Media and system combination not supported for CleanRip");
|
||||
break;
|
||||
}
|
||||
|
||||
return (!missingFiles.Any(), missingFiles);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void GenerateArtifacts(SubmissionInfo info, string basePath)
|
||||
{
|
||||
info.Artifacts ??= [];
|
||||
|
||||
if (File.Exists(basePath + ".bca"))
|
||||
info.Artifacts["bca"] = ProcessingTool.GetBase64(ProcessingTool.GetFullFile(basePath + ".bca", binary: true)) ?? string.Empty;
|
||||
if (File.Exists(basePath + "-dumpinfo.txt"))
|
||||
info.Artifacts["dumpinfo"] = ProcessingTool.GetBase64(ProcessingTool.GetFullFile(basePath + "-dumpinfo.txt")) ?? string.Empty;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void GenerateSubmissionInfo(SubmissionInfo info, string basePath, bool redumpCompat)
|
||||
{
|
||||
// Ensure that required sections exist
|
||||
info = Builder.EnsureAllSections(info);
|
||||
|
||||
// TODO: Determine if there's a CleanRip version anywhere
|
||||
info.DumpingInfo!.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/>
|
||||
public override List<string> GetLogFilePaths(string basePath)
|
||||
{
|
||||
var logFiles = new List<string>();
|
||||
switch (Type)
|
||||
{
|
||||
case MediaType.DVD: // Only added here to help users; not strictly correct
|
||||
case MediaType.NintendoGameCubeGameDisc:
|
||||
case MediaType.NintendoWiiOpticalDisc:
|
||||
if (File.Exists($"{basePath}-dumpinfo.txt"))
|
||||
logFiles.Add($"{basePath}-dumpinfo.txt");
|
||||
if (File.Exists($"{basePath}.bca"))
|
||||
logFiles.Add($"{basePath}.bca");
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return logFiles;
|
||||
}
|
||||
|
||||
#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;
|
||||
|
||||
return Regex.Replace(hex, ".{32}", "$0\n");
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
2283
MPF.Processors/DiscImageCreator.cs
Normal file
2283
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
67
MPF.Processors/MPF.Processors.csproj
Normal file
67
MPF.Processors/MPF.Processors.csproj
Normal file
@@ -0,0 +1,67 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<!-- Assembly Properties -->
|
||||
<TargetFrameworks>net20;net35;net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0</TargetFrameworks>
|
||||
<CheckEolTargetFramework>false</CheckEolTargetFramework>
|
||||
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<VersionPrefix>3.2.1</VersionPrefix>
|
||||
<WarningsNotAsErrors>NU5104</WarningsNotAsErrors>
|
||||
|
||||
<!-- Package Properties -->
|
||||
<Authors>Matt Nadareski;ReignStumble;Jakz</Authors>
|
||||
<Description>Common code for all MPF implementations</Description>
|
||||
<Copyright>Copyright (c) Matt Nadareski 2019-2024</Copyright>
|
||||
<PackageProjectUrl>https://github.com/SabreTools/</PackageProjectUrl>
|
||||
<RepositoryUrl>https://github.com/SabreTools/MPF</RepositoryUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<InternalsVisibleTo Include="MPF.Test" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Support All Frameworks -->
|
||||
<PropertyGroup Condition="$(TargetFramework.StartsWith(`net2`)) OR $(TargetFramework.StartsWith(`net3`)) OR $(TargetFramework.StartsWith(`net4`))">
|
||||
<RuntimeIdentifiers>win-x86;win-x64</RuntimeIdentifiers>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="$(TargetFramework.StartsWith(`netcoreapp`)) OR $(TargetFramework.StartsWith(`net5`))">
|
||||
<RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64</RuntimeIdentifiers>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="$(TargetFramework.StartsWith(`net6`)) OR $(TargetFramework.StartsWith(`net7`)) OR $(TargetFramework.StartsWith(`net8`))">
|
||||
<RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64;osx-arm64</RuntimeIdentifiers>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="$(RuntimeIdentifier.StartsWith(`osx-arm`))">
|
||||
<TargetFrameworks>net6.0;net7.0;net8.0</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Support for old .NET versions -->
|
||||
<ItemGroup Condition="$(TargetFramework.StartsWith(`net2`)) OR $(TargetFramework.StartsWith(`net3`)) OR $(TargetFramework.StartsWith(`net40`))">
|
||||
<PackageReference Include="MinAsyncBridge" Version="0.12.4" />
|
||||
<PackageReference Include="MinTasksExtensionsBridge" Version="0.3.4" />
|
||||
<PackageReference Include="MinThreadingBridge" Version="0.11.4" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="!$(TargetFramework.StartsWith(`net2`)) AND !$(TargetFramework.StartsWith(`net3`)) AND !$(TargetFramework.StartsWith(`net40`))">
|
||||
<PackageReference Include="System.IO.Compression" Version="4.3.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="$(TargetFramework.StartsWith(`net4`)) AND !$(TargetFramework.StartsWith(`net40`))">
|
||||
<PackageReference Include="IndexRange" Version="1.0.3" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="!$(TargetFramework.StartsWith(`net2`)) AND !$(TargetFramework.StartsWith(`net3`)) AND !$(TargetFramework.StartsWith(`net40`)) AND !$(TargetFramework.StartsWith(`net452`))">
|
||||
<PackageReference Include="System.IO.Compression.ZipFile" Version="4.3.0" />
|
||||
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="6.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="psxt001z.Library" Version="0.21.0-rc1" />
|
||||
<PackageReference Include="SabreTools.Hashing" Version="1.2.0" />
|
||||
<PackageReference Include="SabreTools.Models" Version="1.4.8" />
|
||||
<PackageReference Include="SabreTools.RedumpLib" Version="1.4.1" />
|
||||
<PackageReference Include="SabreTools.Serialization" Version="1.6.5" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
192
MPF.Processors/PS3CFW.cs
Normal file
192
MPF.Processors/PS3CFW.cs
Normal file
@@ -0,0 +1,192 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
using SabreTools.Hashing;
|
||||
using SabreTools.Models.Logiqx;
|
||||
using SabreTools.RedumpLib;
|
||||
using SabreTools.RedumpLib.Data;
|
||||
|
||||
namespace MPF.Processors
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents processing PlayStation 3 Custom Firmware outputs
|
||||
/// </summary>
|
||||
public sealed class PS3CFW : BaseProcessor
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public PS3CFW(RedumpSystem? system, MediaType? type) : base(system, type) { }
|
||||
|
||||
#region BaseProcessor Implementations
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override (bool, List<string>) CheckAllOutputFilesExist(string basePath, bool preCheck)
|
||||
{
|
||||
var missingFiles = new List<string>();
|
||||
|
||||
if (Type != MediaType.BluRay || System != RedumpSystem.SonyPlayStation3)
|
||||
{
|
||||
missingFiles.Add("Media and system combination not supported for PS3 CFW");
|
||||
}
|
||||
else
|
||||
{
|
||||
string? getKeyBasePath = GetCFWBasePath(basePath);
|
||||
if (!File.Exists($"{getKeyBasePath}.getkey.log"))
|
||||
missingFiles.Add($"{getKeyBasePath}.getkey.log");
|
||||
if (!File.Exists($"{getKeyBasePath}.disc.pic"))
|
||||
missingFiles.Add($"{getKeyBasePath}.disc.pic");
|
||||
}
|
||||
|
||||
return (missingFiles.Count == 0, missingFiles);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void GenerateArtifacts(SubmissionInfo info, string basePath)
|
||||
{
|
||||
info.Artifacts ??= [];
|
||||
|
||||
string? getKeyBasePath = GetCFWBasePath(basePath);
|
||||
|
||||
if (File.Exists(getKeyBasePath + ".disc.pic"))
|
||||
info.Artifacts["discpic"] = ProcessingTool.GetBase64(ProcessingTool.GetFullFile(getKeyBasePath + ".disc.pic", binary: true)) ?? string.Empty;
|
||||
if (File.Exists(getKeyBasePath + ".getkey.log"))
|
||||
info.Artifacts["getkeylog"] = ProcessingTool.GetBase64(ProcessingTool.GetFullFile(getKeyBasePath + ".getkey.log")) ?? string.Empty;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void GenerateSubmissionInfo(SubmissionInfo info, string basePath, bool redumpCompat)
|
||||
{
|
||||
// Ensure that required sections exist
|
||||
info = Builder.EnsureAllSections(info);
|
||||
|
||||
// TODO: Determine if there's a CFW version anywhere
|
||||
info.DumpingInfo!.DumpingDate = ProcessingTool.GetFileModifiedDate(basePath + ".iso")?.ToString("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
// Get the Datafile information
|
||||
Datafile? datafile = GeneratePS3CFWDatafile(basePath + ".iso");
|
||||
|
||||
// Fill in the hash data
|
||||
info.TracksAndWriteOffsets!.ClrMameProData = ProcessingTool.GenerateDatfile(datafile);
|
||||
|
||||
// Get the individual hash data, as per internal
|
||||
if (ProcessingTool.GetISOHashValues(datafile, out long size, out var crc32, out var md5, out var sha1))
|
||||
{
|
||||
info.SizeAndChecksums!.Size = size;
|
||||
info.SizeAndChecksums.CRC32 = crc32;
|
||||
info.SizeAndChecksums.MD5 = md5;
|
||||
info.SizeAndChecksums.SHA1 = sha1;
|
||||
}
|
||||
|
||||
// Get the PVD from the ISO
|
||||
if (GetPVD(basePath + ".iso", out string? pvd))
|
||||
info.Extras!.PVD = pvd;
|
||||
|
||||
// Try to determine the name of the GetKey file(s)
|
||||
string? getKeyBasePath = GetCFWBasePath(basePath);
|
||||
|
||||
// If GenerateSubmissionInfo is run, .getkey.log existence should already be checked
|
||||
if (!File.Exists(getKeyBasePath + ".getkey.log"))
|
||||
return;
|
||||
|
||||
// Get dumping date from GetKey log date
|
||||
info.DumpingInfo.DumpingDate = ProcessingTool.GetFileModifiedDate(getKeyBasePath + ".getkey.log")?.ToString("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
// TODO: Put info about abnormal PIC info beyond 132 bytes in comments?
|
||||
if (File.Exists(getKeyBasePath + ".disc.pic"))
|
||||
info.Extras!.PIC = GetPIC(getKeyBasePath + ".disc.pic", 264);
|
||||
|
||||
// Parse Disc Key, Disc ID, and PIC from the .getkey.log file
|
||||
if (ProcessingTool.ParseGetKeyLog(getKeyBasePath + ".getkey.log", out string? key, out string? id, out string? pic))
|
||||
{
|
||||
if (key != null)
|
||||
info.Extras!.DiscKey = key.ToUpperInvariant();
|
||||
if (id != null)
|
||||
info.Extras!.DiscID = id.ToUpperInvariant().Substring(0, 24) + "XXXXXXXX";
|
||||
if (string.IsNullOrEmpty(info.Extras!.PIC) && !string.IsNullOrEmpty(pic))
|
||||
{
|
||||
pic = Regex.Replace(pic, ".{32}", "$0\n");
|
||||
info.Extras.PIC = pic;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override List<string> GetLogFilePaths(string basePath)
|
||||
{
|
||||
var logFiles = new List<string>();
|
||||
string? getKeyBasePath = GetCFWBasePath(basePath);
|
||||
|
||||
if (System != RedumpSystem.SonyPlayStation3)
|
||||
return logFiles;
|
||||
|
||||
switch (Type)
|
||||
{
|
||||
case MediaType.BluRay:
|
||||
if (File.Exists($"{getKeyBasePath}.getkey.log"))
|
||||
logFiles.Add($"{getKeyBasePath}.getkey.log");
|
||||
if (File.Exists($"{getKeyBasePath}.disc.pic"))
|
||||
logFiles.Add($"{getKeyBasePath}.disc.pic");
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return logFiles;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Information Extraction Methods
|
||||
|
||||
/// <summary>
|
||||
/// Get a formatted datfile from the PS3 CFW output, if possible
|
||||
/// </summary>
|
||||
/// <param name="iso">Path to ISO file</param>
|
||||
/// <returns></returns>
|
||||
private static Datafile? GeneratePS3CFWDatafile(string iso)
|
||||
{
|
||||
// If the ISO file doesn't exist, we can't get info from it
|
||||
if (!File.Exists(iso))
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
if (HashTool.GetStandardHashes(iso, out long size, out string? crc, out string? md5, out string? sha1))
|
||||
{
|
||||
return new Datafile
|
||||
{
|
||||
Game = [new Game { Rom = [new Rom { Name = Path.GetFileName(iso), Size = size.ToString(), CRC = crc, MD5 = md5, SHA1 = sha1 }] }]
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// We don't care what the exception is right now
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Helper Functions
|
||||
|
||||
/// <summary>
|
||||
/// Estimate the base filename of the .getkey.log file associated with the dump
|
||||
/// </summary>
|
||||
/// <param name="iso">Path to ISO file</param>
|
||||
/// <returns>Base filename, null if not found</returns>
|
||||
private string? GetCFWBasePath(string iso)
|
||||
{
|
||||
string? dir = Path.GetDirectoryName(iso);
|
||||
dir ??= ".";
|
||||
|
||||
string[] files = Directory.GetFiles(dir, "*.getkey.log");
|
||||
|
||||
if (files.Length != 1)
|
||||
return null;
|
||||
|
||||
return files[0].Substring(0, files[0].Length - 11);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
1216
MPF.Processors/ProcessingTool.cs
Normal file
1216
MPF.Processors/ProcessingTool.cs
Normal file
File diff suppressed because it is too large
Load Diff
1678
MPF.Processors/Redumper.cs
Normal file
1678
MPF.Processors/Redumper.cs
Normal file
File diff suppressed because it is too large
Load Diff
320
MPF.Processors/UmdImageCreator.cs
Normal file
320
MPF.Processors/UmdImageCreator.cs
Normal file
@@ -0,0 +1,320 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using SabreTools.Hashing;
|
||||
using SabreTools.Models.Logiqx;
|
||||
using SabreTools.RedumpLib;
|
||||
using SabreTools.RedumpLib.Data;
|
||||
|
||||
namespace MPF.Processors
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents processing UmdImageCreator outputs
|
||||
/// </summary>
|
||||
public sealed class UmdImageCreator : BaseProcessor
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public UmdImageCreator(RedumpSystem? system, MediaType? type) : base(system, type) { }
|
||||
|
||||
#region BaseProcessor Implementations
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override (bool, List<string>) CheckAllOutputFilesExist(string basePath, bool preCheck)
|
||||
{
|
||||
var missingFiles = new List<string>();
|
||||
switch (Type)
|
||||
{
|
||||
case MediaType.UMD:
|
||||
if (!File.Exists($"{basePath}_logs.zip") || !preCheck)
|
||||
{
|
||||
if (!File.Exists($"{basePath}_disc.txt"))
|
||||
missingFiles.Add($"{basePath}_disc.txt");
|
||||
if (!File.Exists($"{basePath}_mainError.txt"))
|
||||
missingFiles.Add($"{basePath}_mainError.txt");
|
||||
if (!File.Exists($"{basePath}_mainInfo.txt"))
|
||||
missingFiles.Add($"{basePath}_mainInfo.txt");
|
||||
if (!File.Exists($"{basePath}_volDesc.txt"))
|
||||
missingFiles.Add($"{basePath}_volDesc.txt");
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
missingFiles.Add("Media and system combination not supported for UmdImageCreator");
|
||||
break;
|
||||
}
|
||||
|
||||
return (!missingFiles.Any(), missingFiles);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void GenerateArtifacts(SubmissionInfo info, string basePath)
|
||||
{
|
||||
info.Artifacts ??= [];
|
||||
|
||||
if (File.Exists($"{basePath}_disc.txt"))
|
||||
info.Artifacts["disc"] = ProcessingTool.GetBase64(ProcessingTool.GetFullFile($"{basePath}_disc.txt")) ?? string.Empty;
|
||||
if (File.Exists($"{basePath}_drive.txt"))
|
||||
info.Artifacts["drive"] = ProcessingTool.GetBase64(ProcessingTool.GetFullFile($"{basePath}_drive.txt")) ?? string.Empty;
|
||||
if (File.Exists($"{basePath}_mainError.txt"))
|
||||
info.Artifacts["mainError"] = ProcessingTool.GetBase64(ProcessingTool.GetFullFile($"{basePath}_mainError.txt")) ?? string.Empty;
|
||||
if (File.Exists($"{basePath}_mainInfo.txt"))
|
||||
info.Artifacts["mainInfo"] = ProcessingTool.GetBase64(ProcessingTool.GetFullFile($"{basePath}_mainInfo.txt")) ?? string.Empty;
|
||||
//if (File.Exists($"{basePath}_PFI.bin"))
|
||||
// info.Artifacts["pfi"] = Convert.ToBase64String(File.ReadAllBytes($"{basePath}_PFI.bin")) ?? string.Empty;
|
||||
if (File.Exists($"{basePath}_volDesc.txt"))
|
||||
info.Artifacts["volDesc"] = ProcessingTool.GetBase64(ProcessingTool.GetFullFile($"{basePath}_volDesc.txt")) ?? string.Empty;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void GenerateSubmissionInfo(SubmissionInfo info, string basePath, bool redumpCompat)
|
||||
{
|
||||
// Ensure that required sections exist
|
||||
info = Builder.EnsureAllSections(info);
|
||||
|
||||
// TODO: Determine if there's a UMDImageCreator version anywhere
|
||||
info.DumpingInfo!.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? umdcat, out var umdversion, out var umdlayer, out long umdsize))
|
||||
{
|
||||
info.CommonDiscInfo!.Title = title ?? string.Empty;
|
||||
info.CommonDiscInfo.Category = umdcat ?? DiscCategory.Games;
|
||||
info.VersionAndEditions!.Version = umdversion ?? string.Empty;
|
||||
info.SizeAndChecksums!.Size = umdsize;
|
||||
|
||||
if (!string.IsNullOrEmpty(umdlayer))
|
||||
info.SizeAndChecksums.Layerbreak = Int64.Parse(umdlayer ?? "-1");
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override List<string> GetLogFilePaths(string basePath)
|
||||
{
|
||||
var logFiles = new List<string>();
|
||||
switch (Type)
|
||||
{
|
||||
case MediaType.UMD:
|
||||
if (File.Exists($"{basePath}_disc.txt"))
|
||||
logFiles.Add($"{basePath}_disc.txt");
|
||||
if (File.Exists($"{basePath}_drive.txt"))
|
||||
logFiles.Add($"{basePath}_drive.txt");
|
||||
if (File.Exists($"{basePath}_mainError.txt"))
|
||||
logFiles.Add($"{basePath}_mainError.txt");
|
||||
if (File.Exists($"{basePath}_mainInfo.txt"))
|
||||
logFiles.Add($"{basePath}_mainInfo.txt");
|
||||
if (File.Exists($"{basePath}_volDesc.txt"))
|
||||
logFiles.Add($"{basePath}_volDesc.txt");
|
||||
|
||||
if (File.Exists($"{basePath}_PFI.bin"))
|
||||
logFiles.Add($"{basePath}_PFI.bin");
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return logFiles;
|
||||
}
|
||||
|
||||
#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? umdcat, out string? umdversion, out string? umdlayer, out long umdsize)
|
||||
{
|
||||
title = null; umdcat = null; umdversion = null; umdlayer = null; umdsize = -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.Substring("TITLE: ".Length);
|
||||
else if (line.StartsWith("DISC_VERSION") && umdversion == null)
|
||||
umdversion = line.Split(' ')[1];
|
||||
else if (line.StartsWith("pspUmdTypes"))
|
||||
umdcat = ProcessingTool.GetUMDCategory(line.Split(' ')[1]);
|
||||
else if (line.StartsWith("L0 length"))
|
||||
umdlayer = line.Split(' ')[2];
|
||||
else if (line.StartsWith("FileSize:"))
|
||||
umdsize = Int64.Parse(line.Split(' ')[1]);
|
||||
}
|
||||
|
||||
// If the L0 length is the size of the full disc, there's no layerbreak
|
||||
if (Int64.TryParse(umdlayer, out long umdlayerValue) && umdlayerValue * 2048 == umdsize)
|
||||
umdlayer = null;
|
||||
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// We don't care what the exception is right now
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <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
|
||||
}
|
||||
}
|
||||
666
MPF.Processors/XboxBackupCreator.cs
Normal file
666
MPF.Processors/XboxBackupCreator.cs
Normal file
@@ -0,0 +1,666 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using SabreTools.Hashing;
|
||||
using SabreTools.Models.Logiqx;
|
||||
using SabreTools.RedumpLib;
|
||||
using SabreTools.RedumpLib.Data;
|
||||
|
||||
namespace MPF.Processors
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents processing Xbox Backup Creator outputs
|
||||
/// </summary>
|
||||
public sealed class XboxBackupCreator : BaseProcessor
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public XboxBackupCreator(RedumpSystem? system, MediaType? type) : base(system, type) { }
|
||||
|
||||
#region BaseProcessor Implementations
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override (bool, List<string>) CheckAllOutputFilesExist(string basePath, bool preCheck)
|
||||
{
|
||||
var missingFiles = new List<string>();
|
||||
switch (Type)
|
||||
{
|
||||
case MediaType.DVD:
|
||||
if (!File.Exists($"{basePath}_logs.zip") || !preCheck)
|
||||
{
|
||||
string baseDir = Path.GetDirectoryName(basePath) + Path.DirectorySeparatorChar;
|
||||
string? logPath = GetLogName(baseDir);
|
||||
if (string.IsNullOrEmpty(logPath))
|
||||
missingFiles.Add($"{baseDir}Log.txt");
|
||||
if (!File.Exists($"{baseDir}DMI.bin"))
|
||||
missingFiles.Add($"{baseDir}DMI.bin");
|
||||
if (!File.Exists($"{baseDir}PFI.bin"))
|
||||
missingFiles.Add($"{baseDir}PFI.bin");
|
||||
if (!File.Exists($"{baseDir}SS.bin"))
|
||||
missingFiles.Add($"{baseDir}SS.bin");
|
||||
|
||||
// Not required from XBC
|
||||
//if (!File.Exists($"{basePath}.dvd"))
|
||||
// missingFiles.Add($"{basePath}.dvd");
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
missingFiles.Add("Media and system combination not supported for XboxBackupCreator");
|
||||
break;
|
||||
}
|
||||
|
||||
return (!missingFiles.Any(), missingFiles);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void GenerateArtifacts(SubmissionInfo info, string basePath)
|
||||
{
|
||||
info.Artifacts ??= [];
|
||||
|
||||
string baseDir = Path.GetDirectoryName(basePath) + Path.DirectorySeparatorChar;
|
||||
string? logPath = GetLogName(baseDir);
|
||||
|
||||
if (File.Exists(logPath))
|
||||
info.Artifacts["log"] = ProcessingTool.GetBase64(ProcessingTool.GetFullFile(logPath!)) ?? string.Empty;
|
||||
if (File.Exists($"{basePath}.dvd"))
|
||||
info.Artifacts["dvd"] = ProcessingTool.GetBase64(ProcessingTool.GetFullFile($"{basePath}.dvd")) ?? string.Empty;
|
||||
//if (File.Exists($"{baseDir}DMI.bin"))
|
||||
// info.Artifacts["dmi"] = Convert.ToBase64String(File.ReadAllBytes($"{baseDir}DMI.bin")) ?? string.Empty;
|
||||
// TODO: Include PFI artifact only if the hash doesn't match known PFI hashes
|
||||
//if (File.Exists($"{baseDir}PFI.bin"))
|
||||
// info.Artifacts["pfi"] = Convert.ToBase64String(File.ReadAllBytes($"{baseDir}PFI.bin")) ?? string.Empty;
|
||||
//if (File.Exists($"{baseDir}SS.bin"))
|
||||
// info.Artifacts["ss"] = Convert.ToBase64String(File.ReadAllBytes($"{baseDir}SS.bin")) ?? string.Empty;
|
||||
//if (File.Exists($"{baseDir}RawSS.bin"))
|
||||
// info.Artifacts["rawss"] = Convert.ToBase64String(File.ReadAllBytes($"{baseDir}RawSS.bin")) ?? string.Empty;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void GenerateSubmissionInfo(SubmissionInfo info, string basePath, bool redumpCompat)
|
||||
{
|
||||
// Ensure that required sections exist
|
||||
info = Builder.EnsureAllSections(info);
|
||||
|
||||
// Get base directory
|
||||
string baseDir = Path.GetDirectoryName(basePath) + Path.DirectorySeparatorChar;
|
||||
|
||||
// Get log filename
|
||||
string? logPath = GetLogName(baseDir);
|
||||
if (string.IsNullOrEmpty(logPath))
|
||||
return;
|
||||
|
||||
// XBC dump info
|
||||
info.DumpingInfo!.DumpingProgram ??= string.Empty;
|
||||
info.DumpingInfo.DumpingProgram += $" {GetVersion(logPath) ?? "Unknown Version"}";
|
||||
info.DumpingInfo.DumpingDate = ProcessingTool.GetFileModifiedDate(logPath)?.ToString("yyyy-MM-dd HH:mm:ss");
|
||||
info.DumpingInfo.Model = GetDrive(logPath) ?? "Unknown Drive";
|
||||
|
||||
// Look for read errors
|
||||
if (GetReadErrors(logPath, out long readErrors))
|
||||
info.CommonDiscInfo!.ErrorsCount = readErrors == -1 ? "Error retrieving error count" : readErrors.ToString();
|
||||
|
||||
// Extract info based generically on MediaType
|
||||
switch (Type)
|
||||
{
|
||||
case MediaType.DVD:
|
||||
|
||||
// Get Layerbreak from .dvd file if possible
|
||||
if (GetLayerbreak($"{basePath}.dvd", out long layerbreak))
|
||||
info.SizeAndChecksums!.Layerbreak = layerbreak;
|
||||
|
||||
// Hash data
|
||||
if (HashTool.GetStandardHashes(basePath + ".iso", out long filesize, out var crc32, out var md5, out var sha1))
|
||||
{
|
||||
// Get the Datafile information
|
||||
var datafile = new Datafile
|
||||
{
|
||||
Game = [new Game { Rom = [new Rom { Name = string.Empty, Size = filesize.ToString(), CRC = crc32, MD5 = md5, SHA1 = sha1 }] }]
|
||||
};
|
||||
|
||||
// Fill in the hash data
|
||||
info.TracksAndWriteOffsets!.ClrMameProData = ProcessingTool.GenerateDatfile(datafile);
|
||||
|
||||
info.SizeAndChecksums!.Size = filesize;
|
||||
info.SizeAndChecksums.CRC32 = crc32;
|
||||
info.SizeAndChecksums.MD5 = md5;
|
||||
info.SizeAndChecksums.SHA1 = sha1;
|
||||
}
|
||||
|
||||
switch (System)
|
||||
{
|
||||
case RedumpSystem.MicrosoftXbox:
|
||||
|
||||
// Parse DMI.bin
|
||||
string xmidString = ProcessingTool.GetXGD1XMID($"{baseDir}DMI.bin");
|
||||
var xmid = SabreTools.Serialization.Wrappers.XMID.Create(xmidString);
|
||||
if (xmid != null)
|
||||
{
|
||||
info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.XMID] = xmidString?.TrimEnd('\0') ?? string.Empty;
|
||||
info.CommonDiscInfo.Serial = xmid.Serial ?? string.Empty;
|
||||
if (!redumpCompat)
|
||||
info.VersionAndEditions!.Version = xmid.Version ?? string.Empty;
|
||||
|
||||
info.CommonDiscInfo.Region = ProcessingTool.GetXGDRegion(xmid.Model.RegionIdentifier);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case RedumpSystem.MicrosoftXbox360:
|
||||
|
||||
// Get PVD from ISO
|
||||
if (GetPVD(basePath + ".iso", out string? pvd))
|
||||
info.Extras!.PVD = pvd;
|
||||
|
||||
// Parse Media ID
|
||||
//string? mediaID = GetMediaID(logPath);
|
||||
|
||||
// Parse DMI.bin
|
||||
string xemidString = ProcessingTool.GetXGD23XeMID($"{baseDir}DMI.bin");
|
||||
var xemid = SabreTools.Serialization.Wrappers.XeMID.Create(xemidString);
|
||||
if (xemid != null)
|
||||
{
|
||||
info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.XeMID] = xemidString?.TrimEnd('\0') ?? string.Empty;
|
||||
info.CommonDiscInfo.Serial = xemid.Serial ?? string.Empty;
|
||||
if (!redumpCompat)
|
||||
info.VersionAndEditions!.Version = xemid.Version ?? string.Empty;
|
||||
|
||||
info.CommonDiscInfo.Region = ProcessingTool.GetXGDRegion(xemid.Model.RegionIdentifier);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// Deal with SS.bin
|
||||
if (File.Exists($"{baseDir}SS.bin"))
|
||||
{
|
||||
// Save security sector ranges
|
||||
string? ranges = ProcessingTool.GetSSRanges($"{baseDir}SS.bin");
|
||||
if (!string.IsNullOrEmpty(ranges))
|
||||
info.Extras!.SecuritySectorRanges = ranges;
|
||||
|
||||
// TODO: Determine SS version?
|
||||
//info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.SSVersion] =
|
||||
|
||||
// Recreate RawSS.bin
|
||||
RecreateSS(logPath!, $"{baseDir}SS.bin", $"{baseDir}RawSS.bin");
|
||||
|
||||
// Run ss_sector_range to get repeatable SS hash
|
||||
ProcessingTool.CleanSS($"{baseDir}SS.bin", $"{baseDir}SS.bin");
|
||||
}
|
||||
|
||||
// DMI/PFI/SS CRC32 hashes
|
||||
if (File.Exists($"{baseDir}DMI.bin"))
|
||||
info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.DMIHash] = HashTool.GetFileHash($"{baseDir}DMI.bin", HashType.CRC32)?.ToUpperInvariant() ?? string.Empty;
|
||||
if (File.Exists($"{baseDir}PFI.bin"))
|
||||
info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.PFIHash] = HashTool.GetFileHash($"{baseDir}PFI.bin", HashType.CRC32)?.ToUpperInvariant() ?? string.Empty;
|
||||
if (File.Exists($"{baseDir}SS.bin"))
|
||||
info.CommonDiscInfo!.CommentsSpecialFields![SiteCode.SSHash] = HashTool.GetFileHash($"{baseDir}SS.bin", HashType.CRC32)?.ToUpperInvariant() ?? string.Empty;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override List<string> GetLogFilePaths(string basePath)
|
||||
{
|
||||
var logFiles = new List<string>();
|
||||
string baseDir = Path.GetDirectoryName(basePath) + Path.DirectorySeparatorChar;
|
||||
switch (Type)
|
||||
{
|
||||
case MediaType.DVD:
|
||||
string? logPath = GetLogName(baseDir);
|
||||
if (!string.IsNullOrEmpty(logPath))
|
||||
logFiles.Add(logPath!);
|
||||
if (File.Exists($"{basePath}.dvd"))
|
||||
logFiles.Add($"{basePath}.dvd");
|
||||
if (File.Exists($"{baseDir}DMI.bin"))
|
||||
logFiles.Add($"{baseDir}DMI.bin");
|
||||
if (File.Exists($"{baseDir}PFI.bin"))
|
||||
logFiles.Add($"{baseDir}PFI.bin");
|
||||
if (File.Exists($"{baseDir}SS.bin"))
|
||||
logFiles.Add($"{baseDir}SS.bin");
|
||||
if (File.Exists($"{baseDir}RawSS.bin"))
|
||||
logFiles.Add($"{baseDir}RawSS.bin");
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return logFiles;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Information Extraction Methods
|
||||
|
||||
/// <summary>
|
||||
/// Determines the file path of the XBC log
|
||||
/// </summary>
|
||||
/// <param name="baseDir">Base directory to search in</param>
|
||||
/// <returns>Log path if found, null otherwise</returns>
|
||||
private static string? GetLogName(string baseDir)
|
||||
{
|
||||
if (IsSuccessfulLog($"{baseDir}Log.txt"))
|
||||
return $"{baseDir}Log.txt";
|
||||
|
||||
// Search for a renamed log file (assume there is only one)
|
||||
string[] files = Directory.GetFiles(baseDir, "*.txt", SearchOption.TopDirectoryOnly);
|
||||
foreach (string file in files)
|
||||
{
|
||||
if (IsSuccessfulLog(file))
|
||||
return file;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if Log file has a successful read in it
|
||||
/// </summary>
|
||||
/// <param name="log">Path to log file</param>
|
||||
/// <returns>True if successful log found, false otherwise</returns>
|
||||
private static bool IsSuccessfulLog(string log)
|
||||
{
|
||||
if (!File.Exists(log))
|
||||
return false;
|
||||
|
||||
// Successful Example:
|
||||
// Read completed in 00:50:23
|
||||
// Failed Example:
|
||||
// Read failed
|
||||
|
||||
try
|
||||
{
|
||||
// If Version is not found, not a valid log file
|
||||
if (string.IsNullOrEmpty(GetVersion(log)))
|
||||
return false;
|
||||
|
||||
// Look for " Read completed in " in log file
|
||||
using var sr = File.OpenText(log);
|
||||
while (!sr.EndOfStream)
|
||||
{
|
||||
string? line = sr.ReadLine();
|
||||
if (line?.StartsWith(" Read completed in ") == true)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// We couldn't find a successful dump
|
||||
return false;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// We don't care what the exception is right now
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the XBC version if possible
|
||||
/// </summary>
|
||||
/// <param name="log">Path to XBC log file</param>
|
||||
/// <returns>Version if possible, null on error</returns>
|
||||
private static string? GetVersion(string? log)
|
||||
{
|
||||
if (string.IsNullOrEmpty(log) || !File.Exists(log))
|
||||
return null;
|
||||
|
||||
// Sample:
|
||||
// ====================================================================
|
||||
// Xbox Backup Creator v2.9 Build:0425 By Redline99
|
||||
//
|
||||
|
||||
try
|
||||
{
|
||||
// Assume version is appended after first mention of Xbox Backup Creator
|
||||
using var sr = File.OpenText(log);
|
||||
while (!sr.EndOfStream)
|
||||
{
|
||||
string? line = sr.ReadLine()?.Trim();
|
||||
if (line?.StartsWith("Xbox Backup Creator ") == true)
|
||||
return line.Substring("Xbox Backup Creator ".Length).Trim();
|
||||
}
|
||||
|
||||
// We couldn't detect the version
|
||||
return null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// We don't care what the exception is right now
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the drive model from the log
|
||||
/// </summary>
|
||||
/// <param name="log">Path to XBC log file</param>
|
||||
/// <returns>Drive model if found, null otherwise</returns>
|
||||
private static string? GetDrive(string? log)
|
||||
{
|
||||
if (string.IsNullOrEmpty(log) || !File.Exists(log))
|
||||
return null;
|
||||
|
||||
// Example:
|
||||
// ========================================
|
||||
// < --Security Sector Details -->
|
||||
// Source Drive: SH-D162D
|
||||
// ----------------------------------------
|
||||
|
||||
try
|
||||
{
|
||||
// Parse drive model from log file
|
||||
using var sr = File.OpenText(log);
|
||||
while (!sr.EndOfStream)
|
||||
{
|
||||
string? line = sr.ReadLine()?.Trim();
|
||||
if (line?.StartsWith("Source Drive: ") == true)
|
||||
{
|
||||
return line.Substring("Source Drive: ".Length).Trim();
|
||||
}
|
||||
}
|
||||
|
||||
// We couldn't detect the drive model
|
||||
return null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// We don't care what the exception is right now
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the Layerbreak value if possible
|
||||
/// </summary>
|
||||
/// <param name="dvd">Path to layerbreak file</param>
|
||||
/// <param name="layerbreak">Layerbreak value if found</param>
|
||||
/// <returns>True if successful, otherwise false</returns>
|
||||
/// <returns></returns>
|
||||
private static bool GetLayerbreak(string? dvd, out long layerbreak)
|
||||
{
|
||||
layerbreak = 0;
|
||||
|
||||
if (string.IsNullOrEmpty(dvd) || !File.Exists(dvd))
|
||||
return false;
|
||||
|
||||
// Example:
|
||||
// LayerBreak=1913776
|
||||
// track.iso
|
||||
|
||||
try
|
||||
{
|
||||
// Parse Layerbreak value from DVD file
|
||||
using var sr = File.OpenText(dvd);
|
||||
while (!sr.EndOfStream)
|
||||
{
|
||||
string? line = sr.ReadLine()?.Trim();
|
||||
if (line?.StartsWith("LayerBreak=") == true)
|
||||
{
|
||||
return long.TryParse(line.Substring("LayerBreak=".Length).Trim(), out layerbreak);
|
||||
}
|
||||
}
|
||||
|
||||
// We couldn't detect the Layerbreak
|
||||
return false;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// We don't care what the exception is right now
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the read error count if possible
|
||||
/// </summary>
|
||||
/// <param name="log">Path to XBC log file</param>
|
||||
/// <param name="readErrors">Read error count if found, -1 otherwise</param>
|
||||
/// <returns>True if sucessful, otherwise false</returns>
|
||||
private bool GetReadErrors(string? log, out long readErrors)
|
||||
{
|
||||
readErrors = -1;
|
||||
|
||||
if (string.IsNullOrEmpty(log) || !File.Exists(log))
|
||||
return false;
|
||||
|
||||
// TODO: Logic when more than one dump is in the logs
|
||||
|
||||
// Example: (replace [E] with drive letter)
|
||||
// Creating SplitVid backup image [E]
|
||||
// ...
|
||||
// Reading Game Partition
|
||||
// Setting read speed to 1x
|
||||
// Unrecovered read error at Partition LBA: 0
|
||||
|
||||
// Example: (replace track with base filename)
|
||||
// Creating Layer Break File
|
||||
// LayerBreak file saved as: "track.dvd"
|
||||
// A total of 1 sectors were zeroed out.
|
||||
|
||||
// Example: (for Original Xbox)
|
||||
// A total of 65,536 sectors were zeroed out.
|
||||
// A total of 31 sectors with read errors were recovered.
|
||||
|
||||
try
|
||||
{
|
||||
// Parse Layerbreak value from DVD file
|
||||
using var sr = File.OpenText(log);
|
||||
while (!sr.EndOfStream)
|
||||
{
|
||||
string? line = sr.ReadLine()?.Trim();
|
||||
if (line?.StartsWith("Creating Layer Break File") == true)
|
||||
{
|
||||
// Read error count is two lines below
|
||||
line = sr.ReadLine()?.Trim();
|
||||
line = sr.ReadLine()?.Trim();
|
||||
if (line?.StartsWith("A total of ") == true && line?.EndsWith(" sectors were zeroed out.") == true)
|
||||
{
|
||||
string? errorCount = line.Substring("A total of ".Length, line.Length - 36).Replace(",", "").Trim();
|
||||
bool success = long.TryParse(errorCount, out readErrors);
|
||||
|
||||
// Original Xbox should have 65536 read errors when dumping with XBC
|
||||
if (System == RedumpSystem.MicrosoftXbox)
|
||||
{
|
||||
if (readErrors == 65536)
|
||||
readErrors = 0;
|
||||
else if (readErrors > 65536)
|
||||
readErrors -= 65536;
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We couldn't detect the read error count
|
||||
return false;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// We don't care what the exception is right now
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get Xbox360 Media ID from XBC log file
|
||||
/// </summary>
|
||||
/// <param name="log">Path to XBC log file</param>
|
||||
/// <returns>Media ID if Log successfully parsed, null otherwise</returns>
|
||||
private string? GetMediaID(string? log)
|
||||
{
|
||||
if (string.IsNullOrEmpty(log) || !File.Exists(log))
|
||||
return null;
|
||||
|
||||
if (System == RedumpSystem.MicrosoftXbox)
|
||||
return null;
|
||||
|
||||
// Example:
|
||||
// ----------------------------------------
|
||||
// Media ID
|
||||
// A76B9983D170EFF8749A892BC-8B62A812
|
||||
// ----------------------------------------
|
||||
|
||||
try
|
||||
{
|
||||
// Parse Layerbreak value from DVD file
|
||||
using var sr = File.OpenText(log);
|
||||
while (!sr.EndOfStream)
|
||||
{
|
||||
string? line = sr.ReadLine()?.Trim();
|
||||
if (line?.StartsWith("Media ID") == true)
|
||||
{
|
||||
line = sr.ReadLine()?.Trim();
|
||||
return line?.Substring(25).Trim();
|
||||
}
|
||||
}
|
||||
|
||||
// We couldn't detect the Layerbreak
|
||||
return null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// We don't care what the exception is right now
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recreate an SS.bin file from XBC log and write it to a file
|
||||
/// </summary>
|
||||
/// <param name="log">Path to XBC log</param>
|
||||
/// <param name="cleanSS">Path to the clean SS file to read from</param>
|
||||
/// <param name="rawSS">Path to the raw SS file to write to</param>
|
||||
/// <returns>True if successful, false otherwise</returns>
|
||||
private static bool RecreateSS(string log, string cleanSS, string rawSS)
|
||||
{
|
||||
if (!File.Exists(log) || !File.Exists(cleanSS))
|
||||
return false;
|
||||
|
||||
byte[] ss = File.ReadAllBytes(cleanSS);
|
||||
if (ss.Length != 2048)
|
||||
return false;
|
||||
|
||||
if (!RecreateSS(log!, ss))
|
||||
return false;
|
||||
|
||||
File.WriteAllBytes(rawSS, ss);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recreate an SS.bin byte array from an XBC log.
|
||||
/// With help from https://github.com/hadzz/SS-Angle-Fixer/
|
||||
/// </summary>
|
||||
/// <param name="log">Path to XBC log</param>
|
||||
/// <param name="ss">Byte array of SS sector</param>
|
||||
/// <returns>True if successful, false otherwise</returns>
|
||||
private static bool RecreateSS(string log, byte[] ss)
|
||||
{
|
||||
// Log file must exist
|
||||
if (!File.Exists(log))
|
||||
return false;
|
||||
|
||||
// SS must be complete sector
|
||||
if (ss.Length != 2048)
|
||||
return false;
|
||||
|
||||
// Ignore XGD1 discs
|
||||
if (!ProcessingTool.GetXGDType(ss, out int xgdType))
|
||||
return false;
|
||||
if (xgdType == 0)
|
||||
return false;
|
||||
|
||||
// Don't recreate an already raw SS
|
||||
// (but do save to file, so return true)
|
||||
if (!ProcessingTool.IsCleanSS(ss))
|
||||
return true;
|
||||
|
||||
// Example replay table:
|
||||
/*
|
||||
----------------------------------------
|
||||
RT CID MOD DATA Drive Response
|
||||
-- -- -- ------------- -------------------
|
||||
01 14 00 033100 0340FF B7D8C32A B703590100
|
||||
03 BE 00 244530 24552F F4B9B528 BE46360500
|
||||
01 97 00 DBBAD0 DBCACF DD7787F4 484977ED00
|
||||
03 45 00 FCAF00 FCBEFF FB7A7773 AAB662FC00
|
||||
05 6B 00 033100 033E7F 0A31252A 0200000200
|
||||
07 46 00 244530 2452AF F8E77EBC 5B00005B00
|
||||
05 36 00 DBBAD0 DBC84F F5DFA735 B50000B500
|
||||
07 A1 00 FCAF00 FCBC7F 6B749DBF 0E01000E01
|
||||
E0 50 00 42F4E1 00B6F7 00000000 0000000000
|
||||
--------------------------------------------
|
||||
*/
|
||||
|
||||
try
|
||||
{
|
||||
// Parse Replay Table from log
|
||||
using var sr = File.OpenText(log);
|
||||
while (!sr.EndOfStream)
|
||||
{
|
||||
string? line = sr.ReadLine()?.Trim();
|
||||
if (line?.StartsWith("RT CID MOD DATA Drive Response") == true)
|
||||
{
|
||||
// Ignore next line
|
||||
line = sr.ReadLine()?.Trim();
|
||||
if (sr.EndOfStream)
|
||||
return false;
|
||||
|
||||
byte[][] responses = new byte[4][];
|
||||
|
||||
// Parse the nine rows from replay table
|
||||
for (int i = 0; i < 9; i++)
|
||||
{
|
||||
line = sr.ReadLine()?.Trim();
|
||||
// Validate line
|
||||
if (sr.EndOfStream || string.IsNullOrEmpty(line) || line!.Length < 44)
|
||||
return false;
|
||||
|
||||
// Save useful angle responses
|
||||
if (i >= 4 && i <= 7)
|
||||
{
|
||||
byte[]? angles = ProcessingTool.HexStringToByteArray(line!.Substring(34, 10));
|
||||
if (angles == null || angles.Length != 5)
|
||||
return false;
|
||||
responses[i - 4] = angles!;
|
||||
}
|
||||
}
|
||||
|
||||
int rtOffset = 0x204;
|
||||
if (xgdType == 3)
|
||||
rtOffset = 0x24;
|
||||
|
||||
// Replace angles
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
int offset = rtOffset + (9 * (i + 4));
|
||||
for (int j = 0; j < 5; j++)
|
||||
{
|
||||
// Ignore the middle byte
|
||||
if (j == 2)
|
||||
continue;
|
||||
|
||||
ss[offset + j] = responses[i][j];
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// We couldn't detect the replay table
|
||||
return false;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// We don't care what the exception is right now
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
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.10.0-release-24177-07" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0-release-24177-07" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="SabreTools.RedumpLib" Version="1.4.1" />
|
||||
<PackageReference Include="xunit" Version="2.8.0" />
|
||||
<PackageReference Include="xunit.abstractions" Version="2.0.3" />
|
||||
<PackageReference Include="xunit.analyzers" Version="1.13.0" />
|
||||
<PackageReference Include="xunit.assert" Version="2.8.0" />
|
||||
<PackageReference Include="xunit.core" Version="2.8.0" />
|
||||
<PackageReference Include="xunit.extensibility.core" Version="2.8.0" />
|
||||
<PackageReference Include="xunit.extensibility.execution" Version="2.8.0" />
|
||||
<PackageReference Include="xunit.runner.console" Version="2.8.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
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());
|
||||
}
|
||||
}
|
||||
50
MPF.UI/ElementConverter.cs
Normal file
50
MPF.UI/ElementConverter.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows.Data;
|
||||
using MPF.Frontend;
|
||||
using MPF.Frontend.ComboBoxItems;
|
||||
using SabreTools.RedumpLib.Data;
|
||||
using RedumperReadMethod = MPF.ExecutionContexts.Redumper.ReadMethod;
|
||||
using RedumperSectorOrder = MPF.ExecutionContexts.Redumper.SectorOrder;
|
||||
|
||||
namespace MPF.UI
|
||||
{
|
||||
internal class ElementConverter: IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
return value switch
|
||||
{
|
||||
DiscCategory discCategory => new Element<DiscCategory>(discCategory),
|
||||
InternalProgram internalProgram => new Element<InternalProgram>(internalProgram),
|
||||
MediaType mediaType => new Element<MediaType>(mediaType),
|
||||
RedumperReadMethod readMethod => new Element<RedumperReadMethod>(readMethod),
|
||||
RedumperSectorOrder sectorOrder => new Element<RedumperSectorOrder>(sectorOrder),
|
||||
RedumpSystem redumpSystem => new RedumpSystemComboBoxItem(redumpSystem),
|
||||
Region region => new Element<Region>(region),
|
||||
|
||||
// Null values are treated as a system value
|
||||
_ => new RedumpSystemComboBoxItem((RedumpSystem?)null),
|
||||
};
|
||||
}
|
||||
|
||||
public object? ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
// If it's an IElement but ends up null
|
||||
if (value is not IElement element)
|
||||
return null;
|
||||
|
||||
return element switch
|
||||
{
|
||||
Element<DiscCategory> dcElement => dcElement.Value,
|
||||
Element<InternalProgram> ipElement => ipElement.Value,
|
||||
Element<MediaType> mtElement => mtElement.Value,
|
||||
Element<RedumperReadMethod> rmElement => rmElement.Value,
|
||||
Element<RedumperSectorOrder> soElement => soElement.Value,
|
||||
RedumpSystemComboBoxItem rsElement => rsElement.Value,
|
||||
Element<Region> reValue => reValue.Value,
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
596
MPF.UI/External/WPFCustomMessageBox/CustomMessageBox.cs
vendored
Normal file
596
MPF.UI/External/WPFCustomMessageBox/CustomMessageBox.cs
vendored
Normal file
@@ -0,0 +1,596 @@
|
||||
// -----------------------------------------------------------------------
|
||||
// <copyright file="MessageBox.cs">
|
||||
// TODO: Update copyright text.
|
||||
// </copyright>
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
namespace WPFCustomMessageBox
|
||||
{
|
||||
using System;
|
||||
using System.Windows;
|
||||
|
||||
/// <summary>
|
||||
/// Displays a message box.
|
||||
/// </summary>
|
||||
public static class CustomMessageBox
|
||||
{
|
||||
/// <summary>
|
||||
/// Global parameter to enable (true) or disable (false) the removal of the title bar icon.
|
||||
/// If you are using a custom window style, the icon removal may cause issues like displaying two title bar (the default windows one and the custom one).
|
||||
/// </summary>
|
||||
private static readonly bool RemoveTitleBarIcon = true;
|
||||
|
||||
/// <summary>
|
||||
/// Displays a message box that has a message and returns a result.
|
||||
/// </summary>
|
||||
/// <param name="messageBoxText">A System.String that specifies the text to display.</param>
|
||||
/// <param name="timeout">
|
||||
/// The message box will close automatically after <paramref name="timeout"/> milliseconds.
|
||||
/// If null, the message box will act like default message box and not close automatically.
|
||||
/// </param>
|
||||
/// <param name="timeoutResult">If the message box closes automatically due to the <paramref name="timeout"/> being exceeded, this result is returned.</param>
|
||||
/// <returns>A System.Windows.MessageBoxResult value that specifies which message box button is clicked by the user.</returns>
|
||||
public static MessageBoxResult Show(string messageBoxText, int? timeout = null, MessageBoxResult timeoutResult = MessageBoxResult.None)
|
||||
{
|
||||
return ShowOKMessage(null, messageBoxText, string.Empty, null, null, null, timeout, timeoutResult);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Displays a message box that has a message and a title bar caption; and that returns a result.
|
||||
/// </summary>
|
||||
/// <param name="messageBoxText">A System.String that specifies the text to display.</param>
|
||||
/// <param name="caption">A System.String that specifies the title bar caption to display.</param>
|
||||
/// <param name="timeout">
|
||||
/// The message box will close automatically after <paramref name="timeout"/> milliseconds.
|
||||
/// If null, the message box will act like default message box and not close automatically.
|
||||
/// </param>
|
||||
/// <param name="timeoutResult">If the message box closes automatically due to the <paramref name="timeout"/> being exceeded, this result is returned.</param>
|
||||
/// <returns>A System.Windows.MessageBoxResult value that specifies which message box button is clicked by the user.</returns>
|
||||
public static MessageBoxResult Show(string messageBoxText, string caption, int? timeout = null, MessageBoxResult timeoutResult = MessageBoxResult.None)
|
||||
{
|
||||
return ShowOKMessage(null, messageBoxText, caption, null, null, null, timeout, timeoutResult);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Displays a message box in front of the specified window. The message box displays a message and returns a result.
|
||||
/// </summary>
|
||||
/// <param name="owner">A System.Windows.Window that represents the owner window of the message box.</param>
|
||||
/// <param name="messageBoxText">A System.String that specifies the text to display.</param>
|
||||
/// <param name="timeout">
|
||||
/// The message box will close automatically after <paramref name="timeout"/> milliseconds.
|
||||
/// If null, the message box will act like default message box and not close automatically.
|
||||
/// </param>
|
||||
/// <param name="timeoutResult">If the message box closes automatically due to the <paramref name="timeout"/> being exceeded, this result is returned.</param>
|
||||
/// <returns>A System.Windows.MessageBoxResult value that specifies which message box button is clicked by the user.</returns>
|
||||
public static MessageBoxResult Show(Window owner, string messageBoxText, int? timeout = null, MessageBoxResult timeoutResult = MessageBoxResult.None)
|
||||
{
|
||||
return ShowOKMessage(owner, messageBoxText, string.Empty, null, null, null, timeout, timeoutResult);
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Displays a message box in front of the specified window. The message box displays a message and title bar caption; and it returns a result.
|
||||
/// </summary>
|
||||
/// <param name="owner">A System.Windows.Window that represents the owner window of the message box.</param>
|
||||
/// <param name="messageBoxText">A System.String that specifies the text to display.</param>
|
||||
/// <param name="caption">A System.String that specifies the title bar caption to display.</param>
|
||||
/// <param name="timeout">
|
||||
/// The message box will close automatically after <paramref name="timeout"/> milliseconds.
|
||||
/// If null, the message box will act like default message box and not close automatically.
|
||||
/// </param>
|
||||
/// <param name="timeoutResult">If the message box closes automatically due to the <paramref name="timeout"/> being exceeded, this result is returned.</param>
|
||||
/// <returns>A System.Windows.MessageBoxResult value that specifies which message box button is clicked by the user.</returns>
|
||||
public static MessageBoxResult Show(Window owner, string messageBoxText, string caption, int? timeout = null, MessageBoxResult timeoutResult = MessageBoxResult.None)
|
||||
{
|
||||
return ShowOKMessage(owner, messageBoxText, caption, null, null, null, timeout, timeoutResult);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Displays a message box that has a message, title bar caption, and button; and that returns a result.
|
||||
/// </summary>
|
||||
/// <param name="messageBoxText">A System.String that specifies the text to display.</param>
|
||||
/// <param name="caption">A System.String that specifies the title bar caption to display.</param>
|
||||
/// <param name="button">A System.Windows.MessageBoxButton value that specifies which button or buttons to display.</param>
|
||||
/// <param name="timeout">
|
||||
/// The message box will close automatically after <paramref name="timeout"/> milliseconds.
|
||||
/// If null, the message box will act like default message box and not close automatically.
|
||||
/// </param>
|
||||
/// <param name="timeoutResult">If the message box closes automatically due to the <paramref name="timeout"/> being exceeded, this result is returned.</param>
|
||||
/// <returns>A System.Windows.MessageBoxResult value that specifies which message box button is clicked by the user.</returns>
|
||||
public static MessageBoxResult Show(string messageBoxText, string caption, MessageBoxButton button, int? timeout = null, MessageBoxResult timeoutResult = MessageBoxResult.None)
|
||||
{
|
||||
switch (button)
|
||||
{
|
||||
case MessageBoxButton.YesNo:
|
||||
case MessageBoxButton.YesNoCancel:
|
||||
return ShowYesNoMessage(null, messageBoxText, caption, null, null, null, null, timeout, timeoutResult);
|
||||
default:
|
||||
return ShowOKMessage(null, messageBoxText, caption, null, null, null, timeout, timeoutResult);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Displays a message box that has a message, title bar caption, and button; and that returns a result.
|
||||
/// </summary>
|
||||
/// <param name="owner">A System.Windows.Window that represents the owner window of the message box.</param>
|
||||
/// <param name="messageBoxText">A System.String that specifies the text to display.</param>
|
||||
/// <param name="caption">A System.String that specifies the title bar caption to display.</param>
|
||||
/// <param name="button">A System.Windows.MessageBoxButton value that specifies which button or buttons to display.</param>
|
||||
/// <param name="timeout">
|
||||
/// The message box will close automatically after <paramref name="timeout"/> milliseconds.
|
||||
/// If null, the message box will act like default message box and not close automatically.
|
||||
/// </param>
|
||||
/// <param name="timeoutResult">If the message box closes automatically due to the <paramref name="timeout"/> being exceeded, this result is returned.</param>
|
||||
/// <returns>A System.Windows.MessageBoxResult value that specifies which message box button is clicked by the user.</returns>
|
||||
public static MessageBoxResult Show(Window owner, string messageBoxText, string caption, MessageBoxButton button, int? timeout = null, MessageBoxResult timeoutResult = MessageBoxResult.None)
|
||||
{
|
||||
switch (button)
|
||||
{
|
||||
case MessageBoxButton.YesNo:
|
||||
case MessageBoxButton.YesNoCancel:
|
||||
return ShowYesNoMessage(owner, messageBoxText, caption, null, null, null, null, timeout, timeoutResult);
|
||||
default:
|
||||
return ShowOKMessage(owner, messageBoxText, caption, null, null, null, timeout, timeoutResult);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Displays a message box that has a message, title bar caption, button, and icon; and that returns a result.
|
||||
/// </summary>
|
||||
/// <param name="messageBoxText">A System.String that specifies the text to display.</param>
|
||||
/// <param name="caption">A System.String that specifies the title bar caption to display.</param>
|
||||
/// <param name="button">A System.Windows.MessageBoxButton value that specifies which button or buttons to display.</param>
|
||||
/// <param name="icon">A System.Windows.MessageBoxImage value that specifies the icon to display.</param>
|
||||
/// <param name="timeout">
|
||||
/// The message box will close automatically after <paramref name="timeout"/> milliseconds.
|
||||
/// If null, the message box will act like default message box and not close automatically.
|
||||
/// </param>
|
||||
/// <param name="timeoutResult">If the message box closes automatically due to the <paramref name="timeout"/> being exceeded, this result is returned.</param>
|
||||
/// <returns>A System.Windows.MessageBoxResult value that specifies which message box button is clicked by the user.</returns>
|
||||
public static MessageBoxResult Show(string messageBoxText, string caption, MessageBoxButton button, MessageBoxImage icon, int? timeout = null, MessageBoxResult timeoutResult = MessageBoxResult.None)
|
||||
{
|
||||
switch (button)
|
||||
{
|
||||
case MessageBoxButton.YesNo:
|
||||
case MessageBoxButton.YesNoCancel:
|
||||
return ShowYesNoMessage(null, messageBoxText, caption, null, null, null, icon, timeout, timeoutResult);
|
||||
default:
|
||||
return ShowOKMessage(null, messageBoxText, caption, null, null, icon, timeout, timeoutResult);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Displays a message box that has a message, title bar caption, button, and icon; and that returns a result.
|
||||
/// </summary>
|
||||
/// <param name="owner">A System.Windows.Window that represents the owner window of the message box.</param>
|
||||
/// <param name="messageBoxText">A System.String that specifies the text to display.</param>
|
||||
/// <param name="caption">A System.String that specifies the title bar caption to display.</param>
|
||||
/// <param name="button">A System.Windows.MessageBoxButton value that specifies which button or buttons to display.</param>
|
||||
/// <param name="icon">A System.Windows.MessageBoxImage value that specifies the icon to display.</param>
|
||||
/// <param name="timeout">
|
||||
/// The message box will close automatically after <paramref name="timeout"/> milliseconds.
|
||||
/// If null, the message box will act like default message box and not close automatically.
|
||||
/// </param>
|
||||
/// <param name="timeoutResult">If the message box closes automatically due to the <paramref name="timeout"/> being exceeded, this result is returned.</param>
|
||||
/// <returns>A System.Windows.MessageBoxResult value that specifies which message box button is clicked by the user.</returns>
|
||||
public static MessageBoxResult Show(Window owner, string? messageBoxText, string caption, MessageBoxButton button, MessageBoxImage icon, int? timeout = null, MessageBoxResult timeoutResult = MessageBoxResult.None)
|
||||
{
|
||||
switch (button)
|
||||
{
|
||||
case MessageBoxButton.YesNo:
|
||||
case MessageBoxButton.YesNoCancel:
|
||||
return ShowYesNoMessage(owner, messageBoxText, caption, null, null, null, icon, timeout, timeoutResult);
|
||||
default:
|
||||
return ShowOKMessage(owner, messageBoxText, caption, null, null, icon, timeout, timeoutResult);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Displays a message box that has a message, title bar caption, and OK button with a custom System.String value for the button's text; and that returns a result.
|
||||
/// </summary>
|
||||
/// <param name="messageBoxText">A System.String that specifies the text to display.</param>
|
||||
/// <param name="caption">A System.String that specifies the title bar caption to display.</param>
|
||||
/// <param name="okButtonText">A System.String that specifies the text to display within the OK button.</param>
|
||||
/// <param name="timeout">
|
||||
/// The message box will close automatically after <paramref name="timeout"/> milliseconds.
|
||||
/// If null, the message box will act like default message box and not close automatically.
|
||||
/// </param>
|
||||
/// <param name="timeoutResult">If the message box closes automatically due to the <paramref name="timeout"/> being exceeded, this result is returned.</param>
|
||||
/// <returns>A System.Windows.MessageBoxResult value that specifies which message box button is clicked by the user.</returns>
|
||||
public static MessageBoxResult ShowOK(string messageBoxText, string caption, string okButtonText, int? timeout = null, MessageBoxResult timeoutResult = MessageBoxResult.None)
|
||||
{
|
||||
return ShowOKMessage(null, messageBoxText, caption, okButtonText, null, null, timeout, timeoutResult);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Displays a message box that has a message, title bar caption, and OK button with a custom System.String value for the button's text; and that returns a result.
|
||||
/// </summary>
|
||||
/// <param name="owner">A System.Windows.Window that represents the owner window of the message box.</param>
|
||||
/// <param name="messageBoxText">A System.String that specifies the text to display.</param>
|
||||
/// <param name="caption">A System.String that specifies the title bar caption to display.</param>
|
||||
/// <param name="okButtonText">A System.String that specifies the text to display within the OK button.</param>
|
||||
/// <param name="timeout">
|
||||
/// The message box will close automatically after <paramref name="timeout"/> milliseconds.
|
||||
/// If null, the message box will act like default message box and not close automatically.
|
||||
/// </param>
|
||||
/// <param name="timeoutResult">If the message box closes automatically due to the <paramref name="timeout"/> being exceeded, this result is returned.</param>
|
||||
/// <returns>A System.Windows.MessageBoxResult value that specifies which message box button is clicked by the user.</returns>
|
||||
public static MessageBoxResult ShowOK(Window owner, string messageBoxText, string caption, string okButtonText, int? timeout = null, MessageBoxResult timeoutResult = MessageBoxResult.None)
|
||||
{
|
||||
return ShowOKMessage(owner, messageBoxText, caption, okButtonText, null, null, timeout, timeoutResult);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Displays a message box that has a message, title bar caption, OK button with a custom System.String value for the button's text, and icon; and that returns a result.
|
||||
/// </summary>
|
||||
/// <param name="messageBoxText">A System.String that specifies the text to display.</param>
|
||||
/// <param name="caption">A System.String that specifies the title bar caption to display.</param>
|
||||
/// <param name="okButtonText">A System.String that specifies the text to display within the OK button.</param>
|
||||
/// <param name="icon">A System.Windows.MessageBoxImage value that specifies the icon to display.</param>
|
||||
/// <param name="timeout">
|
||||
/// The message box will close automatically after <paramref name="timeout"/> milliseconds.
|
||||
/// If null, the message box will act like default message box and not close automatically.
|
||||
/// </param>
|
||||
/// <param name="timeoutResult">If the message box closes automatically due to the <paramref name="timeout"/> being exceeded, this result is returned.</param>
|
||||
/// <returns>A System.Windows.MessageBoxResult value that specifies which message box button is clicked by the user.</returns>
|
||||
public static MessageBoxResult ShowOK(string messageBoxText, string caption, string okButtonText, MessageBoxImage icon, int? timeout = null, MessageBoxResult timeoutResult = MessageBoxResult.None)
|
||||
{
|
||||
return ShowOKMessage(null, messageBoxText, caption, okButtonText, null, icon, timeout, timeoutResult);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Displays a message box that has a message, title bar caption, OK button with a custom System.String value for the button's text, and icon; and that returns a result.
|
||||
/// </summary>
|
||||
/// <param name="owner">A System.Windows.Window that represents the owner window of the message box.</param>
|
||||
/// <param name="messageBoxText">A System.String that specifies the text to display.</param>
|
||||
/// <param name="caption">A System.String that specifies the title bar caption to display.</param>
|
||||
/// <param name="okButtonText">A System.String that specifies the text to display within the OK button.</param>
|
||||
/// <param name="icon">A System.Windows.MessageBoxImage value that specifies the icon to display.</param>
|
||||
/// <param name="timeout">
|
||||
/// The message box will close automatically after <paramref name="timeout"/> milliseconds.
|
||||
/// If null, the message box will act like default message box and not close automatically.
|
||||
/// </param>
|
||||
/// <param name="timeoutResult">If the message box closes automatically due to the <paramref name="timeout"/> being exceeded, this result is returned.</param>
|
||||
/// <returns>A System.Windows.MessageBoxResult value that specifies which message box button is clicked by the user.</returns>
|
||||
public static MessageBoxResult ShowOK(Window owner, string messageBoxText, string caption, string okButtonText, MessageBoxImage icon, int? timeout = null, MessageBoxResult timeoutResult = MessageBoxResult.None)
|
||||
{
|
||||
return ShowOKMessage(owner, messageBoxText, caption, okButtonText, null, icon, timeout, timeoutResult);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Displays a message box that has a message, caption, and OK/Cancel buttons with custom System.String values for the buttons' text;
|
||||
/// and that returns a result.
|
||||
/// </summary>
|
||||
/// <param name="messageBoxText">A System.String that specifies the text to display.</param>
|
||||
/// <param name="caption">A System.String that specifies the title bar caption to display.</param>
|
||||
/// <param name="okButtonText">A System.String that specifies the text to display within the OK button.</param>
|
||||
/// <param name="cancelButtonText">A System.String that specifies the text to display within the Cancel button.</param>
|
||||
/// <param name="timeout">
|
||||
/// The message box will close automatically after <paramref name="timeout"/> milliseconds.
|
||||
/// If null, the message box will act like default message box and not close automatically.
|
||||
/// </param>
|
||||
/// <param name="timeoutResult">If the message box closes automatically due to the <paramref name="timeout"/> being exceeded, this result is returned.</param>
|
||||
/// <returns>A System.Windows.MessageBoxResult value that specifies which message box button is clicked by the user.</returns>
|
||||
public static MessageBoxResult ShowOKCancel(string messageBoxText, string caption, string okButtonText, string cancelButtonText, int? timeout = null, MessageBoxResult timeoutResult = MessageBoxResult.None)
|
||||
{
|
||||
return ShowOKMessage(null, messageBoxText, caption, okButtonText, cancelButtonText, null, timeout, timeoutResult);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Displays a message box that has a message, caption, and OK/Cancel buttons with custom System.String values for the buttons' text;
|
||||
/// and that returns a result.
|
||||
/// </summary>
|
||||
/// <param name="owner">A System.Windows.Window that represents the owner window of the message box.</param>
|
||||
/// <param name="messageBoxText">A System.String that specifies the text to display.</param>
|
||||
/// <param name="caption">A System.String that specifies the title bar caption to display.</param>
|
||||
/// <param name="okButtonText">A System.String that specifies the text to display within the OK button.</param>
|
||||
/// <param name="cancelButtonText">A System.String that specifies the text to display within the Cancel button.</param>
|
||||
/// <param name="timeout">
|
||||
/// The message box will close automatically after <paramref name="timeout"/> milliseconds.
|
||||
/// If null, the message box will act like default message box and not close automatically.
|
||||
/// </param>
|
||||
/// <param name="timeoutResult">If the message box closes automatically due to the <paramref name="timeout"/> being exceeded, this result is returned.</param>
|
||||
/// <returns>A System.Windows.MessageBoxResult value that specifies which message box button is clicked by the user.</returns>
|
||||
public static MessageBoxResult ShowOKCancel(Window owner, string messageBoxText, string caption, string okButtonText, string cancelButtonText, int? timeout = null, MessageBoxResult timeoutResult = MessageBoxResult.None)
|
||||
{
|
||||
return ShowOKMessage(owner, messageBoxText, caption, okButtonText, cancelButtonText, null, timeout, timeoutResult);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Displays a message box that has a message, caption, OK/Cancel buttons with custom System.String values for the buttons' text, and icon;
|
||||
/// and that returns a result.
|
||||
/// </summary>
|
||||
/// <param name="messageBoxText">A System.String that specifies the text to display.</param>
|
||||
/// <param name="caption">A System.String that specifies the title bar caption to display.</param>
|
||||
/// <param name="okButtonText">A System.String that specifies the text to display within the OK button.</param>
|
||||
/// <param name="cancelButtonText">A System.String that specifies the text to display within the Cancel button.</param>
|
||||
/// <param name="icon">A System.Windows.MessageBoxImage value that specifies the icon to display.</param>
|
||||
/// <param name="timeout">
|
||||
/// The message box will close automatically after <paramref name="timeout"/> milliseconds.
|
||||
/// If null, the message box will act like default message box and not close automatically.
|
||||
/// </param>
|
||||
/// <param name="timeoutResult">If the message box closes automatically due to the <paramref name="timeout"/> being exceeded, this result is returned.</param>
|
||||
/// <returns>A System.Windows.MessageBoxResult value that specifies which message box button is clicked by the user.</returns>
|
||||
public static MessageBoxResult ShowOKCancel(string messageBoxText, string caption, string okButtonText, string cancelButtonText, MessageBoxImage icon, int? timeout = null, MessageBoxResult timeoutResult = MessageBoxResult.None)
|
||||
{
|
||||
return ShowOKMessage(null, messageBoxText, caption, okButtonText, cancelButtonText, icon, timeout, timeoutResult);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Displays a message box that has a message, caption, OK/Cancel buttons with custom System.String values for the buttons' text, and icon;
|
||||
/// and that returns a result.
|
||||
/// </summary>
|
||||
/// <param name="owner">A System.Windows.Window that represents the owner window of the message box.</param>
|
||||
/// <param name="messageBoxText">A System.String that specifies the text to display.</param>
|
||||
/// <param name="caption">A System.String that specifies the title bar caption to display.</param>
|
||||
/// <param name="okButtonText">A System.String that specifies the text to display within the OK button.</param>
|
||||
/// <param name="cancelButtonText">A System.String that specifies the text to display within the Cancel button.</param>
|
||||
/// <param name="icon">A System.Windows.MessageBoxImage value that specifies the icon to display.</param>
|
||||
/// <param name="timeout">
|
||||
/// The message box will close automatically after <paramref name="timeout"/> milliseconds.
|
||||
/// If null, the message box will act like default message box and not close automatically.
|
||||
/// </param>
|
||||
/// <param name="timeoutResult">If the message box closes automatically due to the <paramref name="timeout"/> being exceeded, this result is returned.</param>
|
||||
/// <returns>A System.Windows.MessageBoxResult value that specifies which message box button is clicked by the user.</returns>
|
||||
public static MessageBoxResult ShowOKCancel(Window owner, string messageBoxText, string caption, string okButtonText, string cancelButtonText, MessageBoxImage icon, int? timeout = null, MessageBoxResult timeoutResult = MessageBoxResult.None)
|
||||
{
|
||||
return ShowOKMessage(owner, messageBoxText, caption, okButtonText, cancelButtonText, icon, timeout, timeoutResult);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Displays a message box that has a message, caption, and Yes/No buttons with custom System.String values for the buttons' text;
|
||||
/// and that returns a result.
|
||||
/// </summary>
|
||||
/// <param name="messageBoxText">A System.String that specifies the text to display.</param>
|
||||
/// <param name="caption">A System.String that specifies the title bar caption to display.</param>
|
||||
/// <param name="yesButtonText">A System.String that specifies the text to display within the Yes button.</param>
|
||||
/// <param name="noButtonText">A System.String that specifies the text to display within the No button.</param>
|
||||
/// <param name="timeout">
|
||||
/// The message box will close automatically after <paramref name="timeout"/> milliseconds.
|
||||
/// If null, the message box will act like default message box and not close automatically.
|
||||
/// </param>
|
||||
/// <param name="timeoutResult">If the message box closes automatically due to the <paramref name="timeout"/> being exceeded, this result is returned.</param>
|
||||
/// <returns>A System.Windows.MessageBoxResult value that specifies which message box button is clicked by the user.</returns>
|
||||
public static MessageBoxResult ShowYesNo(string messageBoxText, string caption, string yesButtonText, string noButtonText, int? timeout = null, MessageBoxResult timeoutResult = MessageBoxResult.None)
|
||||
{
|
||||
return ShowYesNoMessage(null, messageBoxText, caption, yesButtonText, noButtonText, null, null, timeout, timeoutResult);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Displays a message box that has a message, caption, and Yes/No buttons with custom System.String values for the buttons' text;
|
||||
/// and that returns a result.
|
||||
/// </summary>
|
||||
/// <param name="owner">A System.Windows.Window that represents the owner window of the message box.</param>
|
||||
/// <param name="messageBoxText">A System.String that specifies the text to display.</param>
|
||||
/// <param name="caption">A System.String that specifies the title bar caption to display.</param>
|
||||
/// <param name="yesButtonText">A System.String that specifies the text to display within the Yes button.</param>
|
||||
/// <param name="noButtonText">A System.String that specifies the text to display within the No button.</param>
|
||||
/// <param name="timeout">
|
||||
/// The message box will close automatically after <paramref name="timeout"/> milliseconds.
|
||||
/// If null, the message box will act like default message box and not close automatically.
|
||||
/// </param>
|
||||
/// <param name="timeoutResult">If the message box closes automatically due to the <paramref name="timeout"/> being exceeded, this result is returned.</param>
|
||||
/// <returns>A System.Windows.MessageBoxResult value that specifies which message box button is clicked by the user.</returns>
|
||||
public static MessageBoxResult ShowYesNo(Window owner, string messageBoxText, string caption, string yesButtonText, string noButtonText, int? timeout = null, MessageBoxResult timeoutResult = MessageBoxResult.None)
|
||||
{
|
||||
return ShowYesNoMessage(owner, messageBoxText, caption, yesButtonText, noButtonText, null, null, timeout, timeoutResult);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Displays a message box that has a message, caption, Yes/No buttons with custom System.String values for the buttons' text, and icon;
|
||||
/// and that returns a result.
|
||||
/// </summary>
|
||||
/// <param name="messageBoxText">A System.String that specifies the text to display.</param>
|
||||
/// <param name="caption">A System.String that specifies the title bar caption to display.</param>
|
||||
/// <param name="yesButtonText">A System.String that specifies the text to display within the Yes button.</param>
|
||||
/// <param name="noButtonText">A System.String that specifies the text to display within the No button.</param>
|
||||
/// <param name="icon">A System.Windows.MessageBoxImage value that specifies the icon to display.</param>
|
||||
/// <param name="timeout">
|
||||
/// The message box will close automatically after <paramref name="timeout"/> milliseconds.
|
||||
/// If null, the message box will act like default message box and not close automatically.
|
||||
/// </param>
|
||||
/// <param name="timeoutResult">If the message box closes automatically due to the <paramref name="timeout"/> being exceeded, this result is returned.</param>
|
||||
/// <returns>A System.Windows.MessageBoxResult value that specifies which message box button is clicked by the user.</returns>
|
||||
public static MessageBoxResult ShowYesNo(string messageBoxText, string caption, string yesButtonText, string noButtonText, MessageBoxImage icon, int? timeout = null, MessageBoxResult timeoutResult = MessageBoxResult.None)
|
||||
{
|
||||
return ShowYesNoMessage(null, messageBoxText, caption, yesButtonText, noButtonText, null, icon, timeout, timeoutResult);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Displays a message box that has a message, caption, Yes/No buttons with custom System.String values for the buttons' text, and icon;
|
||||
/// and that returns a result.
|
||||
/// </summary>
|
||||
/// <param name="owner">A System.Windows.Window that represents the owner window of the message box.</param>
|
||||
/// <param name="messageBoxText">A System.String that specifies the text to display.</param>
|
||||
/// <param name="caption">A System.String that specifies the title bar caption to display.</param>
|
||||
/// <param name="yesButtonText">A System.String that specifies the text to display within the Yes button.</param>
|
||||
/// <param name="noButtonText">A System.String that specifies the text to display within the No button.</param>
|
||||
/// <param name="icon">A System.Windows.MessageBoxImage value that specifies the icon to display.</param>
|
||||
/// <param name="timeout">
|
||||
/// The message box will close automatically after <paramref name="timeout"/> milliseconds.
|
||||
/// If null, the message box will act like default message box and not close automatically.
|
||||
/// </param>
|
||||
/// <param name="timeoutResult">If the message box closes automatically due to the <paramref name="timeout"/> being exceeded, this result is returned.</param>
|
||||
/// <returns>A System.Windows.MessageBoxResult value that specifies which message box button is clicked by the user.</returns>
|
||||
public static MessageBoxResult ShowYesNo(Window owner, string messageBoxText, string caption, string yesButtonText, string noButtonText, MessageBoxImage icon, int? timeout = null, MessageBoxResult timeoutResult = MessageBoxResult.None)
|
||||
{
|
||||
return ShowYesNoMessage(owner, messageBoxText, caption, yesButtonText, noButtonText, null, icon, timeout, timeoutResult);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Displays a message box that has a message, caption, and Yes/No/Cancel buttons with custom System.String values for the buttons' text;
|
||||
/// and that returns a result.
|
||||
/// </summary>
|
||||
/// <param name="messageBoxText">A System.String that specifies the text to display.</param>
|
||||
/// <param name="caption">A System.String that specifies the title bar caption to display.</param>
|
||||
/// <param name="yesButtonText">A System.String that specifies the text to display within the Yes button.</param>
|
||||
/// <param name="noButtonText">A System.String that specifies the text to display within the No button.</param>
|
||||
/// <param name="cancelButtonText">A System.String that specifies the text to display within the Cancel button.</param>
|
||||
/// <param name="timeout">
|
||||
/// The message box will close automatically after <paramref name="timeout"/> milliseconds.
|
||||
/// If null, the message box will act like default message box and not close automatically.
|
||||
/// </param>
|
||||
/// <param name="timeoutResult">If the message box closes automatically due to the <paramref name="timeout"/> being exceeded, this result is returned.</param>
|
||||
/// <returns>A System.Windows.MessageBoxResult value that specifies which message box button is clicked by the user.</returns>
|
||||
public static MessageBoxResult ShowYesNoCancel(string messageBoxText, string caption, string yesButtonText, string noButtonText, string cancelButtonText, int? timeout = null, MessageBoxResult timeoutResult = MessageBoxResult.None)
|
||||
{
|
||||
return ShowYesNoMessage(null, messageBoxText, caption, yesButtonText, noButtonText, cancelButtonText, null, timeout, timeoutResult);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Displays a message box that has a message, caption, and Yes/No/Cancel buttons with custom System.String values for the buttons' text;
|
||||
/// and that returns a result.
|
||||
/// </summary>
|
||||
/// <param name="owner">A System.Windows.Window that represents the owner window of the message box.</param>
|
||||
/// <param name="messageBoxText">A System.String that specifies the text to display.</param>
|
||||
/// <param name="caption">A System.String that specifies the title bar caption to display.</param>
|
||||
/// <param name="yesButtonText">A System.String that specifies the text to display within the Yes button.</param>
|
||||
/// <param name="noButtonText">A System.String that specifies the text to display within the No button.</param>
|
||||
/// <param name="cancelButtonText">A System.String that specifies the text to display within the Cancel button.</param>
|
||||
/// <param name="timeout">
|
||||
/// The message box will close automatically after <paramref name="timeout"/> milliseconds.
|
||||
/// If null, the message box will act like default message box and not close automatically.
|
||||
/// </param>
|
||||
/// <param name="timeoutResult">If the message box closes automatically due to the <paramref name="timeout"/> being exceeded, this result is returned.</param>
|
||||
/// <returns>A System.Windows.MessageBoxResult value that specifies which message box button is clicked by the user.</returns>
|
||||
public static MessageBoxResult ShowYesNoCancel(Window owner, string messageBoxText, string caption, string yesButtonText, string noButtonText, string cancelButtonText, int? timeout = null, MessageBoxResult timeoutResult = MessageBoxResult.None)
|
||||
{
|
||||
return ShowYesNoMessage(owner, messageBoxText, caption, yesButtonText, noButtonText, cancelButtonText, null, timeout, timeoutResult);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Displays a message box that has a message, caption, Yes/No/Cancel buttons with custom System.String values for the buttons' text, and icon;
|
||||
/// and that returns a result.
|
||||
/// </summary>
|
||||
/// <param name="messageBoxText">A System.String that specifies the text to display.</param>
|
||||
/// <param name="caption">A System.String that specifies the title bar caption to display.</param>
|
||||
/// <param name="yesButtonText">A System.String that specifies the text to display within the Yes button.</param>
|
||||
/// <param name="noButtonText">A System.String that specifies the text to display within the No button.</param>
|
||||
/// <param name="cancelButtonText">A System.String that specifies the text to display within the Cancel button.</param>
|
||||
/// <param name="icon">A System.Windows.MessageBoxImage value that specifies the icon to display.</param>
|
||||
/// <param name="timeout">
|
||||
/// The message box will close automatically after <paramref name="timeout"/> milliseconds.
|
||||
/// If null, the message box will act like default message box and not close automatically.
|
||||
/// </param>
|
||||
/// <param name="timeoutResult">If the message box closes automatically due to the <paramref name="timeout"/> being exceeded, this result is returned.</param>
|
||||
/// <returns>A System.Windows.MessageBoxResult value that specifies which message box button is clicked by the user.</returns>
|
||||
public static MessageBoxResult ShowYesNoCancel(string messageBoxText, string caption, string yesButtonText, string noButtonText, string cancelButtonText, MessageBoxImage icon, int? timeout = null, MessageBoxResult timeoutResult = MessageBoxResult.None)
|
||||
{
|
||||
return ShowYesNoMessage(null, messageBoxText, caption, yesButtonText, noButtonText, cancelButtonText, icon, timeout, timeoutResult);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Displays a message box that has a message, caption, Yes/No/Cancel buttons with custom System.String values for the buttons' text, and icon;
|
||||
/// and that returns a result.
|
||||
/// </summary>
|
||||
/// <param name="owner">A System.Windows.Window that represents the owner window of the message box.</param>
|
||||
/// <param name="messageBoxText">A System.String that specifies the text to display.</param>
|
||||
/// <param name="caption">A System.String that specifies the title bar caption to display.</param>
|
||||
/// <param name="yesButtonText">A System.String that specifies the text to display within the Yes button.</param>
|
||||
/// <param name="noButtonText">A System.String that specifies the text to display within the No button.</param>
|
||||
/// <param name="cancelButtonText">A System.String that specifies the text to display within the Cancel button.</param>
|
||||
/// <param name="icon">A System.Windows.MessageBoxImage value that specifies the icon to display.</param>
|
||||
/// <param name="timeout">
|
||||
/// The message box will close automatically after <paramref name="timeout"/> milliseconds.
|
||||
/// If null, the message box will act like default message box and not close automatically.
|
||||
/// </param>
|
||||
/// <param name="timeoutResult">If the message box closes automatically due to the <paramref name="timeout"/> being exceeded, this result is returned.</param>
|
||||
/// <returns>A System.Windows.MessageBoxResult value that specifies which message box button is clicked by the user.</returns>
|
||||
public static MessageBoxResult ShowYesNoCancel(Window owner, string messageBoxText, string caption, string yesButtonText, string noButtonText, string cancelButtonText, MessageBoxImage icon, int? timeout = null, MessageBoxResult timeoutResult = MessageBoxResult.None)
|
||||
{
|
||||
return ShowYesNoMessage(owner, messageBoxText, caption, yesButtonText, noButtonText, cancelButtonText, icon, timeout, timeoutResult);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Displays a message box that has a message, caption, OK/Cancel buttons with custom System.String values for the buttons' text, and icon;
|
||||
/// and that returns a result.
|
||||
/// </summary>
|
||||
/// <param name="owner">A System.Windows.Window that represents the owner window of the message box.</param>
|
||||
/// <param name="messageBoxText">A System.String that specifies the text to display.</param>
|
||||
/// <param name="caption">A System.String that specifies the title bar caption to display.</param>
|
||||
/// <param name="okButtonText">A System.String that specifies the text to display within the OK button.</param>
|
||||
/// <param name="cancelButtonText">A System.String that specifies the text to display within the Cancel button.</param>
|
||||
/// <param name="icon">A System.Windows.MessageBoxImage value that specifies the icon to display.</param>
|
||||
/// <param name="timeout">The message box will close automatically after given milliseconds</param>
|
||||
/// <param name="timeoutResult">If the message box closes automatically due to <paramref name="timeout"/> being exceeded, this result is returned.</param>
|
||||
/// <returns>A System.Windows.MessageBoxResult value that specifies which message box button is clicked by the user.</returns>
|
||||
private static MessageBoxResult ShowOKMessage(Window? owner, string? messageBoxText, string caption, string? okButtonText, string? cancelButtonText, MessageBoxImage? icon, int? timeout = null, MessageBoxResult timeoutResult = MessageBoxResult.None)
|
||||
{
|
||||
MessageBoxButton buttonLayout = string.IsNullOrEmpty(cancelButtonText) ? MessageBoxButton.OK : MessageBoxButton.OKCancel;
|
||||
|
||||
var msg = new CustomMessageBoxWindow(owner, messageBoxText, caption, buttonLayout, icon, RemoveTitleBarIcon);
|
||||
if (!string.IsNullOrEmpty(okButtonText))
|
||||
msg.OkButtonText = okButtonText;
|
||||
if (!string.IsNullOrEmpty(cancelButtonText))
|
||||
msg.CancelButtonText = cancelButtonText;
|
||||
|
||||
ShowDialog(msg, timeout, timeoutResult);
|
||||
|
||||
return msg.Result;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Displays a message box that has a message, caption, Yes/No/Cancel buttons with custom System.String values for the buttons' text, and icon;
|
||||
/// and that returns a result.
|
||||
/// </summary>
|
||||
/// <param name="owner">A System.Windows.Window that represents the owner window of the message box.</param>
|
||||
/// <param name="messageBoxText">A System.String that specifies the text to display.</param>
|
||||
/// <param name="caption">A System.String that specifies the title bar caption to display.</param>
|
||||
/// <param name="yesButtonText">A System.String that specifies the text to display within the Yes button.</param>
|
||||
/// <param name="noButtonText">A System.String that specifies the text to display within the No button.</param>
|
||||
/// <param name="cancelButtonText">A System.String that specifies the text to display within the Cancel button.</param>
|
||||
/// <param name="icon">A System.Windows.MessageBoxImage value that specifies the icon to display.</param>
|
||||
/// <param name="timeout">The message box will close automatically after given milliseconds</param>
|
||||
/// <param name="timeoutResult">If the message box closes automatically due to <paramref name="timeout"/> being exceeded, this result is returned.</param>
|
||||
/// <returns>A System.Windows.MessageBoxResult value that specifies which message box button is clicked by the user.</returns>
|
||||
private static MessageBoxResult ShowYesNoMessage(Window? owner, string? messageBoxText, string caption, string? yesButtonText, string? noButtonText, string? cancelButtonText, MessageBoxImage? icon, int? timeout = null, MessageBoxResult timeoutResult = MessageBoxResult.None)
|
||||
{
|
||||
MessageBoxButton buttonLayout = string.IsNullOrEmpty(cancelButtonText) ? MessageBoxButton.YesNo : MessageBoxButton.YesNoCancel;
|
||||
|
||||
var msg = new CustomMessageBoxWindow(owner, messageBoxText, caption, buttonLayout, icon, RemoveTitleBarIcon);
|
||||
if (!string.IsNullOrEmpty(yesButtonText))
|
||||
msg.YesButtonText = yesButtonText;
|
||||
if (!string.IsNullOrEmpty(noButtonText))
|
||||
msg.NoButtonText = noButtonText;
|
||||
if (!string.IsNullOrEmpty(cancelButtonText))
|
||||
msg.CancelButtonText = cancelButtonText;
|
||||
|
||||
ShowDialog(msg, timeout, timeoutResult);
|
||||
|
||||
return msg.Result;
|
||||
}
|
||||
|
||||
private static void ShowDialog(CustomMessageBoxWindow dialog, int? timeout, MessageBoxResult timeoutResult)
|
||||
{
|
||||
if (timeout.HasValue && timeout.Value <= 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(timeout), string.Format("Timeout must be greater than 0."));
|
||||
}
|
||||
|
||||
if (timeout.HasValue)
|
||||
{
|
||||
//System.Threading.Timer timer = null;
|
||||
//timer = new System.Threading.Timer(s => { msg.Close(); timer.Dispose(); }, null, timeout.Value, System.Threading.Timeout.Infinite);
|
||||
var timer = new System.Timers.Timer(timeout.Value) { AutoReset = false };
|
||||
timer.Elapsed += delegate {
|
||||
Application.Current.Dispatcher.Invoke(new Action(() =>
|
||||
{
|
||||
dialog.Result = timeoutResult;
|
||||
dialog.Close();
|
||||
//timer.Stop();
|
||||
//timer.Dispose();
|
||||
//timer = null;
|
||||
}));
|
||||
};
|
||||
timer.Start();
|
||||
dialog.ShowDialog();
|
||||
timer.Stop();
|
||||
timer.Dispose();
|
||||
}
|
||||
else
|
||||
dialog.ShowDialog();
|
||||
}
|
||||
}
|
||||
}
|
||||
78
MPF.UI/External/WPFCustomMessageBox/CustomMessageBoxWindow.xaml
vendored
Normal file
78
MPF.UI/External/WPFCustomMessageBox/CustomMessageBoxWindow.xaml
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
<Window x:Class="WPFCustomMessageBox.CustomMessageBoxWindow"
|
||||
x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
WindowStyle="None"
|
||||
ResizeMode="NoResize" SizeToContent="WidthAndHeight"
|
||||
Title="" MinHeight="155" MaxWidth="470" MinWidth="154"
|
||||
BorderBrush="DarkGray" BorderThickness="2">
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid Grid.Row="0" Margin="0,10,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="25"/>
|
||||
<ColumnDefinition Width="115"/>
|
||||
<ColumnDefinition/>
|
||||
<ColumnDefinition/>
|
||||
<ColumnDefinition/>
|
||||
<ColumnDefinition Width="50"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Image Grid.Column="0" Source="/Images/Icon.ico" Height="20" Width="20" Margin="1" />
|
||||
<Label Grid.Column="1" Grid.ColumnSpan="4" HorizontalAlignment="Stretch" HorizontalContentAlignment="Center" MouseDown="TitleMouseDown" Content="{Binding Path=Title,RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}"/>
|
||||
</Grid>
|
||||
|
||||
<!-- Make the window width fit the title by embedding the title invisibly -->
|
||||
<TextBlock Text="{Binding Path=Title,RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}"
|
||||
Visibility="Hidden" Height="0" Margin="50 0 0 0" />
|
||||
|
||||
<Grid Grid.Row="1" Background="{DynamicResource CustomMessageBox.Static.Background}" MinHeight="69">
|
||||
<DockPanel>
|
||||
<Image Name="Image_MessageBox" Width="32" Height="32" HorizontalAlignment="Left" DockPanel.Dock="Left" Margin="30,0,0,0" Visibility="Collapsed"/>
|
||||
<TextBlock Name="TextBlock_Message" TextWrapping="Wrap" MaxWidth="500" Width="Auto"
|
||||
VerticalAlignment="Center" Margin="12,20,41,15" />
|
||||
</DockPanel>
|
||||
</Grid>
|
||||
|
||||
<Grid Grid.Row="2" Background="{DynamicResource CustomMessageBox.Static.Background}" MinHeight="49">
|
||||
<DockPanel Margin="5,0">
|
||||
|
||||
<!-- Cancel Button -->
|
||||
<Button Name="Button_Cancel" MinWidth="88" MaxWidth="160" Height="26" Margin="5,0" HorizontalAlignment="Right" Visibility="Collapsed" IsCancel="True"
|
||||
DockPanel.Dock="Right" Click="Button_Cancel_Click" Style="{DynamicResource CustomButtonStyle}">
|
||||
<Label Name="Label_Cancel" Padding="0" Margin="10,0">_Cancel</Label>
|
||||
</Button>
|
||||
<!-- End Cancel Button -->
|
||||
|
||||
<!-- No Button -->
|
||||
<Button Name="Button_No" MinWidth="88" MaxWidth="160" Height="26" Margin="5,0" HorizontalAlignment="Right" Visibility="Collapsed"
|
||||
DockPanel.Dock="Right" Click="Button_No_Click" Style="{DynamicResource CustomButtonStyle}">
|
||||
<Label Name="Label_No" Padding="0" Margin="10,0">_No</Label>
|
||||
</Button>
|
||||
<!-- End No Button -->
|
||||
|
||||
<!-- Yes Button -->
|
||||
<Button Name="Button_Yes" MinWidth="88" MaxWidth="160" Height="26" Margin="35,0,5,0" HorizontalAlignment="Right" Visibility="Collapsed"
|
||||
DockPanel.Dock="Right" Click="Button_Yes_Click" Style="{DynamicResource CustomButtonStyle}">
|
||||
<Label Name="Label_Yes" Padding="0" Margin="10,0">_Yes</Label>
|
||||
</Button>
|
||||
<!-- End Yes Button -->
|
||||
|
||||
<!-- OK Button -->
|
||||
<Button Name="Button_OK" MinWidth="88" MaxWidth="160" Margin="35,0,5,0" HorizontalAlignment="Right" Height="26"
|
||||
Click="Button_OK_Click" Style="{DynamicResource CustomButtonStyle}">
|
||||
<Label Name="Label_Ok" Padding="0" Margin="10,0">_OK</Label>
|
||||
</Button>
|
||||
<!-- End OK Button -->
|
||||
|
||||
</DockPanel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Window>
|
||||
347
MPF.UI/External/WPFCustomMessageBox/CustomMessageBoxWindow.xaml.cs
vendored
Normal file
347
MPF.UI/External/WPFCustomMessageBox/CustomMessageBoxWindow.xaml.cs
vendored
Normal file
@@ -0,0 +1,347 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using MPF.UI;
|
||||
|
||||
#pragma warning disable IDE1006 // Naming Styles
|
||||
|
||||
namespace WPFCustomMessageBox
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for ModalDialog.xaml
|
||||
/// </summary>
|
||||
internal partial class CustomMessageBoxWindow : Window
|
||||
{
|
||||
private readonly bool _removeTitleBarIcon = true;
|
||||
|
||||
public string? Caption
|
||||
{
|
||||
get
|
||||
{
|
||||
return Title;
|
||||
}
|
||||
set
|
||||
{
|
||||
Title = value;
|
||||
}
|
||||
}
|
||||
|
||||
public string? Message
|
||||
{
|
||||
get
|
||||
{
|
||||
#if NET35
|
||||
return _TextBlock_Message!.Text;
|
||||
#else
|
||||
return TextBlock_Message.Text;
|
||||
#endif
|
||||
}
|
||||
set
|
||||
{
|
||||
#if NET35
|
||||
_TextBlock_Message!.Text = value;
|
||||
#else
|
||||
TextBlock_Message.Text = value;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public string? OkButtonText
|
||||
{
|
||||
get
|
||||
{
|
||||
#if NET35
|
||||
return _Label_Ok!.Content.ToString();
|
||||
#else
|
||||
return Label_Ok.Content.ToString();
|
||||
#endif
|
||||
}
|
||||
set
|
||||
{
|
||||
#if NET35
|
||||
_Label_Ok!.Content = value.TryAddKeyboardAccellerator();
|
||||
#else
|
||||
Label_Ok.Content = value.TryAddKeyboardAccellerator();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public string? CancelButtonText
|
||||
{
|
||||
get
|
||||
{
|
||||
#if NET35
|
||||
return _Label_Cancel!.Content.ToString();
|
||||
#else
|
||||
return Label_Cancel.Content.ToString();
|
||||
#endif
|
||||
}
|
||||
set
|
||||
{
|
||||
#if NET35
|
||||
_Label_Cancel!.Content = value.TryAddKeyboardAccellerator();
|
||||
#else
|
||||
Label_Cancel.Content = value.TryAddKeyboardAccellerator();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public string? YesButtonText
|
||||
{
|
||||
get
|
||||
{
|
||||
#if NET35
|
||||
return _Label_Yes!.Content.ToString();
|
||||
#else
|
||||
return Label_Yes.Content.ToString();
|
||||
#endif
|
||||
}
|
||||
set
|
||||
{
|
||||
#if NET35
|
||||
_Label_Yes!.Content = value.TryAddKeyboardAccellerator();
|
||||
#else
|
||||
Label_Yes.Content = value.TryAddKeyboardAccellerator();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public string? NoButtonText
|
||||
{
|
||||
get
|
||||
{
|
||||
#if NET35
|
||||
return _Label_No!.Content.ToString();
|
||||
#else
|
||||
return Label_No.Content.ToString();
|
||||
#endif
|
||||
}
|
||||
set
|
||||
{
|
||||
#if NET35
|
||||
_Label_No!.Content = value.TryAddKeyboardAccellerator();
|
||||
#else
|
||||
Label_No.Content = value.TryAddKeyboardAccellerator();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public MessageBoxResult Result { get; set; }
|
||||
|
||||
#if NET35
|
||||
|
||||
private Button? _Button_Cancel => ItemHelper.FindChild<Button>(this, "Button_Cancel");
|
||||
private Button? _Button_No => ItemHelper.FindChild<Button>(this, "Button_No");
|
||||
private Button? _Button_OK => ItemHelper.FindChild<Button>(this, "Button_OK");
|
||||
private Button? _Button_Yes => ItemHelper.FindChild<Button>(this, "Button_Yes");
|
||||
private System.Windows.Controls.Image? _Image_MessageBox => ItemHelper.FindChild<System.Windows.Controls.Image>(this, "Image_MessageBox");
|
||||
private Label? _Label_Cancel => ItemHelper.FindChild<Label>(this, "Label_Cancel");
|
||||
private Label? _Label_No => ItemHelper.FindChild<Label>(this, "Label_No");
|
||||
private Label? _Label_Ok => ItemHelper.FindChild<Label>(this, "Label_Ok");
|
||||
private Label? _Label_Yes => ItemHelper.FindChild<Label>(this, "Label_Yes");
|
||||
private TextBlock? _TextBlock_Message => ItemHelper.FindChild<TextBlock>(this, "TextBlock_Message");
|
||||
|
||||
#endif
|
||||
|
||||
internal CustomMessageBoxWindow(Window? owner, string? message, string? caption = null, MessageBoxButton? button = null, MessageBoxImage? image = null, bool removeTitleBarIcon = true)
|
||||
{
|
||||
#if NET40_OR_GREATER || NETCOREAPP
|
||||
System.Windows.Media.TextOptions.SetTextFormattingMode(this, System.Windows.Media.TextFormattingMode.Display);
|
||||
System.Windows.Media.TextOptions.SetTextRenderingMode(this, System.Windows.Media.TextRenderingMode.ClearType);
|
||||
UseLayoutRounding = true;
|
||||
#endif
|
||||
|
||||
#if NET452_OR_GREATER || NETCOREAPP
|
||||
var chrome = new System.Windows.Shell.WindowChrome
|
||||
{
|
||||
CaptionHeight = 0,
|
||||
ResizeBorderThickness = new Thickness(0),
|
||||
};
|
||||
System.Windows.Shell.WindowChrome.SetWindowChrome(this, chrome);
|
||||
#endif
|
||||
|
||||
#if NET40_OR_GREATER || NETCOREAPP
|
||||
InitializeComponent();
|
||||
#endif
|
||||
|
||||
_removeTitleBarIcon = removeTitleBarIcon;
|
||||
Focusable = true;
|
||||
ShowActivated = true;
|
||||
ShowInTaskbar = true;
|
||||
|
||||
if (owner != null && owner.IsLoaded)
|
||||
{
|
||||
Owner = owner;
|
||||
WindowStartupLocation = WindowStartupLocation.CenterOwner;
|
||||
}
|
||||
|
||||
Message = message;
|
||||
Caption = caption;
|
||||
|
||||
DisplayButtons(button ?? MessageBoxButton.OK);
|
||||
|
||||
if (image.HasValue)
|
||||
DisplayImage(image.Value);
|
||||
else
|
||||
#if NET35
|
||||
_Image_MessageBox!.Visibility = Visibility.Collapsed;
|
||||
#else
|
||||
Image_MessageBox.Visibility = Visibility.Collapsed;
|
||||
#endif
|
||||
}
|
||||
|
||||
protected override void OnSourceInitialized(EventArgs e)
|
||||
{
|
||||
if (_removeTitleBarIcon)
|
||||
Util.RemoveIcon(this);
|
||||
|
||||
base.OnSourceInitialized(e);
|
||||
}
|
||||
|
||||
private void DisplayButtons(MessageBoxButton button)
|
||||
{
|
||||
switch (button)
|
||||
{
|
||||
case MessageBoxButton.OKCancel:
|
||||
// Hide all but OK, Cancel
|
||||
#if NET35
|
||||
_Button_OK!.Visibility = Visibility.Visible;
|
||||
_Button_OK.Focus();
|
||||
_Button_Cancel!.Visibility = Visibility.Visible;
|
||||
|
||||
_Button_Yes!.Visibility = Visibility.Collapsed;
|
||||
_Button_No!.Visibility = Visibility.Collapsed;
|
||||
#else
|
||||
Button_OK.Visibility = Visibility.Visible;
|
||||
Button_OK.Focus();
|
||||
Button_Cancel.Visibility = Visibility.Visible;
|
||||
|
||||
Button_Yes.Visibility = Visibility.Collapsed;
|
||||
Button_No.Visibility = Visibility.Collapsed;
|
||||
#endif
|
||||
break;
|
||||
case MessageBoxButton.YesNo:
|
||||
// Hide all but Yes, No
|
||||
#if NET35
|
||||
_Button_Yes!.Visibility = Visibility.Visible;
|
||||
_Button_Yes.Focus();
|
||||
_Button_No!.Visibility = Visibility.Visible;
|
||||
|
||||
_Button_OK!.Visibility = Visibility.Collapsed;
|
||||
_Button_Cancel!.Visibility = Visibility.Collapsed;
|
||||
#else
|
||||
Button_Yes.Visibility = Visibility.Visible;
|
||||
Button_Yes.Focus();
|
||||
Button_No.Visibility = Visibility.Visible;
|
||||
|
||||
Button_OK.Visibility = Visibility.Collapsed;
|
||||
Button_Cancel.Visibility = Visibility.Collapsed;
|
||||
#endif
|
||||
break;
|
||||
case MessageBoxButton.YesNoCancel:
|
||||
// Hide only OK
|
||||
#if NET35
|
||||
_Button_Yes!.Visibility = Visibility.Visible;
|
||||
_Button_Yes.Focus();
|
||||
_Button_No!.Visibility = Visibility.Visible;
|
||||
_Button_Cancel!.Visibility = Visibility.Visible;
|
||||
|
||||
_Button_OK!.Visibility = Visibility.Collapsed;
|
||||
#else
|
||||
Button_Yes.Visibility = Visibility.Visible;
|
||||
Button_Yes.Focus();
|
||||
Button_No.Visibility = Visibility.Visible;
|
||||
Button_Cancel.Visibility = Visibility.Visible;
|
||||
|
||||
Button_OK.Visibility = Visibility.Collapsed;
|
||||
#endif
|
||||
break;
|
||||
default:
|
||||
// Hide all but OK
|
||||
#if NET35
|
||||
_Button_OK!.Visibility = Visibility.Visible;
|
||||
_Button_OK.Focus();
|
||||
|
||||
_Button_Yes!.Visibility = Visibility.Collapsed;
|
||||
_Button_No!.Visibility = Visibility.Collapsed;
|
||||
_Button_Cancel!.Visibility = Visibility.Collapsed;
|
||||
#else
|
||||
Button_OK.Visibility = Visibility.Visible;
|
||||
Button_OK.Focus();
|
||||
|
||||
Button_Yes.Visibility = Visibility.Collapsed;
|
||||
Button_No.Visibility = Visibility.Collapsed;
|
||||
Button_Cancel.Visibility = Visibility.Collapsed;
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void DisplayImage(MessageBoxImage image)
|
||||
{
|
||||
Icon icon;
|
||||
|
||||
switch (image)
|
||||
{
|
||||
case MessageBoxImage.Exclamation: // Enumeration value 48 - also covers "Warning"
|
||||
icon = SystemIcons.Exclamation;
|
||||
break;
|
||||
case MessageBoxImage.Error: // Enumeration value 16, also covers "Hand" and "Stop"
|
||||
icon = SystemIcons.Hand;
|
||||
break;
|
||||
case MessageBoxImage.Information: // Enumeration value 64 - also covers "Asterisk"
|
||||
icon = SystemIcons.Information;
|
||||
break;
|
||||
case MessageBoxImage.Question:
|
||||
icon = SystemIcons.Question;
|
||||
break;
|
||||
default:
|
||||
icon = SystemIcons.Information;
|
||||
break;
|
||||
}
|
||||
|
||||
#if NET35
|
||||
_Image_MessageBox!.Source = icon.ToImageSource();
|
||||
_Image_MessageBox.Visibility = Visibility.Visible;
|
||||
#else
|
||||
Image_MessageBox.Source = icon.ToImageSource();
|
||||
Image_MessageBox.Visibility = Visibility.Visible;
|
||||
#endif
|
||||
}
|
||||
|
||||
private void Button_OK_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Result = MessageBoxResult.OK;
|
||||
Close();
|
||||
}
|
||||
|
||||
private void Button_Cancel_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Result = MessageBoxResult.Cancel;
|
||||
Close();
|
||||
}
|
||||
|
||||
private void Button_Yes_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Result = MessageBoxResult.Yes;
|
||||
Close();
|
||||
}
|
||||
|
||||
private void Button_No_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Result = MessageBoxResult.No;
|
||||
Close();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handler for Title MouseDown event
|
||||
/// </summary>
|
||||
private void TitleMouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (e.ChangedButton == MouseButton.Left)
|
||||
this.DragMove();
|
||||
}
|
||||
}
|
||||
}
|
||||
23
MPF.UI/External/WPFCustomMessageBox/LICENSE
vendored
Normal file
23
MPF.UI/External/WPFCustomMessageBox/LICENSE
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 Thomas Absenger
|
||||
|
||||
Copyright (c) 2013 Evan Wondrasek / Apricity Software LLC
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
92
MPF.UI/External/WPFCustomMessageBox/Util.cs
vendored
Normal file
92
MPF.UI/External/WPFCustomMessageBox/Util.cs
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
// -----------------------------------------------------------------------
|
||||
// <copyright file="Util.cs">
|
||||
// TODO: Update copyright text.
|
||||
// </copyright>
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
namespace WPFCustomMessageBox
|
||||
{
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Interop;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
|
||||
/// <summary>
|
||||
/// TODO: Update summary.
|
||||
/// </summary>
|
||||
internal static class Util
|
||||
{
|
||||
[DllImport("user32.dll")]
|
||||
static extern int GetWindowLong(IntPtr hwnd, int index);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
static extern int SetWindowLong(IntPtr hwnd, int index, int newStyle);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
static extern bool SetWindowPos(IntPtr hwnd, IntPtr hwndInsertAfter, int x, int y, int width, int height, uint flags);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
static extern IntPtr SendMessage(IntPtr hwnd, uint msg, IntPtr wParam, IntPtr lParam);
|
||||
|
||||
const int GWL_EXSTYLE = -20;
|
||||
const int WS_EX_DLGMODALFRAME = 0x0001;
|
||||
const int SWP_NOSIZE = 0x0001;
|
||||
const int SWP_NOMOVE = 0x0002;
|
||||
const int SWP_NOZORDER = 0x0004;
|
||||
const int SWP_FRAMECHANGED = 0x0020;
|
||||
//const uint WM_SETICON = 0x0080;
|
||||
|
||||
|
||||
internal static ImageSource ToImageSource(this Icon icon)
|
||||
{
|
||||
ImageSource imageSource = Imaging.CreateBitmapSourceFromHIcon(
|
||||
icon.Handle,
|
||||
Int32Rect.Empty,
|
||||
BitmapSizeOptions.FromEmptyOptions());
|
||||
|
||||
return imageSource;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Keyboard Accellerators are used in Windows to allow easy shortcuts to controls like Buttons and
|
||||
/// MenuItems. These allow users to press the Alt key, and a shortcut key will be highlighted on the
|
||||
/// control. If the user presses that key, that control will be activated.
|
||||
/// This method checks a string if it contains a keyboard accellerator. If it doesn't, it adds one to the
|
||||
/// beginning of the string. If there are two strings with the same accellerator, Windows handles it.
|
||||
/// The keyboard accellerator character for WPF is underscore (_). It will not be visible.
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
internal static string? TryAddKeyboardAccellerator(this string? input)
|
||||
{
|
||||
if (input == null)
|
||||
return input;
|
||||
|
||||
const string accellerator = "_"; // This is the default WPF accellerator symbol - used to be & in WinForms
|
||||
|
||||
// If it already contains an accellerator, do nothing
|
||||
if (input.Contains(accellerator)) return input;
|
||||
|
||||
return accellerator + input;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the icon from the given window.
|
||||
/// See https://stackoverflow.com/a/18581096
|
||||
/// </summary>
|
||||
/// <param name="window">The window to remove the icon from.</param>
|
||||
internal static void RemoveIcon(Window window)
|
||||
{
|
||||
// Get this window's handle
|
||||
IntPtr hwnd = new WindowInteropHelper(window).Handle;
|
||||
// Change the extended window style to not show a window icon
|
||||
int extendedStyle = GetWindowLong(hwnd, GWL_EXSTYLE);
|
||||
_ = SetWindowLong(hwnd, GWL_EXSTYLE, extendedStyle | WS_EX_DLGMODALFRAME);
|
||||
// Update the window's non-client area to reflect the changes
|
||||
SetWindowPos(hwnd, IntPtr.Zero, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 71 KiB |
BIN
MPF.UI/Images/ring-code-guide-1-layer.png
Normal file
BIN
MPF.UI/Images/ring-code-guide-1-layer.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 423 KiB |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user