1 /* ScummVM - Graphic Adventure Engine
3 * ScummVM is the legal property of its developers, whose names
4 * are too numerous to list here. Please refer to the COPYRIGHT
5 * file distributed with this source distribution.
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
26 #include "engines/metaengine.h"
27 #include "base/commandLine.h"
28 #include "base/plugins.h"
29 #include "base/version.h"
31 #include "common/config-manager.h"
32 #include "common/system.h"
33 #include "common/fs.h"
35 #include "sound/mididrv.h"
37 #include "gui/ThemeEngine.h"
39 #define DETECTOR_TESTING_HACK
40 #define UPGRADE_ALL_TARGETS_HACK
44 #ifndef DISABLE_COMMAND_LINE
46 static const char USAGE_STRING
[] =
48 "Usage: %s [OPTIONS]... [GAME]\n"
50 "Try '%s --help' for more options.\n"
53 // DONT FIXME: DO NOT ORDER ALPHABETICALLY, THIS IS ORDERED BY IMPORTANCE/CATEGORY! :)
54 #if defined(PALMOS_MODE) || defined(__SYMBIAN32__) || defined(__GP32__)
55 static const char HELP_STRING
[] = "NoUsageString"; // save more data segment space
57 static const char HELP_STRING
[] =
58 "ScummVM - Graphical Adventure Game Interpreter\n"
59 "Usage: %s [OPTIONS]... [GAME]\n"
60 " -v, --version Display ScummVM version information and exit\n"
61 " -h, --help Display a brief help text and exit\n"
62 " -z, --list-games Display list of supported games and exit\n"
63 " -t, --list-targets Display list of configured targets and exit\n"
64 " --list-saves=TARGET Display a list of savegames for the game (TARGET) specified\n"
66 " -c, --config=CONFIG Use alternate configuration file\n"
67 " -p, --path=PATH Path to where the game is installed\n"
68 " -x, --save-slot[=NUM] Save game slot to load (default: autosave)\n"
69 " -f, --fullscreen Force full-screen mode\n"
70 " -F, --no-fullscreen Force windowed mode\n"
71 " -g, --gfx-mode=MODE Select graphics scaler (1x,2x,3x,2xsai,super2xsai,\n"
72 " supereagle,advmame2x,advmame3x,hq2x,hq3x,tv2x,\n"
74 " --gui-theme=THEME Select GUI theme\n"
75 " --themepath=PATH Path to where GUI themes are stored\n"
76 " --list-themes Display list of all usable GUI themes\n"
77 " -e, --music-driver=MODE Select music driver (see README for details)\n"
78 " -q, --language=LANG Select language (en,de,fr,it,pt,es,jp,zh,kr,se,gb,\n"
80 " -m, --music-volume=NUM Set the music volume, 0-255 (default: 192)\n"
81 " -s, --sfx-volume=NUM Set the sfx volume, 0-255 (default: 192)\n"
82 " -r, --speech-volume=NUM Set the speech volume, 0-255 (default: 192)\n"
83 " --midi-gain=NUM Set the gain for MIDI playback, 0-1000 (default:\n"
84 " 100) (only supported by some MIDI drivers)\n"
85 " -n, --subtitles Enable subtitles (use with games that have voice)\n"
86 " -b, --boot-param=NUM Pass number to the boot script (boot param)\n"
87 " -d, --debuglevel=NUM Set debug verbosity level\n"
88 " --debugflags=FLAGS Enable engine specific debug flags\n"
89 " (separated by commas)\n"
90 " -u, --dump-scripts Enable script dumping if a directory called 'dumps'\n"
91 " exists in the current directory\n"
93 " --cdrom=NUM CD drive to play CD audio from (default: 0 = first\n"
95 " --joystick[=NUM] Enable joystick input (default: 0 = first joystick)\n"
96 " --platform=WORD Specify platform of game (allowed values: 2gs, 3do,\n"
97 " acorn, amiga, atari, c64, fmtowns, nes, mac, pc, pc98,\n"
98 " pce, segacd, wii, windows)\n"
99 " --savepath=PATH Path to where savegames are stored\n"
100 " --extrapath=PATH Extra path to additional game data\n"
101 " --soundfont=FILE Select the SoundFont for MIDI playback (only\n"
102 " supported by some MIDI drivers)\n"
103 " --multi-midi Enable combination AdLib and native MIDI\n"
104 " --native-mt32 True Roland MT-32 (disable GM emulation)\n"
105 " --enable-gs Enable Roland GS mode for MIDI playback\n"
106 " --output-rate=RATE Select output sample rate in Hz (e.g. 22050)\n"
107 " --opl-driver=DRIVER Select AdLib (OPL) emulator (db, mame)\n"
108 " --aspect-ratio Enable aspect ratio correction\n"
109 " --render-mode=MODE Enable additional render modes (cga, ega, hercGreen,\n"
110 " hercAmber, amiga)\n"
112 #if defined(ENABLE_SKY) || defined(ENABLE_QUEEN)
113 " --alt-intro Use alternative intro for CD versions of Beneath a\n"
114 " Steel Sky and Flight of the Amazon Queen\n"
116 " --copy-protection Enable copy protection in SCUMM games, when\n"
117 " ScummVM disables it by default.\n"
118 " --talkspeed=NUM Set talk speed for games (default: 60)\n"
119 #if defined(ENABLE_SCUMM) || defined(ENABLE_GROOVIE)
120 " --demo-mode Start demo mode of Maniac Mansion or The 7th Guest\n"
123 " --tempo=NUM Set music tempo (in percent, 50-200) for SCUMM games\n"
127 "The meaning of boolean long options can be inverted by prefixing them with\n"
128 "\"no-\", e.g. \"--no-aspect-ratio\".\n"
132 static const char *s_appName
= "scummvm";
134 static void usage(const char *s
, ...) GCC_PRINTF(1, 2);
136 static void usage(const char *s
, ...) {
137 char buf
[STRINGBUFLEN
];
141 vsnprintf(buf
, STRINGBUFLEN
, s
, va
);
144 #if !(defined(__GP32__) || defined (__SYMBIAN32__))
145 printf(USAGE_STRING
, s_appName
, buf
, s_appName
, s_appName
);
150 #endif // DISABLE_COMMAND_LINE
153 void registerDefaults() {
156 ConfMan
.registerDefault("fullscreen", false);
157 ConfMan
.registerDefault("aspect_ratio", false);
158 ConfMan
.registerDefault("gfx_mode", "normal");
159 ConfMan
.registerDefault("render_mode", "default");
162 ConfMan
.registerDefault("music_volume", 192);
163 ConfMan
.registerDefault("sfx_volume", 192);
164 ConfMan
.registerDefault("speech_volume", 192);
166 ConfMan
.registerDefault("music_mute", false);
167 ConfMan
.registerDefault("sfx_mute", false);
168 ConfMan
.registerDefault("speech_mute", false);
170 ConfMan
.registerDefault("multi_midi", false);
171 ConfMan
.registerDefault("native_mt32", false);
172 ConfMan
.registerDefault("enable_gs", false);
173 ConfMan
.registerDefault("midi_gain", 100);
174 // ConfMan.registerDefault("music_driver", ???);
176 ConfMan
.registerDefault("cdrom", 0);
179 ConfMan
.registerDefault("path", "");
180 ConfMan
.registerDefault("platform", Common::kPlatformPC
);
181 ConfMan
.registerDefault("language", "en");
182 ConfMan
.registerDefault("subtitles", false);
183 ConfMan
.registerDefault("boot_param", 0);
184 ConfMan
.registerDefault("dump_scripts", false);
185 ConfMan
.registerDefault("save_slot", -1);
186 ConfMan
.registerDefault("autosave_period", 5 * 60); // By default, trigger autosave every 5 minutes
188 #if defined(ENABLE_SCUMM) || defined(ENABLE_SWORD2)
189 ConfMan
.registerDefault("object_labels", true);
192 ConfMan
.registerDefault("copy_protection", false);
193 ConfMan
.registerDefault("talkspeed", 60);
195 #if defined(ENABLE_SCUMM) || defined(ENABLE_GROOVIE)
196 ConfMan
.registerDefault("demo_mode", false);
199 ConfMan
.registerDefault("tempo", 0);
202 #if defined(ENABLE_SKY) || defined(ENABLE_QUEEN)
203 ConfMan
.registerDefault("alt_intro", false);
207 ConfMan
.registerDefault("joystick_num", -1);
208 ConfMan
.registerDefault("confirm_exit", false);
209 ConfMan
.registerDefault("disable_sdl_parachute", false);
211 ConfMan
.registerDefault("record_mode", "none");
212 ConfMan
.registerDefault("record_file_name", "record.bin");
213 ConfMan
.registerDefault("record_temp_file_name", "record.tmp");
214 ConfMan
.registerDefault("record_time_file_name", "record.time");
218 // Various macros used by the command line parser.
221 #ifndef DISABLE_COMMAND_LINE
223 // Use this for options which have an *optional* value
224 #define DO_OPTION_OPT(shortCmd, longCmd, defaultVal) \
225 if (isLongCmd ? (!strcmp(s+2, longCmd) || !memcmp(s+2, longCmd"=", sizeof(longCmd"=") - 1)) : (tolower(s[1]) == shortCmd)) { \
228 s += sizeof(longCmd) - 1; \
232 const char *option = s; \
233 if (*s == '\0' && !isLongCmd) { option = s2; i++; } \
234 if (!option || *option == '\0') option = defaultVal; \
235 if (option) settings[longCmd] = option;
237 // Use this for options which have a required (string) value
238 #define DO_OPTION(shortCmd, longCmd) \
239 DO_OPTION_OPT(shortCmd, longCmd, 0) \
240 if (!option) usage("Option '%s' requires an argument", argv[isLongCmd ? i : i-1]);
242 // Use this for options which have a required integer value
243 #define DO_OPTION_INT(shortCmd, longCmd) \
244 DO_OPTION(shortCmd, longCmd) \
246 int intValue; intValue = (int)strtol(option, &endptr, 0); \
247 if (endptr == NULL || *endptr != 0) usage("--%s: Invalid number '%s'", longCmd, option);
249 // Use this for boolean options; this distinguishes between "-x" and "-X",
250 // resp. between "--some-option" and "--no-some-option".
251 #define DO_OPTION_BOOL(shortCmd, longCmd) \
252 if (isLongCmd ? (!strcmp(s+2, longCmd) || !strcmp(s+2, "no-"longCmd)) : (tolower(s[1]) == shortCmd)) { \
253 bool boolValue = (islower(s[1]) != 0); \
256 boolValue = !strcmp(s, longCmd); \
257 s += boolValue ? (sizeof(longCmd) - 1) : (sizeof("no-"longCmd) - 1); \
259 if (*s != '\0') goto unknownOption; \
260 const char *option = boolValue ? "true" : "false"; \
261 settings[longCmd] = option;
263 // Use this for options which never have a value, i.e. for 'commands', like "--help".
264 #define DO_COMMAND(shortCmd, longCmd) \
265 if (isLongCmd ? (!strcmp(s+2, longCmd)) : (tolower(s[1]) == shortCmd)) { \
268 s += sizeof(longCmd) - 1; \
269 if (*s != '\0') goto unknownOption; \
273 #define DO_LONG_OPTION_OPT(longCmd, d) DO_OPTION_OPT(0, longCmd, d)
274 #define DO_LONG_OPTION(longCmd) DO_OPTION(0, longCmd)
275 #define DO_LONG_OPTION_INT(longCmd) DO_OPTION_INT(0, longCmd)
276 #define DO_LONG_OPTION_BOOL(longCmd) DO_OPTION_BOOL(0, longCmd)
277 #define DO_LONG_COMMAND(longCmd) DO_COMMAND(0, longCmd)
279 // End an option handler
285 Common::String
parseCommandLine(Common::StringMap
&settings
, int argc
, const char * const *argv
) {
288 // argv[0] contains the name of the executable.
289 if (argv
&& argv
[0]) {
290 s
= strrchr(argv
[0], '/');
291 s_appName
= s
? (s
+1) : argv
[0];
294 // We store all command line settings into a string map.
296 // Iterate over all command line arguments and parse them into our string map.
297 for (int i
= 1; i
< argc
; ++i
) {
299 s2
= (i
< argc
-1) ? argv
[i
+1] : 0;
302 // The argument doesn't start with a dash, so it's not an option.
303 // Hence it must be the target name. We currently enforce that
304 // this always comes last.
306 usage("Stray argument '%s'", s
);
308 // We defer checking whether this is a valid target to a later point.
312 bool isLongCmd
= (s
[0] == '-' && s
[1] == '-');
314 DO_COMMAND('h', "help")
317 DO_COMMAND('v', "version")
320 DO_COMMAND('t', "list-targets")
323 DO_COMMAND('z', "list-games")
326 #ifdef DETECTOR_TESTING_HACK
327 // HACK FIXME TODO: This command is intentionally *not* documented!
328 DO_LONG_COMMAND("test-detector")
332 #ifdef UPGRADE_ALL_TARGETS_HACK
333 // HACK FIXME TODO: This command is intentionally *not* documented!
334 DO_LONG_COMMAND("upgrade-targets")
338 DO_LONG_OPTION("list-saves")
339 // FIXME: Need to document this.
340 // TODO: Make the argument optional. If no argument is given, list all savegames
341 // for all configured targets.
345 DO_OPTION('c', "config")
348 DO_OPTION_INT('b', "boot-param")
351 DO_OPTION_OPT('d', "debuglevel", "0")
354 DO_LONG_OPTION("debugflags")
357 DO_OPTION('e', "music-driver")
358 if (MidiDriver::findMusicDriver(option
) == 0)
359 usage("Unrecognized music driver '%s'", option
);
362 DO_LONG_OPTION_INT("output-rate")
365 DO_OPTION_BOOL('f', "fullscreen")
368 DO_LONG_OPTION("opl-driver")
371 DO_OPTION('g', "gfx-mode")
372 // Check whether 'option' specifies a valid graphics mode.
373 bool isValid
= false;
374 if (!scumm_stricmp(option
, "normal") || !scumm_stricmp(option
, "default"))
377 const OSystem::GraphicsMode
*gm
= g_system
->getSupportedGraphicsModes();
378 while (gm
->name
&& !isValid
) {
379 isValid
= !scumm_stricmp(gm
->name
, option
);
384 usage("Unrecognized graphics mode '%s'", option
);
387 DO_OPTION_INT('m', "music-volume")
390 DO_OPTION_BOOL('n', "subtitles")
393 DO_OPTION('p', "path")
394 Common::FSNode
path(option
);
395 if (!path
.exists()) {
396 usage("Non-existent game path '%s'", option
);
397 } else if (!path
.isReadable()) {
398 usage("Non-readable game path '%s'", option
);
402 DO_OPTION('q', "language")
403 if (Common::parseLanguage(option
) == Common::UNK_LANG
)
404 usage("Unrecognized language '%s'", option
);
407 DO_OPTION_INT('s', "sfx-volume")
410 DO_OPTION_INT('r', "speech-volume")
413 DO_LONG_OPTION_INT("midi-gain")
416 DO_OPTION_BOOL('u', "dump-scripts")
419 DO_OPTION_OPT('x', "save-slot", "0")
422 DO_LONG_OPTION_INT("cdrom")
425 DO_LONG_OPTION_OPT("joystick", "0")
426 settings
["joystick_num"] = option
;
427 settings
.erase("joystick");
430 DO_LONG_OPTION("platform")
431 int platform
= Common::parsePlatform(option
);
432 if (platform
== Common::kPlatformUnknown
)
433 usage("Unrecognized platform '%s'", option
);
436 DO_LONG_OPTION("soundfont")
437 Common::FSNode
path(option
);
438 if (!path
.exists()) {
439 usage("Non-existent soundfont path '%s'", option
);
440 } else if (!path
.isReadable()) {
441 usage("Non-readable soundfont path '%s'", option
);
445 DO_LONG_OPTION_BOOL("disable-sdl-parachute")
448 DO_LONG_OPTION_BOOL("multi-midi")
451 DO_LONG_OPTION_BOOL("native-mt32")
454 DO_LONG_OPTION_BOOL("enable-gs")
457 DO_LONG_OPTION_BOOL("aspect-ratio")
460 DO_LONG_OPTION("render-mode")
461 int renderMode
= Common::parseRenderMode(option
);
462 if (renderMode
== Common::kRenderDefault
)
463 usage("Unrecognized render mode '%s'", option
);
466 DO_LONG_OPTION("savepath")
467 Common::FSNode
path(option
);
468 if (!path
.exists()) {
469 usage("Non-existent savegames path '%s'", option
);
470 } else if (!path
.isWritable()) {
471 usage("Non-writable savegames path '%s'", option
);
475 DO_LONG_OPTION("extrapath")
476 Common::FSNode
path(option
);
477 if (!path
.exists()) {
478 usage("Non-existent extra path '%s'", option
);
479 } else if (!path
.isReadable()) {
480 usage("Non-readable extra path '%s'", option
);
484 DO_LONG_OPTION_INT("talkspeed")
487 DO_LONG_OPTION_BOOL("copy-protection")
490 DO_LONG_OPTION("gui-theme")
493 DO_LONG_OPTION("themepath")
494 Common::FSNode
path(option
);
495 if (!path
.exists()) {
496 usage("Non-existent theme path '%s'", option
);
497 } else if (!path
.isReadable()) {
498 usage("Non-readable theme path '%s'", option
);
502 DO_LONG_COMMAND("list-themes")
505 DO_LONG_OPTION("target-md5")
509 DO_LONG_OPTION_INT("tempo")
512 #if defined(ENABLE_SCUMM) || defined(ENABLE_GROOVIE)
513 DO_LONG_OPTION_BOOL("demo-mode")
517 #if defined(ENABLE_SKY) || defined(ENABLE_QUEEN)
518 DO_LONG_OPTION_BOOL("alt-intro")
522 DO_LONG_OPTION("record-mode")
525 DO_LONG_OPTION("record-file-name")
528 DO_LONG_OPTION("record-temp-file-name")
531 DO_LONG_OPTION("record-time-file-name")
535 // This is automatically set when launched from the Springboard.
536 DO_LONG_OPTION_OPT("launchedFromSB", 0)
541 // If we get till here, the option is unhandled and hence unknown.
542 usage("Unrecognized option '%s'", argv
[i
]);
546 return Common::String();
549 /** List all supported game IDs, i.e. all games which any loaded plugin supports. */
550 static void listGames() {
551 printf("Game ID Full Title \n"
552 "-------------------- ------------------------------------------------------\n");
554 const EnginePlugin::List
&plugins
= EngineMan
.getPlugins();
555 EnginePlugin::List::const_iterator iter
= plugins
.begin();
556 for (iter
= plugins
.begin(); iter
!= plugins
.end(); ++iter
) {
557 GameList list
= (**iter
)->getSupportedGames();
558 for (GameList::iterator v
= list
.begin(); v
!= list
.end(); ++v
) {
559 printf("%-20s %s\n", v
->gameid().c_str(), v
->description().c_str());
564 /** List all targets which are configured in the config file. */
565 static void listTargets() {
566 printf("Target Description \n"
567 "-------------------- ------------------------------------------------------\n");
569 using namespace Common
;
570 const ConfigManager::DomainMap
&domains
= ConfMan
.getGameDomains();
571 ConfigManager::DomainMap::const_iterator iter
;
572 for (iter
= domains
.begin(); iter
!= domains
.end(); ++iter
) {
573 Common::String
name(iter
->_key
);
574 Common::String
description(iter
->_value
.get("description"));
576 if (description
.empty()) {
577 // FIXME: At this point, we should check for a "gameid" override
578 // to find the proper desc. In fact, the platform probably should
579 // be taken into account, too.
580 Common::String
gameid(name
);
581 GameDescriptor g
= EngineMan
.findGame(gameid
);
582 if (g
.description().size() > 0)
583 description
= g
.description();
586 printf("%-20s %s\n", name
.c_str(), description
.c_str());
591 /** List all saves states for the given target. */
592 static void listSaves(const char *target
) {
594 g_system
->initBackend();
596 // Grab the "target" domain, if any
597 const Common::ConfigManager::Domain
*domain
= ConfMan
.getDomain(target
);
599 // Set up the game domain as newly active domain, so
600 // target specific savepath will be checked
601 Common::String oldDomain
= ConfMan
.getActiveDomainName();
602 ConfMan
.setActiveDomain(target
);
604 // Grab the gameid from the domain resp. use the target as gameid
605 Common::String gameid
;
607 gameid
= domain
->get("gameid");
610 gameid
.toLowercase(); // Normalize it to lower case
612 // Find the plugin that will handle the specified gameid
613 const EnginePlugin
*plugin
= 0;
614 GameDescriptor game
= EngineMan
.findGame(gameid
, &plugin
);
617 error("Could not find any plugin to handle gameid '%s' (target '%s')", gameid
.c_str(), target
);
621 // Query the plugin for a list of savegames
622 SaveStateList saveList
= (*plugin
)->listSaves(target
);
624 // TODO: Include more info about the target (desc, engine name, ...) ???
625 printf("Saves for target '%s':\n", target
);
626 printf(" Slot Description \n"
627 " ---- ------------------------------------------------------\n");
629 for (SaveStateList::const_iterator x
= saveList
.begin(); x
!= saveList
.end(); ++x
) {
630 printf(" %-4s %s\n", x
->save_slot().c_str(), x
->description().c_str());
631 // TODO: Could also iterate over the full hashmap, printing all key-value pairs
634 // Revert to the old active domain
635 ConfMan
.setActiveDomain(oldDomain
);
638 /** Lists all usable themes */
639 static void listThemes() {
640 typedef Common::List
<GUI::ThemeEngine::ThemeDescriptor
> ThList
;
642 GUI::ThemeEngine::listUsableThemes(thList
);
644 printf("Theme Description\n");
645 printf("-------------- ------------------------------------------------\n");
647 for (ThList::const_iterator i
= thList
.begin(); i
!= thList
.end(); ++i
)
648 printf("%-14s %s\n", i
->id
.c_str(), i
->name
.c_str());
652 #ifdef DETECTOR_TESTING_HACK
653 static void runDetectorTest() {
654 // HACK: The following code can be used to test the detection code of our
655 // engines. Basically, it loops over all targets, and calls the detector
656 // for the given path. It then prints out the result and also checks
657 // whether the result agrees with the settings of the target.
659 const Common::ConfigManager::DomainMap
&domains
= ConfMan
.getGameDomains();
660 Common::ConfigManager::DomainMap::const_iterator iter
= domains
.begin();
661 int success
= 0, failure
= 0;
662 for (iter
= domains
.begin(); iter
!= domains
.end(); ++iter
) {
663 Common::String
name(iter
->_key
);
664 Common::String
gameid(iter
->_value
.get("gameid"));
665 Common::String
path(iter
->_value
.get("path"));
666 printf("Looking at target '%s', gameid '%s', path '%s' ...\n",
667 name
.c_str(), gameid
.c_str(), path
.c_str());
669 printf(" ... no path specified, skipping\n");
672 if (gameid
.empty()) {
676 Common::FSNode
dir(path
);
677 Common::FSList files
;
678 if (!dir
.getChildren(files
, Common::FSNode::kListAll
)) {
679 printf(" ... invalid path, skipping\n");
683 GameList
candidates(EngineMan
.detectGames(files
));
684 bool gameidDiffers
= false;
685 GameList::iterator x
;
686 for (x
= candidates
.begin(); x
!= candidates
.end(); ++x
) {
687 gameidDiffers
|= (scumm_stricmp(gameid
.c_str(), x
->gameid().c_str()) != 0);
690 if (candidates
.empty()) {
691 printf(" FAILURE: No games detected\n");
693 } else if (candidates
.size() > 1) {
695 printf(" WARNING: Multiple games detected, some/all with wrong gameid\n");
697 printf(" WARNING: Multiple games detected, but all have matching gameid\n");
700 } else if (gameidDiffers
) {
701 printf(" FAILURE: Wrong gameid detected\n");
704 printf(" SUCCESS: Game was detected correctly\n");
708 for (x
= candidates
.begin(); x
!= candidates
.end(); ++x
) {
709 printf(" gameid '%s', desc '%s', language '%s', platform '%s'\n",
711 x
->description().c_str(),
712 Common::getLanguageCode(x
->language()),
713 Common::getPlatformCode(x
->platform()));
716 int total
= domains
.size();
717 printf("Detector test run: %d fail, %d success, %d skipped, out of %d\n",
718 failure
, success
, total
- failure
- success
, total
);
722 #ifdef UPGRADE_ALL_TARGETS_HACK
723 void upgradeTargets() {
724 // HACK: The following upgrades all your targets to the latest and
725 // greatest. Right now that means updating the guioptions and (optionally)
726 // also the game descriptions.
727 // Basically, it loops over all targets, and calls the detector for the
728 // given path. It then compares the result with the settings of the target.
729 // If the basics seem to match, it updates the guioptions.
731 printf("Upgrading all your existing targets\n");
733 Common::ConfigManager::DomainMap
&domains
= ConfMan
.getGameDomains();
734 Common::ConfigManager::DomainMap::iterator iter
= domains
.begin();
735 for (iter
= domains
.begin(); iter
!= domains
.end(); ++iter
) {
736 Common::ConfigManager::Domain
&dom
= iter
->_value
;
737 Common::String
name(iter
->_key
);
738 Common::String
gameid(dom
.get("gameid"));
739 Common::String
path(dom
.get("path"));
740 printf("Looking at target '%s', gameid '%s' ...\n",
741 name
.c_str(), gameid
.c_str());
743 printf(" ... no path specified, skipping\n");
746 if (gameid
.empty()) {
749 gameid
.toLowercase(); // TODO: Is this paranoia? Maybe we should just assume all lowercase, always?
751 Common::FSNode
dir(path
);
752 Common::FSList files
;
753 if (!dir
.getChildren(files
, Common::FSNode::kListAll
)) {
754 printf(" ... invalid path, skipping\n");
758 Common::Language lang
= Common::parseLanguage(dom
.get("language"));
759 Common::Platform plat
= Common::parsePlatform(dom
.get("platform"));
760 Common::String
desc(dom
.get("description"));
762 GameList
candidates(EngineMan
.detectGames(files
));
763 GameDescriptor
*g
= 0;
765 // We proceed as follows:
766 // * If detection failed to produce candidates, skip.
767 // * If there is a unique detector match, trust it.
768 // * If there are multiple match, run over them comparing gameid, language and platform.
769 // If we end up with a unique match, use it. Otherwise, skip.
770 if (candidates
.size() == 0) {
771 printf(" ... failed to detect game, skipping\n");
774 if (candidates
.size() > 1) {
775 // Scan over all candidates, check if there is a unique match for gameid, language and platform
776 GameList::iterator x
;
777 int matchesFound
= 0;
778 for (x
= candidates
.begin(); x
!= candidates
.end(); ++x
) {
779 if (x
->gameid() == gameid
&& x
->language() == lang
&& x
->platform() == plat
) {
784 if (matchesFound
!= 1) {
785 printf(" ... detected multiple games, could not establish unique match, skipping\n");
789 // Unique match -> use it
793 // At this point, g points to a GameDescriptor which we can use to update
794 // the target referred to by dom. We update several things
796 // Always set the gameid explicitly (in case of legacy targets)
797 dom
["gameid"] = g
->gameid();
799 // Always set the GUI options. The user should not modify them, and engines might
800 // gain more features over time, so we want to keep this list up-to-date.
801 if (g
->contains("guioptions")) {
802 printf(" -> update guioptions to '%s'\n", (*g
)["guioptions"].c_str());
803 dom
["guioptions"] = (*g
)["guioptions"];
804 } else if (dom
.contains("guioptions")) {
805 dom
.erase("guioptions");
808 // Update the language setting but only if none has been set yet.
809 if (lang
== Common::UNK_LANG
&& g
->language() != Common::UNK_LANG
) {
810 printf(" -> set language to '%s'\n", Common::getLanguageCode(g
->language()));
811 dom
["language"] = (*g
)["language"];
814 // Update the platform setting but only if none has been set yet.
815 if (plat
== Common::kPlatformUnknown
&& g
->platform() != Common::kPlatformUnknown
) {
816 printf(" -> set platform to '%s'\n", Common::getPlatformCode(g
->platform()));
817 dom
["platform"] = (*g
)["platform"];
820 // TODO: We could also update the description. But not everybody will want that.
821 // Esp. because for some games (e.g. the combined Zak/Loom FM-TOWNS demo etc.)
822 // ScummVM still generates an incorrect description string. So, the description
823 // should only be updated if the user explicitly requests this.
825 if (desc
!= g
->description()) {
826 printf(" -> update desc from '%s' to\n '%s' ?\n", desc
.c_str(), g
->description().c_str());
827 dom
["description"] = (*g
)["description"];
832 // Finally, save our changes to disk
833 ConfMan
.flushToDisk();
837 #else // DISABLE_COMMAND_LINE
840 Common::String
parseCommandLine(Common::StringMap
&settings
, int argc
, const char * const *argv
) {
841 return Common::String();
845 #endif // DISABLE_COMMAND_LINE
848 bool processSettings(Common::String
&command
, Common::StringMap
&settings
) {
850 #ifndef DISABLE_COMMAND_LINE
852 // Handle commands passed via the command line (like --list-targets and
853 // --list-games). This must be done after the config file and the plugins
855 if (command
== "list-targets") {
858 } else if (command
== "list-games") {
861 } else if (command
== "list-saves") {
862 listSaves(settings
["list-saves"].c_str());
864 } else if (command
== "list-themes") {
867 } else if (command
== "version") {
868 printf("%s\n", gScummVMFullVersion
);
869 printf("Features compiled in: %s\n", gScummVMFeatures
);
871 } else if (command
== "help") {
872 printf(HELP_STRING
, s_appName
);
875 #ifdef DETECTOR_TESTING_HACK
876 else if (command
== "test-detector") {
881 #ifdef UPGRADE_ALL_TARGETS_HACK
882 else if (command
== "upgrade-targets") {
888 #endif // DISABLE_COMMAND_LINE
891 // If a target was specified, check whether there is either a game
892 // domain (i.e. a target) matching this argument, or alternatively
893 // whether there is a gameid matching that name.
894 if (!command
.empty()) {
895 GameDescriptor gd
= EngineMan
.findGame(command
);
896 if (ConfMan
.hasGameDomain(command
) || !gd
.gameid().empty()) {
897 bool idCameFromCommandLine
= false;
899 // WORKAROUND: Fix for bug #1719463: "DETECTOR: Launching
900 // undefined target adds launcher entry"
902 // We designate gameids which come strictly from command line
903 // so AdvancedDetector will not save config file with invalid
904 // gameid in case target autoupgrade was performed
905 if (!ConfMan
.hasGameDomain(command
)) {
906 idCameFromCommandLine
= true;
909 ConfMan
.setActiveDomain(command
);
911 if (idCameFromCommandLine
)
912 ConfMan
.set("id_came_from_command_line", "1");
915 #ifndef DISABLE_COMMAND_LINE
916 usage("Unrecognized game target '%s'", command
.c_str());
917 #endif // DISABLE_COMMAND_LINE
922 // The user can override the savepath with the SCUMMVM_SAVEPATH
923 // environment variable. This is weaker than a --savepath on the
924 // command line, but overrides the default savepath, hence it is
925 // handled here, just before the command line gets parsed.
926 #if !defined(MACOS_CARBON) && !defined(_WIN32_WCE) && !defined(PALMOS_MODE) && !defined(__GP32__)
927 if (!settings
.contains("savepath")) {
928 const char *dir
= getenv("SCUMMVM_SAVEPATH");
929 if (dir
&& *dir
&& strlen(dir
) < MAXPATHLEN
) {
930 Common::FSNode
saveDir(dir
);
931 if (!saveDir
.exists()) {
932 warning("Non-existent SCUMMVM_SAVEPATH save path. It will be ignored.");
933 } else if (!saveDir
.isWritable()) {
934 warning("Non-writable SCUMMVM_SAVEPATH save path. It will be ignored.");
936 settings
["savepath"] = dir
;
942 // Finally, store the command line settings into the config manager.
943 for (Common::StringMap::const_iterator x
= settings
.begin(); x
!= settings
.end(); ++x
) {
944 Common::String
key(x
->_key
);
945 Common::String
value(x
->_value
);
947 // Replace any "-" in the key by "_" (e.g. change "save-slot" to "save_slot").
948 for (Common::String::iterator c
= key
.begin(); c
!= key
.end(); ++c
)
952 // Store it into ConfMan.
953 ConfMan
.set(key
, value
, Common::ConfigManager::kTransientDomain
);
959 } // End of namespace Base