Speech bubbles can point down right.
[scummvm-innocent.git] / base / commandLine.cpp
blob890d14db92c77787e3b26383cb17d95fae877884
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.
21 * $URL$
22 * $Id$
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
42 namespace Base {
44 #ifndef DISABLE_COMMAND_LINE
46 static const char USAGE_STRING[] =
47 "%s: %s\n"
48 "Usage: %s [OPTIONS]... [GAME]\n"
49 "\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
56 #else
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"
65 "\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"
73 " dotmatrix)\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"
79 " hb,ru,cz)\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"
92 "\n"
93 " --cdrom=NUM CD drive to play CD audio from (default: 0 = first\n"
94 " drive)\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"
111 "\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"
115 #endif
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"
121 #endif
122 #ifdef ENABLE_SCUMM
123 " --tempo=NUM Set music tempo (in percent, 50-200) for SCUMM games\n"
124 " (default: 100)\n"
125 #endif
126 "\n"
127 "The meaning of boolean long options can be inverted by prefixing them with\n"
128 "\"no-\", e.g. \"--no-aspect-ratio\".\n"
130 #endif
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];
138 va_list va;
140 va_start(va, s);
141 vsnprintf(buf, STRINGBUFLEN, s, va);
142 va_end(va);
144 #if !(defined(__GP32__) || defined (__SYMBIAN32__))
145 printf(USAGE_STRING, s_appName, buf, s_appName, s_appName);
146 #endif
147 exit(1);
150 #endif // DISABLE_COMMAND_LINE
153 void registerDefaults() {
155 // Graphics
156 ConfMan.registerDefault("fullscreen", false);
157 ConfMan.registerDefault("aspect_ratio", false);
158 ConfMan.registerDefault("gfx_mode", "normal");
159 ConfMan.registerDefault("render_mode", "default");
161 // Sound & Music
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);
178 // Game specific
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);
190 #endif
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);
197 #endif
198 #ifdef ENABLE_SCUMM
199 ConfMan.registerDefault("tempo", 0);
200 #endif
202 #if defined(ENABLE_SKY) || defined(ENABLE_QUEEN)
203 ConfMan.registerDefault("alt_intro", false);
204 #endif
206 // Miscellaneous
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)) { \
226 s += 2; \
227 if (isLongCmd) { \
228 s += sizeof(longCmd) - 1; \
229 if (*s == '=') \
230 s++; \
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) \
245 char *endptr = 0; \
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); \
254 s += 2; \
255 if (isLongCmd) { \
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)) { \
266 s += 2; \
267 if (isLongCmd) \
268 s += sizeof(longCmd) - 1; \
269 if (*s != '\0') goto unknownOption; \
270 return longCmd;
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
280 #define END_OPTION \
281 continue; \
285 Common::String parseCommandLine(Common::StringMap &settings, int argc, const char * const *argv) {
286 const char *s, *s2;
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) {
298 s = argv[i];
299 s2 = (i < argc-1) ? argv[i+1] : 0;
301 if (s[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.
305 if (i != argc - 1)
306 usage("Stray argument '%s'", s);
308 // We defer checking whether this is a valid target to a later point.
309 return s;
310 } else {
312 bool isLongCmd = (s[0] == '-' && s[1] == '-');
314 DO_COMMAND('h', "help")
315 END_OPTION
317 DO_COMMAND('v', "version")
318 END_OPTION
320 DO_COMMAND('t', "list-targets")
321 END_OPTION
323 DO_COMMAND('z', "list-games")
324 END_OPTION
326 #ifdef DETECTOR_TESTING_HACK
327 // HACK FIXME TODO: This command is intentionally *not* documented!
328 DO_LONG_COMMAND("test-detector")
329 END_OPTION
330 #endif
332 #ifdef UPGRADE_ALL_TARGETS_HACK
333 // HACK FIXME TODO: This command is intentionally *not* documented!
334 DO_LONG_COMMAND("upgrade-targets")
335 END_OPTION
336 #endif
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.
342 return "list-saves";
343 END_OPTION
345 DO_OPTION('c', "config")
346 END_OPTION
348 DO_OPTION_INT('b', "boot-param")
349 END_OPTION
351 DO_OPTION_OPT('d', "debuglevel", "0")
352 END_OPTION
354 DO_LONG_OPTION("debugflags")
355 END_OPTION
357 DO_OPTION('e', "music-driver")
358 if (MidiDriver::findMusicDriver(option) == 0)
359 usage("Unrecognized music driver '%s'", option);
360 END_OPTION
362 DO_LONG_OPTION_INT("output-rate")
363 END_OPTION
365 DO_OPTION_BOOL('f', "fullscreen")
366 END_OPTION
368 DO_LONG_OPTION("opl-driver")
369 END_OPTION
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"))
375 isValid = true;
376 if (!isValid) {
377 const OSystem::GraphicsMode *gm = g_system->getSupportedGraphicsModes();
378 while (gm->name && !isValid) {
379 isValid = !scumm_stricmp(gm->name, option);
380 gm++;
383 if (!isValid)
384 usage("Unrecognized graphics mode '%s'", option);
385 END_OPTION
387 DO_OPTION_INT('m', "music-volume")
388 END_OPTION
390 DO_OPTION_BOOL('n', "subtitles")
391 END_OPTION
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);
400 END_OPTION
402 DO_OPTION('q', "language")
403 if (Common::parseLanguage(option) == Common::UNK_LANG)
404 usage("Unrecognized language '%s'", option);
405 END_OPTION
407 DO_OPTION_INT('s', "sfx-volume")
408 END_OPTION
410 DO_OPTION_INT('r', "speech-volume")
411 END_OPTION
413 DO_LONG_OPTION_INT("midi-gain")
414 END_OPTION
416 DO_OPTION_BOOL('u', "dump-scripts")
417 END_OPTION
419 DO_OPTION_OPT('x', "save-slot", "0")
420 END_OPTION
422 DO_LONG_OPTION_INT("cdrom")
423 END_OPTION
425 DO_LONG_OPTION_OPT("joystick", "0")
426 settings["joystick_num"] = option;
427 settings.erase("joystick");
428 END_OPTION
430 DO_LONG_OPTION("platform")
431 int platform = Common::parsePlatform(option);
432 if (platform == Common::kPlatformUnknown)
433 usage("Unrecognized platform '%s'", option);
434 END_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);
443 END_OPTION
445 DO_LONG_OPTION_BOOL("disable-sdl-parachute")
446 END_OPTION
448 DO_LONG_OPTION_BOOL("multi-midi")
449 END_OPTION
451 DO_LONG_OPTION_BOOL("native-mt32")
452 END_OPTION
454 DO_LONG_OPTION_BOOL("enable-gs")
455 END_OPTION
457 DO_LONG_OPTION_BOOL("aspect-ratio")
458 END_OPTION
460 DO_LONG_OPTION("render-mode")
461 int renderMode = Common::parseRenderMode(option);
462 if (renderMode == Common::kRenderDefault)
463 usage("Unrecognized render mode '%s'", option);
464 END_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);
473 END_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);
482 END_OPTION
484 DO_LONG_OPTION_INT("talkspeed")
485 END_OPTION
487 DO_LONG_OPTION_BOOL("copy-protection")
488 END_OPTION
490 DO_LONG_OPTION("gui-theme")
491 END_OPTION
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);
500 END_OPTION
502 DO_LONG_COMMAND("list-themes")
503 END_OPTION
505 DO_LONG_OPTION("target-md5")
506 END_OPTION
508 #ifdef ENABLE_SCUMM
509 DO_LONG_OPTION_INT("tempo")
510 END_OPTION
511 #endif
512 #if defined(ENABLE_SCUMM) || defined(ENABLE_GROOVIE)
513 DO_LONG_OPTION_BOOL("demo-mode")
514 END_OPTION
515 #endif
517 #if defined(ENABLE_SKY) || defined(ENABLE_QUEEN)
518 DO_LONG_OPTION_BOOL("alt-intro")
519 END_OPTION
520 #endif
522 DO_LONG_OPTION("record-mode")
523 END_OPTION
525 DO_LONG_OPTION("record-file-name")
526 END_OPTION
528 DO_LONG_OPTION("record-temp-file-name")
529 END_OPTION
531 DO_LONG_OPTION("record-time-file-name")
532 END_OPTION
534 #ifdef IPHONE
535 // This is automatically set when launched from the Springboard.
536 DO_LONG_OPTION_OPT("launchedFromSB", 0)
537 END_OPTION
538 #endif
540 unknownOption:
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) {
593 // FIXME HACK
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;
606 if (domain)
607 gameid = domain->get("gameid");
608 if (gameid.empty())
609 gameid = target;
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);
616 if (!plugin) {
617 error("Could not find any plugin to handle gameid '%s' (target '%s')", gameid.c_str(), target);
618 return;
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;
641 ThList 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());
668 if (path.empty()) {
669 printf(" ... no path specified, skipping\n");
670 continue;
672 if (gameid.empty()) {
673 gameid = name;
676 Common::FSNode dir(path);
677 Common::FSList files;
678 if (!dir.getChildren(files, Common::FSNode::kListAll)) {
679 printf(" ... invalid path, skipping\n");
680 continue;
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");
692 failure++;
693 } else if (candidates.size() > 1) {
694 if (gameidDiffers) {
695 printf(" WARNING: Multiple games detected, some/all with wrong gameid\n");
696 } else {
697 printf(" WARNING: Multiple games detected, but all have matching gameid\n");
699 failure++;
700 } else if (gameidDiffers) {
701 printf(" FAILURE: Wrong gameid detected\n");
702 failure++;
703 } else {
704 printf(" SUCCESS: Game was detected correctly\n");
705 success++;
708 for (x = candidates.begin(); x != candidates.end(); ++x) {
709 printf(" gameid '%s', desc '%s', language '%s', platform '%s'\n",
710 x->gameid().c_str(),
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);
720 #endif
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());
742 if (path.empty()) {
743 printf(" ... no path specified, skipping\n");
744 continue;
746 if (gameid.empty()) {
747 gameid = name;
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");
755 continue;
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");
772 continue;
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) {
780 matchesFound++;
781 g = &(*x);
784 if (matchesFound != 1) {
785 printf(" ... detected multiple games, could not establish unique match, skipping\n");
786 continue;
788 } else {
789 // Unique match -> use it
790 g = &candidates[0];
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.
824 #if 0
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"];
829 #endif
832 // Finally, save our changes to disk
833 ConfMan.flushToDisk();
835 #endif
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
854 // have been loaded.
855 if (command == "list-targets") {
856 listTargets();
857 return false;
858 } else if (command == "list-games") {
859 listGames();
860 return false;
861 } else if (command == "list-saves") {
862 listSaves(settings["list-saves"].c_str());
863 return false;
864 } else if (command == "list-themes") {
865 listThemes();
866 return false;
867 } else if (command == "version") {
868 printf("%s\n", gScummVMFullVersion);
869 printf("Features compiled in: %s\n", gScummVMFeatures);
870 return false;
871 } else if (command == "help") {
872 printf(HELP_STRING, s_appName);
873 return false;
875 #ifdef DETECTOR_TESTING_HACK
876 else if (command == "test-detector") {
877 runDetectorTest();
878 return false;
880 #endif
881 #ifdef UPGRADE_ALL_TARGETS_HACK
882 else if (command == "upgrade-targets") {
883 upgradeTargets();
884 return false;
886 #endif
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");
914 } else {
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.");
935 } else {
936 settings["savepath"] = dir;
940 #endif
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)
949 if (*c == '-')
950 *c = '_';
952 // Store it into ConfMan.
953 ConfMan.set(key, value, Common::ConfigManager::kTransientDomain);
956 return true;
959 } // End of namespace Base