//Copyright Paul Reiche, Fred Ford. 1992-2002 /* * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifdef HAVE_UNISTD_H # include #endif #ifdef HAVE_GETOPT_LONG # include #else # include "getopt/getopt.h" #endif #include #include #include "libs/graphics/gfx_common.h" #include "libs/graphics/cmap.h" #include "libs/sound/sound.h" #include "libs/input/input_common.h" #include "libs/inplib.h" #include "libs/tasklib.h" #include "uqm/controls.h" #include "uqm/battle.h" // For BATTLE_FRAME_RATE #include "libs/file.h" #include "types.h" #include "port.h" #include "libs/memlib.h" #include "libs/platform.h" #include "libs/log.h" #include "options.h" #include "uqmversion.h" #include "uqm/comm.h" #ifdef NETPLAY # include "libs/callback.h" # include "libs/alarm.h" # include "libs/net.h" # include "uqm/supermelee/netplay/netoptions.h" # include "uqm/supermelee/netplay/netplay.h" #endif #include "uqm/setup.h" #include "uqm/starcon.h" #if defined (GFXMODULE_SDL) # include SDL_INCLUDE(SDL.h) // Including this is actually necessary on OSX. #endif struct bool_option { bool value; bool set; }; struct int_option { int value; bool set; }; struct float_option { float value; bool set; }; struct options_struct { #define DECL_CONFIG_OPTION(type, name) \ struct type##_option name #define DECL_CONFIG_OPTION2(type, name, val1, val2) \ struct { type val1; type val2; bool set; } name // Commandline-only options const char *logFile; enum { runMode_normal, runMode_usage, runMode_version, } runMode; const char *configDir; const char *contentDir; const char *addonDir; const char **addons; int numAddons; const char *graphicsBackend; // Commandline and user config options DECL_CONFIG_OPTION(bool, opengl); DECL_CONFIG_OPTION2(int, resolution, width, height); DECL_CONFIG_OPTION(bool, fullscreen); DECL_CONFIG_OPTION(bool, scanlines); DECL_CONFIG_OPTION(int, scaler); DECL_CONFIG_OPTION(bool, showFps); DECL_CONFIG_OPTION(bool, keepAspectRatio); DECL_CONFIG_OPTION(float, gamma); DECL_CONFIG_OPTION(int, soundDriver); DECL_CONFIG_OPTION(int, soundQuality); DECL_CONFIG_OPTION(bool, use3doMusic); DECL_CONFIG_OPTION(bool, useRemixMusic); DECL_CONFIG_OPTION(bool, useSpeech); DECL_CONFIG_OPTION(int, whichCoarseScan); DECL_CONFIG_OPTION(int, whichMenu); DECL_CONFIG_OPTION(int, whichFonts); DECL_CONFIG_OPTION(int, whichIntro); DECL_CONFIG_OPTION(int, whichShield); DECL_CONFIG_OPTION(int, smoothScroll); DECL_CONFIG_OPTION(int, meleeScale); DECL_CONFIG_OPTION(bool, subtitles); DECL_CONFIG_OPTION(bool, stereoSFX); DECL_CONFIG_OPTION(float, musicVolumeScale); DECL_CONFIG_OPTION(float, sfxVolumeScale); DECL_CONFIG_OPTION(float, speechVolumeScale); DECL_CONFIG_OPTION(bool, safeMode); #define INIT_CONFIG_OPTION(name, val) \ { val, false } #define INIT_CONFIG_OPTION2(name, val1, val2) \ { val1, val2, false } }; struct option_list_value { const char *str; int value; }; static const struct option_list_value scalerList[] = { {"bilinear", TFB_GFXFLAGS_SCALE_BILINEAR}, {"biadapt", TFB_GFXFLAGS_SCALE_BIADAPT}, {"biadv", TFB_GFXFLAGS_SCALE_BIADAPTADV}, {"triscan", TFB_GFXFLAGS_SCALE_TRISCAN}, {"hq", TFB_GFXFLAGS_SCALE_HQXX}, {"none", 0}, {"no", 0}, /* uqm.cfg value */ {NULL, 0} }; static const struct option_list_value meleeScaleList[] = { {"smooth", TFB_SCALE_TRILINEAR}, {"3do", TFB_SCALE_TRILINEAR}, {"step", TFB_SCALE_STEP}, {"pc", TFB_SCALE_STEP}, {"bilinear", TFB_SCALE_BILINEAR}, {NULL, 0} }; static const struct option_list_value audioDriverList[] = { {"openal", audio_DRIVER_OPENAL}, {"mixsdl", audio_DRIVER_MIXSDL}, {"none", audio_DRIVER_NOSOUND}, {"nosound", audio_DRIVER_NOSOUND}, {NULL, 0} }; static const struct option_list_value audioQualityList[] = { {"low", audio_QUALITY_LOW}, {"medium", audio_QUALITY_MEDIUM}, {"high", audio_QUALITY_HIGH}, {NULL, 0} }; static const struct option_list_value choiceList[] = { {"pc", OPT_PC}, {"3do", OPT_3DO}, {NULL, 0} }; static const struct option_list_value accelList[] = { {"mmx", PLATFORM_MMX}, {"sse", PLATFORM_SSE}, {"3dnow", PLATFORM_3DNOW}, {"none", PLATFORM_C}, {"detect", PLATFORM_NULL}, {NULL, 0} }; // Looks up the given string value in the given list and passes // the associated int value back. returns true if value was found. // The list is terminated by a NULL 'str' value. static bool lookupOptionValue (const struct option_list_value *list, const char *strval, int *ret); // Error message buffer used for when we cannot use logging facility yet static char errBuffer[512]; static void saveError (const char *fmt, ...) PRINTF_FUNCTION(1, 2); static int parseOptions (int argc, char *argv[], struct options_struct *options); static void getUserConfigOptions (struct options_struct *options); static void usage (FILE *out, const struct options_struct *defaultOptions); static int parseIntOption (const char *str, int *result, const char *optName); static int parseFloatOption (const char *str, float *f, const char *optName); static void parseIntVolume (int intVol, float *vol); static int InvalidArgument (const char *supplied, const char *opt_name); static const char *choiceOptString (const struct int_option *option); static const char *boolOptString (const struct bool_option *option); static const char *boolNotOptString (const struct bool_option *option); int main (int argc, char *argv[]) { struct options_struct options = { /* .logFile = */ NULL, /* .runMode = */ runMode_normal, /* .configDir = */ NULL, /* .contentDir = */ NULL, /* .addonDir = */ NULL, /* .addons = */ NULL, /* .numAddons = */ 0, /* .graphicsBackend = */ NULL, INIT_CONFIG_OPTION( opengl, false ), INIT_CONFIG_OPTION2( resolution, 320, 240 ), INIT_CONFIG_OPTION( fullscreen, false ), INIT_CONFIG_OPTION( scanlines, false ), INIT_CONFIG_OPTION( scaler, 0 ), INIT_CONFIG_OPTION( showFps, false ), INIT_CONFIG_OPTION( keepAspectRatio, false ), INIT_CONFIG_OPTION( gamma, 1.0f ), INIT_CONFIG_OPTION( soundDriver, audio_DRIVER_MIXSDL ), INIT_CONFIG_OPTION( soundQuality, audio_QUALITY_MEDIUM ), INIT_CONFIG_OPTION( use3doMusic, true ), INIT_CONFIG_OPTION( useRemixMusic, false ), INIT_CONFIG_OPTION( useSpeech, true ), INIT_CONFIG_OPTION( whichCoarseScan, OPT_PC ), INIT_CONFIG_OPTION( whichMenu, OPT_PC ), INIT_CONFIG_OPTION( whichFonts, OPT_PC ), INIT_CONFIG_OPTION( whichIntro, OPT_PC ), INIT_CONFIG_OPTION( whichShield, OPT_PC ), INIT_CONFIG_OPTION( smoothScroll, OPT_PC ), INIT_CONFIG_OPTION( meleeScale, TFB_SCALE_TRILINEAR ), INIT_CONFIG_OPTION( subtitles, true ), INIT_CONFIG_OPTION( stereoSFX, false ), INIT_CONFIG_OPTION( musicVolumeScale, 1.0f ), INIT_CONFIG_OPTION( sfxVolumeScale, 1.0f ), INIT_CONFIG_OPTION( speechVolumeScale, 1.0f ), INIT_CONFIG_OPTION( safeMode, false ), }; struct options_struct defaults = options; int optionsResult; int gfxDriver; int gfxFlags; int i; // NOTE: we cannot use the logging facility yet because we may have to // log to a file, and we'll only get the log file name after parsing // the options. optionsResult = parseOptions (argc, argv, &options); log_init (15); if (options.logFile != NULL) { int i; if (!freopen (options.logFile, "w", stderr)) { printf ("Error %d calling freopen() on stderr\n", errno); return EXIT_FAILURE; } #ifdef UNBUFFERED_LOGFILE setbuf (stderr, NULL); #endif for (i = 0; i < argc; ++i) log_add (log_User, "argv[%d] = [%s]", i, argv[i]); } if (options.runMode == runMode_version) { printf ("%d.%d.%d%s\n", UQM_MAJOR_VERSION, UQM_MINOR_VERSION, UQM_PATCH_VERSION, UQM_EXTRA_VERSION); log_showBox (false, false); return EXIT_SUCCESS; } log_add (log_User, "The Ur-Quan Masters v%d.%d.%d%s (compiled %s %s)\n" "This software comes with ABSOLUTELY NO WARRANTY;\n" "for details see the included 'COPYING' file.\n", UQM_MAJOR_VERSION, UQM_MINOR_VERSION, UQM_PATCH_VERSION, UQM_EXTRA_VERSION, __DATE__, __TIME__); #ifdef NETPLAY log_add (log_User, "Netplay protocol version %d.%d. Netplay opponent " "must have UQM %d.%d.%d or later.", NETPLAY_PROTOCOL_VERSION_MAJOR, NETPLAY_PROTOCOL_VERSION_MINOR, NETPLAY_MIN_UQM_VERSION_MAJOR, NETPLAY_MIN_UQM_VERSION_MINOR, NETPLAY_MIN_UQM_VERSION_PATCH); #endif if (errBuffer[0] != '\0') { // Have some saved error to log log_add (log_Error, "%s", errBuffer); errBuffer[0] = '\0'; } if (options.runMode == runMode_usage) { usage (stdout, &defaults); log_showBox (true, false); return EXIT_SUCCESS; } if (optionsResult != EXIT_SUCCESS) { // Options parsing failed. Oh, well. log_add (log_Fatal, "Run with -h to see the allowed arguments."); return optionsResult; } TFB_PreInit (); mem_init (); InitThreadSystem (); log_initThreads (); initIO (); prepareConfigDir (options.configDir); PlayerControls[0] = CONTROL_TEMPLATE_KB_1; PlayerControls[1] = CONTROL_TEMPLATE_JOY_1; // Fill in the options struct based on uqm.cfg if (!options.safeMode.value) { LoadResourceIndex (configDir, "uqm.cfg", "config."); getUserConfigOptions (&options); } { /* remove old control template names */ int i; for (i = 0; i < 6; ++i) { char cfgkey[64]; snprintf(cfgkey, sizeof(cfgkey), "config.keys.%d.name", i + 1); cfgkey[sizeof(cfgkey) - 1] = '\0'; res_Remove (cfgkey); } } /* TODO: Once threading is gone, these become local variables again. In the meantime, they must be global so that initAudio (in StarCon2Main) can see them. initAudio needed to be moved there because calling AssignTask in the main thread doesn't work */ snddriver = options.soundDriver.value; soundflags = options.soundQuality.value; // Fill in global variables: opt3doMusic = options.use3doMusic.value; optRemixMusic = options.useRemixMusic.value; optSpeech = options.useSpeech.value; optWhichCoarseScan = options.whichCoarseScan.value; optWhichMenu = options.whichMenu.value; optWhichFonts = options.whichFonts.value; optWhichIntro = options.whichIntro.value; optWhichShield = options.whichShield.value; optSmoothScroll = options.smoothScroll.value; optMeleeScale = options.meleeScale.value; optKeepAspectRatio = options.keepAspectRatio.value; optSubtitles = options.subtitles.value; optStereoSFX = options.stereoSFX.value; musicVolumeScale = options.musicVolumeScale.value; sfxVolumeScale = options.sfxVolumeScale.value; speechVolumeScale = options.speechVolumeScale.value; optAddons = options.addons; prepareContentDir (options.contentDir, options.addonDir, argv[0]); prepareMeleeDir (); prepareSaveDir (); prepareShadowAddons (options.addons); #if 0 initTempDir (); #endif InitTimeSystem (); InitTaskSystem (); Alarm_init (); Callback_init (); #ifdef NETPLAY Network_init (); NetManager_init (); #endif gfxDriver = options.opengl.value ? TFB_GFXDRIVER_SDL_OPENGL : TFB_GFXDRIVER_SDL_PURE; gfxFlags = options.scaler.value; if (options.fullscreen.value) gfxFlags |= TFB_GFXFLAGS_FULLSCREEN; if (options.scanlines.value) gfxFlags |= TFB_GFXFLAGS_SCANLINES; if (options.showFps.value) gfxFlags |= TFB_GFXFLAGS_SHOWFPS; TFB_InitGraphics (gfxDriver, gfxFlags, options.graphicsBackend, options.resolution.width, options.resolution.height); if (options.gamma.set && setGammaCorrection (options.gamma.value)) optGamma = options.gamma.value; else optGamma = 1.0f; // failed or default InitColorMaps (); init_communication (); /* TODO: Once threading is gone, restore initAudio here. initAudio calls AssignTask, which currently blocks on ProcessThreadLifecycles... */ // initAudio (snddriver, soundflags); // Make sure that the compiler treats multidim arrays the way we expect assert (sizeof (int [NUM_TEMPLATES * NUM_KEYS]) == sizeof (int [NUM_TEMPLATES][NUM_KEYS])); TFB_SetInputVectors (ImmediateInputState.menu, NUM_MENU_KEYS, (volatile int *)ImmediateInputState.key, NUM_TEMPLATES, NUM_KEYS); TFB_InitInput (TFB_INPUTDRIVER_SDL, 0); StartThread (Starcon2Main, NULL, 1024, "Starcon2Main"); for (i = 0; i < 2000 && !MainExited; ) { if (QuitPosted) { /* Try to stop the main thread, but limited number of times */ SignalStopMainThread (); ++i; } else if (!GameActive) { // Throttle down the main loop when game is inactive HibernateThread (ONE_SECOND / 4); } TFB_ProcessEvents (); ProcessUtilityKeys (); ProcessThreadLifecycles (); TFB_FlushGraphics (); } /* Currently, we use atexit() callbacks everywhere, so we * cannot simply call unInitAudio() and the like, because other * tasks might still be using it */ if (MainExited) { TFB_UninitInput (); unInitAudio (); uninit_communication (); TFB_PurgeDanglingGraphics (); // Purge above refers to colormaps which have to be still up UninitColorMaps (); TFB_UninitGraphics (); #ifdef NETPLAY NetManager_uninit (); Network_uninit (); #endif Callback_uninit (); Alarm_uninit (); CleanupTaskSystem (); UnInitTimeSystem (); #if 0 unInitTempDir (); #endif unprepareAllDirs (); uninitIO (); UnInitThreadSystem (); mem_uninit (); } HFree (options.addons); return EXIT_SUCCESS; } static void saveErrorV (const char *fmt, va_list list) { int len = strlen (errBuffer); int left = sizeof (errBuffer) - len; if (len > 0 && left > 0) { // Already something there errBuffer[len] = '\n'; ++len; --left; } vsnprintf (errBuffer + len, left, fmt, list); errBuffer[sizeof (errBuffer) - 1] = '\0'; } static void saveError (const char *fmt, ...) { va_list list; va_start (list, fmt); saveErrorV (fmt, list); va_end (list); } static bool lookupOptionValue (const struct option_list_value *list, const char *strval, int *ret) { if (!list) return false; // The list is terminated by a NULL 'str' value. while (list->str && strcmp (strval, list->str) != 0) ++list; if (!list->str) return false; *ret = list->value; return true; } static void getBoolConfigValue (struct bool_option *option, const char *config_val) { if (option->set || !res_IsBoolean (config_val)) return; option->value = res_GetBoolean (config_val); option->set = true; } static void getBoolConfigValueXlat (struct int_option *option, const char *config_val, int true_val, int false_val) { if (option->set || !res_IsBoolean (config_val)) return; option->value = res_GetBoolean (config_val) ? true_val : false_val; option->set = true; } static void getVolumeConfigValue (struct float_option *option, const char *config_val) { if (option->set || !res_IsInteger (config_val)) return; parseIntVolume (res_GetInteger (config_val), &option->value); option->set = true; } static void getGammaConfigValue (struct float_option *option, const char *config_val) { int val; if (option->set || !res_IsInteger (config_val)) return; val = res_GetInteger (config_val); // gamma config option is a fixed-point number // ignore ridiculously out-of-range values if (val < (int)(0.03 * GAMMA_SCALE) || val > (int)(9.9 * GAMMA_SCALE)) return; option->value = val / (float)GAMMA_SCALE; // avoid setting gamma when not necessary if (option->value != 1.0f) option->set = true; } static bool getListConfigValue (struct int_option *option, const char *config_val, const struct option_list_value *list) { const char *strval; bool found; if (option->set || !res_IsString (config_val) || !list) return false; strval = res_GetString (config_val); found = lookupOptionValue (list, strval, &option->value); option->set = found; return found; } static void getUserConfigOptions (struct options_struct *options) { // Most of the user config options are only applied if they // have not already been set (i.e. on the commandline) if (res_IsInteger ("config.reswidth") && res_IsInteger ("config.resheight") && !options->resolution.set) { options->resolution.width = res_GetInteger ("config.reswidth"); options->resolution.height = res_GetInteger ("config.resheight"); options->resolution.set = true; } if (res_IsBoolean ("config.alwaysgl") && !options->opengl.set) { // config.alwaysgl is processed differently than others // Only set when it's 'true' if (res_GetBoolean ("config.alwaysgl")) { options->opengl.value = true; options->opengl.set = true; } } getBoolConfigValue (&options->opengl, "config.usegl"); getListConfigValue (&options->scaler, "config.scaler", scalerList); getBoolConfigValue (&options->fullscreen, "config.fullscreen"); getBoolConfigValue (&options->scanlines, "config.scanlines"); getBoolConfigValue (&options->showFps, "config.showfps"); getBoolConfigValue (&options->keepAspectRatio, "config.keepaspectratio"); getGammaConfigValue (&options->gamma, "config.gamma"); getBoolConfigValue (&options->subtitles, "config.subtitles"); getBoolConfigValueXlat (&options->whichMenu, "config.textmenu", OPT_PC, OPT_3DO); getBoolConfigValueXlat (&options->whichFonts, "config.textgradients", OPT_PC, OPT_3DO); getBoolConfigValueXlat (&options->whichCoarseScan, "config.iconicscan", OPT_3DO, OPT_PC); getBoolConfigValueXlat (&options->smoothScroll, "config.smoothscroll", OPT_3DO, OPT_PC); getBoolConfigValueXlat (&options->whichShield, "config.pulseshield", OPT_3DO, OPT_PC); getBoolConfigValueXlat (&options->whichIntro, "config.3domovies", OPT_3DO, OPT_PC); getBoolConfigValue (&options->use3doMusic, "config.3domusic"); getBoolConfigValue (&options->useRemixMusic, "config.remixmusic"); getBoolConfigValue (&options->useSpeech, "config.speech"); getBoolConfigValueXlat (&options->meleeScale, "config.smoothmelee", TFB_SCALE_TRILINEAR, TFB_SCALE_STEP); getListConfigValue (&options->soundDriver, "config.audiodriver", audioDriverList); getListConfigValue (&options->soundQuality, "config.audioquality", audioQualityList); getBoolConfigValue (&options->stereoSFX, "config.positionalsfx"); getVolumeConfigValue (&options->musicVolumeScale, "config.musicvol"); getVolumeConfigValue (&options->sfxVolumeScale, "config.sfxvol"); getVolumeConfigValue (&options->speechVolumeScale, "config.speechvol"); if (res_IsInteger ("config.player1control")) { PlayerControls[0] = res_GetInteger ("config.player1control"); /* This is an unsigned, so no < 0 check is necessary */ if (PlayerControls[0] >= NUM_TEMPLATES) { log_add (log_Error, "Illegal control template '%d' for Player " "One.", PlayerControls[0]); PlayerControls[0] = CONTROL_TEMPLATE_KB_1; } } if (res_IsInteger ("config.player2control")) { /* This is an unsigned, so no < 0 check is necessary */ PlayerControls[1] = res_GetInteger ("config.player2control"); if (PlayerControls[1] >= NUM_TEMPLATES) { log_add (log_Error, "Illegal control template '%d' for Player " "Two.", PlayerControls[1]); PlayerControls[1] = CONTROL_TEMPLATE_JOY_1; } } } enum { CSCAN_OPT = 1000, MENU_OPT, FONT_OPT, SHIELD_OPT, SCROLL_OPT, SOUND_OPT, STEREOSFX_OPT, ADDON_OPT, ADDONDIR_OPT, ACCEL_OPT, SAFEMODE_OPT, RENDERER_OPT, #ifdef NETPLAY NETHOST1_OPT, NETPORT1_OPT, NETHOST2_OPT, NETPORT2_OPT, NETDELAY_OPT, #endif }; static const char *optString = "+r:foc:b:spC:n:?hM:S:T:q:ug:l:i:vwxk"; static struct option longOptions[] = { {"res", 1, NULL, 'r'}, {"fullscreen", 0, NULL, 'f'}, {"opengl", 0, NULL, 'o'}, {"scale", 1, NULL, 'c'}, {"meleezoom", 1, NULL, 'b'}, {"scanlines", 0, NULL, 's'}, {"fps", 0, NULL, 'p'}, {"configdir", 1, NULL, 'C'}, {"contentdir", 1, NULL, 'n'}, {"help", 0, NULL, 'h'}, {"musicvol", 1, NULL, 'M'}, {"sfxvol", 1, NULL, 'S'}, {"speechvol", 1, NULL, 'T'}, {"audioquality", 1, NULL, 'q'}, {"nosubtitles", 0, NULL, 'u'}, {"gamma", 1, NULL, 'g'}, {"logfile", 1, NULL, 'l'}, {"intro", 1, NULL, 'i'}, {"version", 0, NULL, 'v'}, {"windowed", 0, NULL, 'w'}, {"nogl", 0, NULL, 'x'}, {"keepaspectratio", 0, NULL, 'k'}, // options with no short equivalent: {"cscan", 1, NULL, CSCAN_OPT}, {"menu", 1, NULL, MENU_OPT}, {"font", 1, NULL, FONT_OPT}, {"shield", 1, NULL, SHIELD_OPT}, {"scroll", 1, NULL, SCROLL_OPT}, {"sound", 1, NULL, SOUND_OPT}, {"stereosfx", 0, NULL, STEREOSFX_OPT}, {"addon", 1, NULL, ADDON_OPT}, {"addondir", 1, NULL, ADDONDIR_OPT}, {"accel", 1, NULL, ACCEL_OPT}, {"safe", 0, NULL, SAFEMODE_OPT}, {"renderer", 1, NULL, RENDERER_OPT}, #ifdef NETPLAY {"nethost1", 1, NULL, NETHOST1_OPT}, {"netport1", 1, NULL, NETPORT1_OPT}, {"nethost2", 1, NULL, NETHOST2_OPT}, {"netport2", 1, NULL, NETPORT2_OPT}, {"netdelay", 1, NULL, NETDELAY_OPT}, #endif {0, 0, 0, 0} }; static inline void setBoolOption (struct bool_option *option, bool value) { option->value = value; option->set = true; } static bool setFloatOption (struct float_option *option, const char *strval, const char *optName) { if (parseFloatOption (strval, &option->value, optName) != 0) return false; option->set = true; return true; } // returns true is value was found and set successfully static bool setListOption (struct int_option *option, const char *strval, const struct option_list_value *list) { bool found; if (!list) return false; // not found found = lookupOptionValue (list, strval, &option->value); option->set = found; return found; } static inline bool setChoiceOption (struct int_option *option, const char *strval) { return setListOption (option, strval, choiceList); } static bool setVolumeOption (struct float_option *option, const char *strval, const char *optName) { int intVol; if (parseIntOption (strval, &intVol, optName) != 0) return false; parseIntVolume (intVol, &option->value); option->set = true; return true; } static int parseOptions (int argc, char *argv[], struct options_struct *options) { int optionIndex; bool badArg = false; opterr = 0; options->addons = HMalloc (1 * sizeof (const char *)); options->addons[0] = NULL; options->numAddons = 0; if (argc == 0) { saveError ("Error: Bad command line."); return EXIT_FAILURE; } #ifdef __APPLE__ // If we are launched by double-clicking an application bundle, Finder // sticks a "-psn_" argument into the list, which makes // getopt extremely unhappy. Check for this case and wipe out the // entire command line if it looks like it happened. if ((argc >= 2) && (strncmp (argv[1], "-psn_", 5) == 0)) { return EXIT_SUCCESS; } #endif while (!badArg) { int c; optionIndex = -1; c = getopt_long (argc, argv, optString, longOptions, &optionIndex); if (c == -1) break; switch (c) { case '?': if (optopt != '?') { saveError ("\nInvalid option or its argument"); badArg = true; break; } // fall through case 'h': options->runMode = runMode_usage; return EXIT_SUCCESS; case 'v': options->runMode = runMode_version; return EXIT_SUCCESS; case 'r': { int width, height; if (sscanf (optarg, "%dx%d", &width, &height) != 2) { saveError ("Error: invalid argument specified " "as resolution."); badArg = true; break; } options->resolution.width = width; options->resolution.height = height; options->resolution.set = true; break; } case 'f': setBoolOption (&options->fullscreen, true); break; case 'w': setBoolOption (&options->fullscreen, false); break; case 'o': setBoolOption (&options->opengl, true); break; case 'x': setBoolOption (&options->opengl, false); break; case 'k': setBoolOption (&options->keepAspectRatio, true); break; case 'c': if (!setListOption (&options->scaler, optarg, scalerList)) { InvalidArgument (optarg, "--scale or -c"); badArg = true; } break; case 'b': if (!setListOption (&options->meleeScale, optarg, meleeScaleList)) { InvalidArgument (optarg, "--meleezoom or -b"); badArg = true; } break; case 's': setBoolOption (&options->scanlines, true); break; case 'p': setBoolOption (&options->showFps, true); break; case 'n': options->contentDir = optarg; break; case 'M': if (!setVolumeOption (&options->musicVolumeScale, optarg, "music volume")) { badArg = true; } break; case 'S': if (!setVolumeOption (&options->sfxVolumeScale, optarg, "sfx volume")) { badArg = true; } break; case 'T': if (!setVolumeOption (&options->speechVolumeScale, optarg, "speech volume")) { badArg = true; } break; case 'q': if (!setListOption (&options->soundQuality, optarg, audioQualityList)) { InvalidArgument (optarg, "--audioquality or -q"); badArg = true; } break; case 'u': setBoolOption (&options->subtitles, false); break; case 'g': if (!setFloatOption (&options->gamma, optarg, "gamma correction")) { badArg = true; } break; case 'l': options->logFile = optarg; break; case 'C': options->configDir = optarg; break; case 'i': if (!setChoiceOption (&options->whichIntro, optarg)) { InvalidArgument (optarg, "--intro or -i"); badArg = true; } break; case CSCAN_OPT: if (!setChoiceOption (&options->whichCoarseScan, optarg)) { InvalidArgument (optarg, "--cscan"); badArg = true; } break; case MENU_OPT: if (!setChoiceOption (&options->whichMenu, optarg)) { InvalidArgument (optarg, "--menu"); badArg = true; } break; case FONT_OPT: if (!setChoiceOption (&options->whichFonts, optarg)) { InvalidArgument (optarg, "--font"); badArg = true; } break; case SHIELD_OPT: if (!setChoiceOption (&options->whichShield, optarg)) { InvalidArgument (optarg, "--shield"); badArg = true; } break; case SCROLL_OPT: if (!setChoiceOption (&options->smoothScroll, optarg)) { InvalidArgument (optarg, "--scroll"); badArg = true; } break; case SOUND_OPT: if (!setListOption (&options->soundDriver, optarg, audioDriverList)) { InvalidArgument (optarg, "--sound"); badArg = true; } break; case STEREOSFX_OPT: setBoolOption (&options->stereoSFX, true); break; case ADDON_OPT: options->numAddons++; options->addons = HRealloc ((void *) options->addons, (options->numAddons + 1) * sizeof (const char *)); options->addons[options->numAddons - 1] = optarg; options->addons[options->numAddons] = NULL; break; case ADDONDIR_OPT: options->addonDir = optarg; break; case ACCEL_OPT: { int value; if (lookupOptionValue (accelList, optarg, &value)) { force_platform = value; } else { InvalidArgument (optarg, "--accel"); badArg = true; } break; } case SAFEMODE_OPT: setBoolOption (&options->safeMode, true); break; case RENDERER_OPT: options->graphicsBackend = optarg; break; #ifdef NETPLAY case NETHOST1_OPT: netplayOptions.peer[0].isServer = false; netplayOptions.peer[0].host = optarg; break; case NETPORT1_OPT: netplayOptions.peer[0].port = optarg; break; case NETHOST2_OPT: netplayOptions.peer[1].isServer = false; netplayOptions.peer[1].host = optarg; break; case NETPORT2_OPT: netplayOptions.peer[1].port = optarg; break; case NETDELAY_OPT: { int temp; if (parseIntOption (optarg, &temp, "network input delay") == -1) { badArg = true; break; } netplayOptions.inputDelay = temp; if (netplayOptions.inputDelay > BATTLE_FRAME_RATE) { saveError ("Network input delay is absurdly large."); badArg = true; } break; } #endif default: saveError ("Error: Unknown option '%s'", optionIndex < 0 ? "" : longOptions[optionIndex].name); badArg = true; break; } } if (!badArg && optind != argc) { saveError ("\nError: Extra arguments found on the command line."); badArg = true; } return badArg ? EXIT_FAILURE : EXIT_SUCCESS; } static void parseIntVolume (int intVol, float *vol) { if (intVol < 0) { *vol = 0.0f; return; } if (intVol > 100) { *vol = 1.0f; return; } *vol = intVol / 100.0f; return; } static int parseIntOption (const char *str, int *result, const char *optName) { char *endPtr; int temp; if (str[0] == '\0') { saveError ("Error: Invalid value for '%s'.", optName); return -1; } temp = (int) strtol (str, &endPtr, 10); if (*endPtr != '\0') { saveError ("Error: Junk characters in argument '%s'.", optName); return -1; } *result = temp; return 0; } static int parseFloatOption (const char *str, float *f, const char *optName) { char *endPtr; float temp; if (str[0] == '\0') { saveError ("Error: Invalid value for '%s'.", optName); return -1; } temp = (float) strtod (str, &endPtr); if (*endPtr != '\0') { saveError ("Error: Junk characters in argument '%s'.", optName); return -1; } *f = temp; return 0; } static void usage (FILE *out, const struct options_struct *defaults) { FILE *old = log_setOutput (out); log_captureLines (LOG_CAPTURE_ALL); log_add (log_User, "Options:"); log_add (log_User, " -r, --res=WIDTHxHEIGHT (default 640x480, bigger " "works only with --opengl)"); log_add (log_User, " -f, --fullscreen (default %s)", boolOptString (&defaults->fullscreen)); log_add (log_User, " -w, --windowed (default %s)", boolNotOptString (&defaults->fullscreen)); log_add (log_User, " -o, --opengl (default %s)", boolOptString (&defaults->opengl)); log_add (log_User, " -x, --nogl (default %s)", boolNotOptString (&defaults->opengl)); log_add (log_User, " -k, --keepaspectratio (default %s)", boolOptString (&defaults->keepAspectRatio)); log_add (log_User, " -c, --scale=MODE (bilinear, biadapt, biadv, " "triscan, hq or none (default) )"); log_add (log_User, " -b, --meleezoom=MODE (step, aka pc, or smooth, " "aka 3do; default is 3do)"); log_add (log_User, " -s, --scanlines (default %s)", boolOptString (&defaults->scanlines)); log_add (log_User, " -p, --fps (default %s)", boolOptString (&defaults->showFps)); log_add (log_User, " -g, --gamma=CORRECTIONVALUE (default 1.0, which " "causes no change)"); log_add (log_User, " -C, --configdir=CONFIGDIR"); log_add (log_User, " -n, --contentdir=CONTENTDIR"); log_add (log_User, " -M, --musicvol=VOLUME (0-100, default 100)"); log_add (log_User, " -S, --sfxvol=VOLUME (0-100, default 100)"); log_add (log_User, " -T, --speechvol=VOLUME (0-100, default 100)"); log_add (log_User, " -q, --audioquality=QUALITY (high, medium or low, " "default medium)"); log_add (log_User, " -u, --nosubtitles"); log_add (log_User, " -l, --logfile=FILE (sends console output to " "logfile FILE)"); log_add (log_User, " --addon ADDON (using a specific addon; " "may be specified multiple times)"); log_add (log_User, " --addondir=ADDONDIR (directory where addons " "reside)"); log_add (log_User, " --renderer=name (Select named rendering engine " "if possible)"); log_add (log_User, " --sound=DRIVER (openal, mixsdl, none; default " "mixsdl)"); log_add (log_User, " --stereosfx (enables positional sound effects, " "currently only for openal)"); log_add (log_User, " --safe (start in safe mode)"); #ifdef NETPLAY log_add (log_User, " --nethostN=HOSTNAME (server to connect to for " "player N (1=bottom, 2=top)"); log_add (log_User, " --netportN=PORT (port to connect to/listen on for " "player N (1=bottom, 2=top)"); log_add (log_User, " --netdelay=FRAMES (number of frames to " "buffer/delay network input for"); #endif log_add (log_User, "The following options can take either '3do' or 'pc' " "as an option:"); log_add (log_User, " -i, --intro : Intro/ending version (default %s)", choiceOptString (&defaults->whichIntro)); log_add (log_User, " --cscan : coarse-scan display, pc=text, " "3do=hieroglyphs (default %s)", choiceOptString (&defaults->whichCoarseScan)); log_add (log_User, " --menu : menu type, pc=text, 3do=graphical " "(default %s)", choiceOptString (&defaults->whichMenu)); log_add (log_User, " --font : font types and colors (default %s)", choiceOptString (&defaults->whichFonts)); log_add (log_User, " --shield : slave shield type; pc=static, " "3do=throbbing (default %s)", choiceOptString (&defaults->whichShield)); log_add (log_User, " --scroll : ff/frev during comm. pc=per-page, " "3do=smooth (default %s)", choiceOptString (&defaults->smoothScroll)); log_setOutput (old); } static int InvalidArgument (const char *supplied, const char *opt_name) { saveError ("Invalid argument '%s' to option %s.", supplied, opt_name); return EXIT_FAILURE; } static const char * choiceOptString (const struct int_option *option) { switch (option->value) { case OPT_3DO: return "3do"; case OPT_PC: return "pc"; default: /* 0 */ return "none"; } } static const char * boolOptString (const struct bool_option *option) { return option->value ? "on" : "off"; } static const char * boolNotOptString (const struct bool_option *option) { return option->value ? "off" : "on"; }