-
Notifications
You must be signed in to change notification settings - Fork 35
Expand file tree
/
Copy pathinput.py
More file actions
1671 lines (1404 loc) · 65.6 KB
/
input.py
File metadata and controls
1671 lines (1404 loc) · 65.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
from __future__ import annotations
import argparse
import json
import pathlib
import re
import sys
from functools import reduce
from typing import TYPE_CHECKING, Any
from strictyaml import YAML, Map, MapPattern, Seq, Str # type: ignore
import modules.constants as const
if TYPE_CHECKING:
from modules.config.config import Config
from modules.dat.process_dat import Dat
from modules.clone_lists.clone_list import CloneList
from modules.config.read_write_config import read_config
from modules.utils import Font, SmartFormatter, download, eprint, minimum_version, regex_test
class UserInput:
def __init__(
self,
input_file_name: str = '',
update: bool = False,
no_1g1r: bool = False,
filter_languages: bool = False,
oldest: bool = False,
retroachievements: bool = False,
local_names: bool = False,
region_bias: bool = False,
legacy: bool = False,
demote_unl: bool = False,
modern: bool = False,
compilations: str = '',
no_applications: bool = False,
no_audio: bool = False,
no_bad_dumps: bool = False,
no_bios: bool = False,
no_coverdiscs: bool = False,
no_demos: bool = False,
no_add_ons: bool = False,
no_educational: bool = False,
no_aftermarket: bool = False,
no_games: bool = False,
no_mia: bool = False,
no_manuals: bool = False,
no_multimedia: bool = False,
no_bonus_discs: bool = False,
no_pirate: bool = False,
no_preproduction: bool = False,
no_promotional: bool = False,
no_unlicensed: bool = False,
no_video: bool = False,
clone_list: str = '',
user_config: str = '',
metadata: str = '',
mia: str = '',
ra: str = '',
no_overrides: bool = False,
list_names: bool = False,
report: bool = False,
machine_not_game: bool = False,
label_mia: bool = False,
label_retro: bool = False,
original_header: bool = False,
output_folder_name: str = '',
user_output_folder: str = '',
output_region_split: bool = False,
output_remove_dat: bool = False,
replace: bool = False,
reprocess_dat: bool = False,
verbose: bool = False,
warningpause: bool = False,
single_cpu: bool = False,
trace: str = '',
excludes: str = '',
dev_mode: bool = False,
user_options: str = '',
user_clone_list_location: str = '',
user_clone_list_metadata_download_location: str = '',
user_metadata_location: str = '',
user_mia_location: str = '',
user_ra_location: str = '',
test: bool = False,
) -> None:
"""
Stores user input values, including what types of titles to exclude.
Args:
input_file_name (str, optional): The path to the input DAT file. Defaults to
`''`.
update (bool, optional): Calls the clone list update function. Defaults to
`False`.
no_1g1r (bool, optional): Disables 1G1R processing. Defaults to `False`.
filter_languages (bool, optional): Filters by languages, removing any title
that doesn't support the languages in the supplied list. Defaults to
`False`.
local_names (bool, optional): Uses local names for titles if available. For
example, `ダイナマイト ヘッディー (Japan): instead of `Dynamite Headdy
(Japan)`.
oldest (bool, optional): Prefers oldest production versions of titles instead
of newest.
retroachievements (bool, optional): Prefers titles with RetroAchievements.
region_bias (bool, optional): Prefers regions over languages. Defaults to
`False`.
legacy (bool, optional): Outputs the DAT file in legacy mode, complete with
parent/clone tags. Only useful for clone list maintainers who want to
track changes between DAT releases. Defaults to `False`.
demote_unl (bool, optional): Demotes unlicensed, aftermarket, and pirate
titles if a production version of a title is found in another region.
Defaults to `False`.
modern (bool, optional): Whether to choose a version of a title ripped from a
modern rerelease (e.g. Steam, Virtual Console) over the original title.
Defaults to `False`.
compilations (str, optional): What compilations mode to set Retool to.
Defaults to ''.
no_applications (bool, optional): Excludes applications. Defaults to `False`.
no_audio (bool, optional): Excludes audio. Defaults to `False`.
no_bad_dumps (bool, optional): Excludes bad dumps. Defaults to `False`.
no_bios (bool, optional): Excludes BIOS and other chip-based titles. Defaults
to `False`.
no_coverdiscs (bool, optional): Excludes coverdiscs. Defaults to `False`.
no_demos (bool, optional): Excludes demos. Defaults to `False`.
no_add_ons (bool, optional): Excludes add-ons. Defaults to `False`.
no_educational (bool, optional): Excludes educational titles. Defaults to
`False`.
no_aftermarket (bool, optional): Excludes aftermarket titles. Defaults to
`False`.
no_games (bool, optional): Excludes games. Defaults to `False`.
no_mia (bool, optional): Excludes MIA titles. Defaults to `False`.
no_manuals (bool, optional): Excludes manuals. Defaults to `False`.
no_multimedia (bool, optional): Excludes multimedia titles. Defaults to
`False`.
no_bonus_discs (bool, optional): Excludes bonus discs. Defaults to `False`.
no_pirate (bool, optional): Excludes pirate titles. Defaults to `False`.
no_preproduction (bool, optional): Excludes preproduction titles. Defaults to
`False`.
no_promotional (bool, optional): Excludes promotional titles. Defaults to
`False`.
no_unlicensed (bool, optional): Excludes unlicensed, aftermarket, and pirate
titles. Defaults to `False`.
no_video (bool, optional): Excludes video titles. Defaults to `False`.
clone_list (str, optional): The path to a clone list to load, overriding the
default selection. Defaults to `''`.
user_config (str, optional): The path to a user config file to load,
overriding the default selection. Defaults to `''`.
metadata (str, optional): The path to a metadata file to load, overriding the
default selection. Defaults to `''`.
mia (str, optional): The path to an MIA file to load, overriding the default
selection. Defaults to `''`.
ra (str, optional): The path to a RetroAchievements file to load, overriding
the default selection. Defaults to `''`.
no_overrides (bool, optional): Disables global and system overrides. Defaults
to `False`.
list_names (bool, optional): Additionally outputs a file that contains just
the names of the 1G1R titles found after processing. Defaults to `False`.
report (bool, optional): Additionally outputs a file that shows what titles
have been kept and removed. Defaults to `False`.
machine_not_game (bool, optional): Uses the MAME standard of <machine> for
title nodes in the output DAT file instead of <game>. Defaults to `False`.
label_mia (bool, optional): Adds MIA attributes to ROMs or titles. Defaults to
`False`.
label_retro (bool, optional): Adds RetroAchievements attributes to titles.
Defaults to `False`.
original_header (bool, optional): Uses the header from the input DAT in the
output DAT. Useful to update original No-Intro and Redump DATs already
loaded in CLRMAMEPro. Defaults to `False`.
output_folder_name (str, optional): Sets the folder DATs are written to.
Defaults to `''`.
user_output_folder (str, optional): Whether the output folder is user
provided. Defaults to ''.
output_region_split (bool, optional): Produces multiple DAT files split by
region, instead of just a single DAT file. Defaults to `False`.
output_remove_dat (bool, optional): Additionally outputs a DAT that contains
all the titles Retool has removed as part of its process. Defaults to
`False`.
replace (bool, optional): Delete the input DAT file and create Retool files in
the same folder. Defaults to `False`.
reprocess_dat (bool, optional): Let DAT files be processed even if Retool has
already processed them. Defaults to `False`.
verbose (bool, optional): Displays warnings when clone list errors occur.
Defaults to `False`.
warningpause (bool, optional): Pauses Retool when a clone list error is
reported. Defaults to `False`.
single_cpu (bool, optional): Uses a single CPU to do the processing, instead
of using all available processors. Defaults to `False`.
trace (str, optional): Traces a title through Retool's process, using the
supplied string as regex. Defaults to `''`.
excludes (str, optional): A string representation of all the exclusion options
as single letters. Used in naming the output DAT file as a way to
determine what options generated the file. Defaults to `''`.
dev_mode (bool, optional): Enables dev mode. Displays some extra messages to
help troubleshoot code issues. Defaults to `False`.
user_options (str, optional): If a user has enabled single letter user options
(-delryz), adds them to the output filename. Defaults to `''`.
user_clone_list_location (str, optional): A user-defined folder for where
clone lists live. Only settable in the GUI. Defaults to `''`.
user_clone_list_metadata_download_location (str, optional): A user-defined URL
for where to download clone list and metadata updates from. Only settable
in the GUI. Defaults to `''`.
user_metadata_location (str, optional): A user-defined folder for where
metadata files live. Only settable in the GUI. Defaults to `''`.
user_mia_location (str, optional): A user-defined folder for where MIA files
live. Only settable in the GUI. Defaults to `''`.
user_ra_location (str, optional): A user-defined folder for where
RetroAchievements files live. Only settable in the GUI. Defaults to `''`.
test (bool, optional): Runs tests helpful to Retool's development. Defaults to
`False`.
"""
# Positional
self.input_file_name: str = input_file_name
# Optional
self.no_1g1r: bool = no_1g1r
self.legacy: bool = legacy
self.filter_languages: bool = filter_languages
self.local_names: bool = local_names
self.oldest: bool = oldest
self.retroachievements: bool = retroachievements
self.no_mia: bool = no_mia
self.region_bias: bool = region_bias
self.demote_unl: bool = demote_unl
self.modern: bool = modern
self.compilations: str = compilations
# Excludes
self.no_add_ons: bool = no_add_ons
self.no_aftermarket: bool = no_aftermarket
self.no_applications: bool = no_applications
self.no_audio: bool = no_audio
self.no_bad_dumps: bool = no_bad_dumps
self.no_bios: bool = no_bios
self.no_bonus_discs: bool = no_bonus_discs
self.no_coverdiscs: bool = no_coverdiscs
self.no_demos: bool = no_demos
self.no_educational: bool = no_educational
self.no_games: bool = no_games
self.no_manuals: bool = no_manuals
self.no_multimedia: bool = no_multimedia
self.no_pirate: bool = no_pirate
self.no_preproduction: bool = no_preproduction
self.no_promotional: bool = no_promotional
self.no_unlicensed: bool = no_unlicensed
self.no_video: bool = no_video
# Inputs
self.clone_list: str = clone_list
self.user_config: str = user_config
self.metadata: str = metadata
self.mia: str = mia
self.ra: str = ra
self.user_clone_list_location: str = user_clone_list_location
self.user_clone_list_metadata_download_location: str = (
user_clone_list_metadata_download_location
)
self.user_metadata_location: str = user_metadata_location
self.user_mia_location: str = user_mia_location
self.user_ra_location: str = user_ra_location
self.no_overrides: bool = no_overrides
# Outputs
self.list_names: bool = list_names
self.report: bool = report
self.machine_not_game: bool = machine_not_game
self.label_mia: bool = label_mia
self.label_retro: bool = label_retro
# ROMs need to be labeled MIA for them to be removed
if self.no_mia:
self.label_mia = True
# Titles need to be labeled for RetroAchievements for them to be selected
if self.retroachievements:
self.label_retro = True
self.original_header: bool = original_header
self.output_folder_name: str = output_folder_name
self.user_output_folder: bool = bool(user_output_folder)
self.output_region_split: bool = output_region_split
self.output_remove_dat: bool = output_remove_dat
self.replace: bool = replace
self.reprocess_dat: bool = reprocess_dat
# Debug
self.verbose: bool = verbose
self.warningpause: bool = warningpause
self.single_cpu: bool = single_cpu
self.trace: str = trace
# Internal
self.user_options: str = user_options
self.excludes: str = excludes
self.dev_mode: bool = dev_mode
self.test: bool = test
self.update: bool = update
# Check for valid regex in the trace
if self.trace:
trace_test: list[str] = regex_test([self.trace], '', 'trace')
if not trace_test:
self.trace = ''
else:
# Enable single CPU, as the regex is valid and multiprocessor doesn't
# like the output provided by the trace
self.single_cpu = True
def check_input() -> UserInput:
"""Checks user input values, and creates a UserInput object for Retool to use."""
parser: argparse.ArgumentParser = argparse.ArgumentParser(
usage='\n\n%(prog)s <input DAT/folder> <options>\n\nOR to download updated clone lists:\n\n%(prog)s --update',
allow_abbrev=False,
formatter_class=SmartFormatter,
add_help=False,
)
# Help text order is determined by the group order here
exclusions: Any = parser.add_argument_group('exclusions')
outputs: Any = parser.add_argument_group('outputs')
debug: Any = parser.add_argument_group('debug')
system: Any = parser.add_argument_group('system')
parser.add_argument(
'Input',
metavar='<input DAT/folder>',
type=str,
help='R|The path to the DAT file, or folder of DAT files you\nwant to process.\n',
nargs='?',
)
parser.add_argument(
'-h', '--help', '-?', action='help', default=argparse.SUPPRESS, help=argparse.SUPPRESS
)
parser.add_argument(
'-c',
action='store_true',
help='R|Prefer titles with RetroAchievements.\n\n',
)
parser.add_argument(
'-d',
action='store_true',
help='R|Disable 1G1R filtering. Clone lists are ignored, and each'
'\ntitle is treated as unique. User settings and excludes are'
'\nstill respected. Useful if you want to keep everything'
'\nfrom a specific set of regions and/or languages. Not'
'\ncompatible with -x.'
'\n\n',
)
parser.add_argument(
'-l',
action='store_true',
help=f'R|Filter by languages using a list. If a title doesn\'t'
'\nsupport any of the languages on the list, it\'s removed'
f'\n(see {Font.b}config/user-config.yaml{Font.be}).'
'\n\n',
)
parser.add_argument(
'-n',
action='store_true',
help=f'R|Use local names for titles if available. For example,'
'\nシャイニング·フォースⅡ 『古の封印』 instead of'
'\nShining Force II - Inishie no Fuuin'
f'\n(see {Font.b}config/user-config.yaml{Font.be}).'
'\n\n',
)
parser.add_argument(
'-o',
action='store_true',
help='R|Prefer oldest production versions instead of newest.'
'\nUseful for speedrunners and those concerned about censorship,'
'\nwho often want unpatched versions of games.'
'\n\n',
)
parser.add_argument(
'-r',
action='store_true',
help='R|Prefer regions over languages. By default, if a title'
'\nfrom a higher priority region doesn\'t support your'
'\npreferred languages but a lower priority region does,'
'\nRetool selects the latter. This option disables this'
'\nbehavior, forcing strict adherence to region priority'
'\nregardless of language support. This option also'
'\noverrides similar behavior in superset selection, which'
'\nmeans you might get a title that was released in your'
'\npreferred region that has less content, instead of one'
'\nthat was released in another region that contains more'
'\ncontent and supports your preferred languages.'
'\n\n',
)
parser.add_argument(
'-y',
action='store_true',
help='R|Prefer licensed versions over unlicensed, aftermarket,'
'\nor pirate titles. This might select titles with lower'
'\npriority regions or languages, or with less features.'
'\n\n',
)
parser.add_argument(
'-z',
action='store_true',
help='R|Prefer titles ripped from modern rereleases over original'
'\nsystem releases, such as those found in Virtual Console'
'\n(ripped titles might not work in emulators).'
'\n\n',
)
parser.add_argument(
'--compilations',
action='extend',
metavar='',
help='R|How compilations should be handled. By default, Retool chooses'
'\nindividual titles most of the time. It only chooses compilations'
'\nwhen they have a higher region, language, or clone list priority,'
'\nor contain unique titles. When choosing a compilation for unique'
'\ntitles, if other titles in the compilation have individual'
'\nequivalents, the individual titles are also included, leading to'
'\nsome title duplication.'
'\n'
'\nTo change this behavior, use this flag and add one of the'
'\nfollowing single letters afterwards to select a mode:'
'\n\ni\tAlways prefer individual titles. Choose individual titles'
'\n \tregardless of region, language, and clone list priorities,'
'\n \tand discard compilations unless they contain unique games.'
'\n \tYou\'re likely to prefer this mode if you use ROM hacks or'
'\n \tRetroAchievements. When choosing a compilation for unique'
'\n \ttitles, if other titles in the compilation have individual'
'\n \tequivalents, the individual titles are also included, leading'
'\n \tto some title duplication.'
'\n\nk\tKeep individual titles and compilations. Ignores the'
'\n \trelationship between individual titles and compilations, meaning'
'\n \tindividual titles are only compared against other individual'
'\n \ttitles, and compilations against other compilations. This option'
'\n \thas the most title duplication.'
f'\n\no\t{Font.b}(Beta){Font.be} Optimize for the least possible title duplication. Not'
'\n \trecommended. While this mode can save disk space, it can be hard'
'\n \tto tell what compilations contain based on their filename. This'
'\n \tmode might not choose the optimal solution when supersets or'
'\n \tclone list priorities are involved.'
'\n\n',
nargs=1,
)
parser.add_argument(
'--nooverrides', action='store_true', help='R|Don\'t load global and system overrides.\n'
)
parser.add_argument('-q', action='store_true', help=argparse.SUPPRESS)
parser.add_argument('--test', action='store_true', help=argparse.SUPPRESS)
outputs.add_argument(
'--labelmia',
action='store_true',
help='R|Mark files as MIA with an mia="yes" attribute. Don\'t use this if you\'re '
'\na DATVault subscriber.'
'\n\n',
)
outputs.add_argument(
'--labelretro',
action='store_true',
help='R|Mark titles with a retroachievements="yes" attribute.\n\n',
)
outputs.add_argument(
'--listnames',
action='store_true',
help='R|Also output a TXT file of just the kept title names. See'
f'\n{Font.b}config/user-config.yaml{Font.be} to add a prefix and/or suffix'
'\nto each line.'
'\n\n',
)
outputs.add_argument(
'--machine',
action='store_true',
help='R|Export each title node to the output DAT file using the MAME'
'\nstandard of <machine> instead of <game>.'
'\n\n',
)
outputs.add_argument(
'--originalheader',
action='store_true',
help='R|Use the original input DAT headers in output DAT files.'
'\nUseful if you want to load Retool DATs as an update'
'\nto original Redump and No-Intro DATs already in CLRMAMEPro.'
'\n\n',
)
outputs.add_argument(
'--output',
metavar='<folder>',
type=str,
help='R|Set an output folder where the new 1G1R DAT/s will be\ncreated.'
'\n\nNot compatible with --replace.'
'\n\n',
)
outputs.add_argument(
'--regionsplit',
action='store_true',
help='R|Split the result into multiple DATs based on region. Use '
'\nwith -d to only split by region with no 1G1R processing.'
'\n\nNot compatible with --legacy.'
'\n\n',
)
outputs.add_argument(
'--removesdat',
action='store_true',
help='R|Also output DAT files containing titles that were'
'\nremoved from 1G1R DAT files.'
'\n\n',
)
outputs.add_argument(
'--replace',
action='store_true',
help='R|Replace input DAT files with Retool versions. Only use this if'
'\nyou can recover the original DAT files from elsewhere. Useful'
'\nfor RomVault or DatVault users operating directly on their'
'\nDatRoot files.'
'\n\nNot compatible with --output.'
'\n\n',
)
outputs.add_argument(
'--report',
action='store_true',
help='R|Also output a report of the titles that have been kept,'
'\nremoved, and set as clones.'
'\n\n',
)
outputs.add_argument(
'--reprocess',
action='store_true',
help='R|Let DAT files be processed even if Retool has already\nprocessed them.\n',
)
debug.add_argument(
'--config',
metavar='<file>',
type=str,
help='R|Set a custom user config file to use instead of the\ndefault.\n\n',
)
debug.add_argument(
'--clonelist',
metavar='<file>',
type=str,
help='R|Set a custom clone list to use instead of the default.'
'\nUseful if you want to use your own, or if Redump or'
'\nNo-Intro renames their DAT, and the clone list isn\'t'
'\nautomatically detected anymore. Often used together with'
'\n--metadata, --mia, and --ra.'
'\n\n',
)
debug.add_argument(
'--legacy',
action='store_true',
help='R|Output DAT files in legacy parent/clone format.'
'\n\nNot compatible with -d.'
'\n\n',
)
debug.add_argument(
'--metadata',
metavar='<file>',
type=str,
help='R|Set a custom metadata file to use instead of the default.'
'\nUseful if you want to use your own, or if Redump or'
'\nNo-Intro renames their DAT, and the metadata file isn\'t'
'\nautomatically detected anymore. Often used together with'
'\n--clonelist, --mia, and --ra.'
'\n\n',
)
debug.add_argument(
'--mia',
metavar='<file>',
type=str,
help='R|Set a custom MIA file to use instead of the default.'
'\nUseful if you want to use your own, or if Redump or'
'\nNo-Intro renames their DAT, and the MIA file isn\'t'
'\nautomatically detected anymore. Often used together with'
'\n--clonelist, --metadata, and --ra.'
'\n\n',
)
debug.add_argument(
'--ra',
metavar='<file>',
type=str,
help='R|Set a custom RetroAchievements file to use instead of the'
'\ndefault. Useful if you want to use your own, or if Redump or'
'\nNo-Intro renames their DAT, and the RetroAchievements file'
'\n isn\'t automatically detected anymore. Often used together'
'\nwith --clonelist, --metadata, and --mia.'
'\n\n',
)
debug.add_argument(
'--singlecpu', action='store_true', help='R|Disable multiprocessor usage.\n\n'
)
debug.add_argument(
'--trace',
action='extend',
metavar='',
help='R|Trace a title through the Retool process for debugging.'
'\nTo function properly, this disables using multiple'
'\nprocessors during parent selection.'
'\n\nUsage: --trace "regex of titles to trace"'
'\n\n',
nargs='+',
)
debug.add_argument(
'--warnings',
action='store_true',
help='R|Report clone list warnings during processing.\n\n',
)
debug.add_argument(
'--warningpause',
action='store_true',
help='R|Pause when a clone list warning is found. Useful when\nbatch processing DATS.'
'\n\n',
)
exclusions.add_argument(
'--exclude',
action='extend',
metavar='',
help='R|Add the following single letter filters after the\n'
'exclude option to exclude different title types:\n'
'\na\tApplications'
'\nA\tAudio (might include game soundtracks)'
'\nb\tBad dumps'
'\nB\tBIOS and other chips'
'\nc\tCoverdiscs (discs on the front of '
'magazines)'
'\nd\tDemos, kiosks, and samples'
'\nD\tAdd-ons (expansion packs, additional material)'
'\ne\tEducational titles'
'\nf\tAftermarket titles'
'\ng\tGames'
'\nk\tTitles with MIA ROMs'
'\nm\tManuals'
'\nM\tMultimedia titles (might include games)'
'\no\tBonus discs'
'\np\tPirate titles'
'\nP\tPreproduction titles (alphas, betas, prototypes)'
'\nr\tPromotional titles'
'\nu\tUnlicensed (unl) titles'
'\nv\tVideo'
'\n',
nargs='+',
)
system.add_argument('--update', action='store_true', help=argparse.SUPPRESS)
if len(sys.argv) == 1:
sys.exit(1)
args: argparse.Namespace = parser.parse_args()
# Make sure incompatible flags aren't used, and handle other edge case situations
if args.legacy and args.d:
eprint('• -d and --legacy modes can\'t be used together. Exiting...', level='warning')
sys.exit(1)
if args.legacy and args.regionsplit:
eprint(
'• --regionsplit and --legacy modes can\'t be used together. Exiting...',
level='warning',
)
sys.exit(1)
if args.replace and args.output:
eprint('• --replace and --output can\'t be used together. Exiting...', level='warning')
sys.exit(1)
if not args.update and args.Input is None:
eprint(
'• Unless you\'re updating clone lists, you must specify an input DAT or folder.',
level='warning',
)
eprint(
f'\nUsage: {pathlib.Path(sys.argv[0]).name} <input DAT/folder> <options>'
f'\n\nType {Font.b}{pathlib.Path(sys.argv[0]).name} -h{Font.be} for all '
'options\n',
wrap=False,
)
sys.exit(1)
# Set warnings to always be true if in dev mode
dev_mode: bool = False
if pathlib.Path('.dev').is_file() and not args.q:
dev_mode = True
if not args.test:
setattr(args, 'warnings', True)
setattr(args, 'warningpause', True)
eprint(f'• {Font.b}Operating in dev mode{Font.be}', level='warning')
eprint('')
# Notify if in test mode
if args.test:
eprint(f'• {Font.b}Operating in test mode{Font.be}', level='warning')
eprint(f'• {Font.b}Running Python version: {sys.version}{Font.be}', level='warning')
# Set legacy mode if in dev mode or test mode
if (dev_mode or args.test) and not args.q:
setattr(args, 'legacy', True)
# Compensate for trailing backslash in Windows
if args.Input is not None:
if sys.platform.startswith('win') and '"' in args.Input:
args.Input = re.sub('".*', '', args.Input)
else:
args.Input = ''
# Validate the output folder the user specified
if args.output is not None:
if pathlib.Path(args.output).is_file():
eprint(
f'Can\'t output to {Font.b}"{args.output}"{Font.be}, as it\'s a file, '
'not a folder.\n',
indent=0,
level='error',
)
sys.exit(1)
elif not pathlib.Path(args.output).exists():
eprint(f'• Creating folder "{Font.b}{args.output}{Font.be}"')
pathlib.Path(args.output).mkdir(parents=True, exist_ok=True)
else:
args.output = ''
# Validate that user specified files exist
def validate_user_file(user_file_path: str | None, user_file_type: str) -> str:
"""
Check that a file path provided by the user exists, otherwise ignore it.
Args:
user_file_path (str | None): The path to the user file.
user_file_type (str): A description of the file. Used in messages to the user.
Returns:
str: The file path the user provided.
"""
if user_file_path is not None:
if pathlib.Path(user_file_path).is_file():
eprint(f'• Custom {user_file_type} found: "{Font.b}{user_file_path}{Font.be}".')
else:
eprint(
f'• Can\'t find the specified {user_file_type}: '
f'"{Font.b}{user_file_path}{Font.be}". Ignoring...',
level='warning',
)
else:
user_file_path = ''
return user_file_path
args.clonelist = validate_user_file(args.clonelist, 'clone list')
args.metadata = validate_user_file(args.metadata, 'metadata file')
args.mia = validate_user_file(args.mia, 'MIA file')
args.ra = validate_user_file(args.ra, 'RetroAchievements file')
# Create user options string
user_options: list[str] = []
hidden_options: tuple[str, ...] = (
'Input',
'output',
'clonelist',
'compilations',
'config',
'e',
'exclude',
'legacy',
'labelmia',
'labelretro',
'listnames',
'machine',
'metadata',
'mia',
'mianooverrides',
'originalheader',
'q',
'regionsplit',
'removesdat',
'replace',
'report',
'reprocess',
'retroachievements',
'singlecpu',
'test',
'trace',
'warnings',
'warningpause',
)
for arg in vars(args):
if arg not in hidden_options and getattr(args, arg):
user_options.append(arg)
# Add another marker for legacy output DATs
if args.legacy:
user_options.append('x')
args_set: set[str] = set()
if not args.exclude:
args.exclude = []
else:
args_set = set(args.exclude[0])
compilations: str = ''
if args.compilations:
compilations = args.compilations[0][0:1]
if not args.config:
args.config = ''
else:
args.config = pathlib.Path(args.config).resolve()
if not pathlib.Path(args.config).is_file():
eprint(
f'• The user config file you specified, '
f'{Font.b}{args.config}{Font.be}, doesn\'t exist. '
'Using the default config/user-config.yaml.',
level='warning',
)
args.config = pathlib.Path('config/user-config.yaml').resolve()
if not args.trace:
args.trace = []
user_options_string: str = ''
if user_options != []:
user_options_string = (
f' (-{"".join(sorted(user_options, key=lambda s: (s.lower(), s[0].isupper())))})'
)
return UserInput(
input_file_name=str(pathlib.Path(args.Input).resolve()),
update=args.update,
no_1g1r=args.d,
filter_languages=args.l,
local_names=args.n,
oldest=args.o,
retroachievements=args.c,
region_bias=args.r,
legacy=args.legacy,
demote_unl=args.y,
modern=args.z,
compilations=compilations,
no_applications=bool('a' in args_set),
no_audio=bool('A' in args_set),
no_bad_dumps=bool('b' in args_set),
no_bios=bool('B' in args_set),
no_coverdiscs=bool('c' in args_set),
no_demos=bool('d' in args_set),
no_add_ons=bool('D' in args_set),
no_educational=bool('e' in args_set),
no_aftermarket=bool('f' in args_set),
no_games=bool('g' in args_set),
no_mia=bool('k' in args_set),
no_manuals=bool('m' in args_set),
no_multimedia=bool('M' in args_set),
no_bonus_discs=bool('o' in args_set),
no_pirate=bool('p' in args_set),
no_preproduction=bool('P' in args_set),
no_promotional=bool('r' in args_set),
no_unlicensed=bool('u' in args_set),
no_video=bool('v' in args_set),
clone_list=str(pathlib.Path(args.clonelist).resolve()),
user_config=args.config,
metadata=str(pathlib.Path(args.metadata).resolve()),
mia=str(pathlib.Path(args.mia).resolve()),
ra=str(pathlib.Path(args.ra).resolve()),
no_overrides=args.nooverrides,
list_names=args.listnames,
report=args.report,
machine_not_game=args.machine,
label_mia=args.labelmia,
label_retro=args.labelretro,
original_header=args.originalheader,
output_folder_name=str(pathlib.Path(args.output).resolve()),
user_output_folder=args.output,
output_region_split=args.regionsplit,
output_remove_dat=args.removesdat,
replace=args.replace,
reprocess_dat=args.reprocess,
verbose=args.warnings,
warningpause=args.warningpause,
single_cpu=args.singlecpu,
trace=' '.join(args.trace),
excludes=''.join(sorted(args_set, key=lambda s: (s.lower(), s[0].isupper()))),
dev_mode=dev_mode,
user_options=user_options_string,
user_clone_list_location='',
user_clone_list_metadata_download_location='',
user_metadata_location='',
user_mia_location='',
user_ra_location='',
test=args.test,
)
def get_config_value(
config_object: tuple[Any, ...], key: str, default_value: str, is_path: bool = True
) -> str:
"""
Gets a value for a specific key in an object out of `user-config.yaml` and system
config files.
Args:
config_object (tuple[Any, ...]): A YAML object from a config file.