diff options
Diffstat (limited to 'src/uqm.c')
-rw-r--r-- | src/uqm.c | 1285 |
1 files changed, 1285 insertions, 0 deletions
diff --git a/src/uqm.c b/src/uqm.c new file mode 100644 index 0000000..43c25d8 --- /dev/null +++ b/src/uqm.c @@ -0,0 +1,1285 @@ +//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 <unistd.h> +#endif + +#ifdef HAVE_GETOPT_LONG +# include <getopt.h> +#else +# include "getopt/getopt.h" +#endif + +#include <stdarg.h> +#include <errno.h> +#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, 640, 480 ), + 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_<some_number>" 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 ? "<unknown>" : + 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"; +} |