summaryrefslogtreecommitdiff
path: root/src/uqm.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/uqm.c')
-rw-r--r--src/uqm.c1285
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";
+}