[gens-sdl] Options: Initial command line option parser.
authorDavid Korth <gerbilsoft@gerbilsoft.com>
Mon, 7 Sep 2015 15:22:30 +0000 (11:22 -0400)
committerDavid Korth <gerbilsoft@gerbilsoft.com>
Mon, 7 Sep 2015 15:22:30 +0000 (11:22 -0400)
Several options are present, but currently only the ROM filename,
auto-pause, and paused-effect are used.

TODO: Rewrite Options to use accessors instead of publicly exposing
options, and store the actual options in a private class.

src/gens-sdl/CMakeLists.txt
src/gens-sdl/CrazyEffectLoop.cpp
src/gens-sdl/CrazyEffectLoop.hpp
src/gens-sdl/EmuLoop.cpp
src/gens-sdl/EmuLoop.hpp
src/gens-sdl/EventLoop.cpp
src/gens-sdl/EventLoop.hpp
src/gens-sdl/EventLoop_p.hpp
src/gens-sdl/Options.cpp [new file with mode: 0644]
src/gens-sdl/Options.hpp [new file with mode: 0644]
src/gens-sdl/gens-sdl.cpp

index f8fabd2..0bdf481 100644 (file)
@@ -26,6 +26,9 @@ INCLUDE_DIRECTORIES(${ZLIB_INCLUDE_DIR})
 INCLUDE_DIRECTORIES(${OPENGL_INCLUDE_DIR})
 INCLUDE_DIRECTORIES(${GLEW_INCLUDE_DIR})
 
+# Popt include directory.
+INCLUDE_DIRECTORIES(${POPT_INCLUDE_DIR})
+
 # Write the config.h file.
 CONFIGURE_FILE("${CMAKE_CURRENT_SOURCE_DIR}/config.gens-sdl.h.in" "${CMAKE_CURRENT_BINARY_DIR}/config.gens-sdl.h")
 
@@ -45,6 +48,7 @@ SET(gens-sdl_SRCS
        GLBackend.cpp
        str_lookup.cpp
        GLTex.cpp
+       Options.cpp
        )
 
 # Headers.
@@ -63,6 +67,7 @@ SET(gens-sdl_H
        GLBackend.hpp
        str_lookup.hpp
        GLTex.hpp
+       Options.hpp
        )
 
 # OSD sources.
@@ -116,6 +121,7 @@ TARGET_LINK_LIBRARIES(gens-sdl
        ${SDL2_LIBRARY}
        ${OPENGL_gl_LIBRARY}
        ${GLEW_LIBRARY}
+       ${POPT_LIBRARY}
        )
 
 # Set the compile definitions.
index c905878..ee751e0 100644 (file)
@@ -42,6 +42,9 @@ using LibGens::MdFb;
 #include "libgens/Effects/CrazyEffect.hpp"
 using LibGens::CrazyEffect;
 
+// Command line parameters.
+#include "Options.hpp"
+
 #include "EventLoop_p.hpp"
 namespace GensSdl {
 
@@ -89,18 +92,20 @@ CrazyEffectLoop::~CrazyEffectLoop()
 
 /**
  * Run the event loop.
- * @param rom_filename ROM filename. [TODO: Replace with options struct?]
+ * @param options Options.
  * @return Exit code.
  */
-int CrazyEffectLoop::run(const char *rom_filename)
+int CrazyEffectLoop::run(const Options *options)
 {
-       // rom_filename isn't used here.
-       ((void)rom_filename);
+       // Save options.
+       // TODO: Make EmuLoop::run() non-virtual, save options there,
+       // and then call protected virtual run_int()?
+       CrazyEffectLoopPrivate *const d = d_func();
+       d->options = options;
 
        // TODO: Move common code back to gens-sdl?
 
        // Initialize the SDL handlers.
-       CrazyEffectLoopPrivate *const d = d_func();
        d->sdlHandler = new SdlHandler();
        if (d->sdlHandler->init_video() < 0)
                return EXIT_FAILURE;
index e9b5002..10f1957 100644 (file)
@@ -44,10 +44,10 @@ class CrazyEffectLoop : public EventLoop
        public:
                /**
                 * Run the event loop.
-                * @param rom_filename ROM filename. [TODO: Replace with options struct?]
+                * @param options Options.
                 * @return Exit code.
                 */
-               virtual int run(const char *rom_filename) final;
+               virtual int run(const Options *options) final;
 
        protected:
                // TODO: Move to CrazyEffectLoopPrivate?
index 99651c9..a898c75 100644 (file)
@@ -64,6 +64,9 @@ using LibGensKeys::KeyManager;
 using LibZomg::ZomgBase;
 using LibZomg::Zomg;
 
+// Command line parameters.
+#include "Options.hpp"
+
 // C++ includes.
 #include <string>
 using std::string;
@@ -512,21 +515,29 @@ int EmuLoop::processSdlEvent(const SDL_Event *event) {
 
 /**
  * Run the event loop.
- * @param rom_filename ROM filename. [TODO: Replace with options struct?]
+ * @param options Options.
  * @return Exit code.
  */
-int EmuLoop::run(const char *rom_filename)
+int EmuLoop::run(const Options *options)
 {
-       // Load the ROM image.
+       // Save options.
+       // TODO: Make EmuLoop::run() non-virtual, save options there,
+       // and then call protected virtual run_int()?
        EmuLoopPrivate *const d = d_func();
-       d->rom = new Rom(rom_filename);
+       d->options = options;
+       
+       // Load the ROM image.
+       d->rom = new Rom(options->rom_filename.c_str());
        if (!d->rom->isOpen()) {
                // Error opening the ROM.
                // TODO: Error code?
                fprintf(stderr, "Error opening ROM file %s: (TODO get error code)\n",
-                       rom_filename);
+                       options->rom_filename.c_str());
+               delete d->rom;
+               d->rom = nullptr;
                return EXIT_FAILURE;
        }
+
        if (d->rom->isMultiFile()) {
                // Select the first file.
                d->rom->select_z_entry(d->rom->get_z_entry_list());
@@ -537,7 +548,7 @@ int EmuLoop::run(const char *rom_filename)
                // ROM format is not supported.
                const char *rom_format = romFormatToString(d->rom->romFormat());
                fprintf(stderr, "Error loading ROM file %s: ROM is in %s format.\nOnly plain binary and SMD-format ROMs are supported.\n",
-                       rom_filename, rom_format);
+                       options->rom_filename.c_str(), rom_format);
                return EXIT_FAILURE;
        }
 
@@ -546,7 +557,7 @@ int EmuLoop::run(const char *rom_filename)
                // System is not supported.
                const char *rom_sysId = sysIdToString(d->rom->sysId());
                fprintf(stderr, "Error loading ROM file %s: ROM is for %s.\nOnly Mega Drive and Pico ROMs are supported.\n",
-                       rom_filename, rom_sysId);
+                       options->rom_filename.c_str(), rom_sysId);
                return EXIT_FAILURE;
        }
 
@@ -565,7 +576,7 @@ int EmuLoop::run(const char *rom_filename)
                // Error loading the ROM into EmuMD.
                // TODO: Error code?
                fprintf(stderr, "Error initializing EmuContext for %s: (TODO get error code)\n",
-                       rom_filename);
+                       options->rom_filename.c_str());
                return EXIT_FAILURE;
        }
 
index 58e45cf..adef179 100644 (file)
@@ -49,10 +49,10 @@ class EmuLoop : public EventLoop
        public:
                /**
                 * Run the event loop.
-                * @param rom_filename ROM filename. [TODO: Replace with options struct?]
+                * @param options Options.
                 * @return Exit code.
                 */
-               virtual int run(const char *rom_filename) final;
+               virtual int run(const Options *options) final;
 
        protected:
                /**
index 01fb059..8657fd7 100644 (file)
@@ -40,6 +40,9 @@ using GensSdl::VBackend;
 #include "libgens/Util/MdFb.hpp"
 using LibGens::MdFb;
 
+// Command line parameters.
+#include "Options.hpp"
+
 // C++ includes.
 #include <string>
 using std::string;
@@ -54,7 +57,7 @@ EventLoopPrivate::EventLoopPrivate()
        , vBackend(nullptr)
        , running(false)
        , frameskip(true)
-       , autoPause(false)
+       , options(nullptr)
        , exposed(false)
        , lastF1time(0)
        , usec_per_frame(0)
@@ -94,9 +97,13 @@ void EventLoopPrivate::doPauseProcessing(void)
 {
        bool manual = paused.manual;
        bool any = !!paused.data;
-       // TODO: Option to disable the Paused Effect?
-       // When enabled, it's only used for Manual Pause.
-       vBackend->setPausedEffect(manual);
+
+       // Set the paused effect.
+       if (options->paused_effect) {
+               vBackend->setPausedEffect(manual);
+       } else {
+               vBackend->setPausedEffect(false);
+       }
 
        // Reset the clocks and counters.
        clks.reset();
@@ -280,14 +287,14 @@ int EventLoop::processSdlEvent(const SDL_Event *event)
                                        break;
                                case SDL_WINDOWEVENT_FOCUS_LOST:
                                        // If AutoPause is enabled, pause the emulator.
-                                       if (d_ptr->autoPause) {
+                                       if (d_ptr->options->auto_pause) {
                                                d_ptr->doAutoPause(true);
                                        }
                                        break;
                                case SDL_WINDOWEVENT_FOCUS_GAINED:
                                        // If AutoPause is enabled, unpause the emulator.
                                        // TODO: Always run this, even if !autoPause?
-                                       if (d_ptr->autoPause) {
+                                       if (d_ptr->options->auto_pause) {
                                                d_ptr->doAutoPause(false);
                                        }
                                        break;
index 896c0ef..a80cbf2 100644 (file)
@@ -35,6 +35,7 @@
 namespace GensSdl {
 
 class VBackend;
+class Options;
 
 class EventLoopPrivate;
 class EventLoop
@@ -55,10 +56,10 @@ class EventLoop
        public:
                /**
                 * Run the event loop.
-                * @param rom_filename ROM filename. [TODO: Replace with options struct?]
+                * @param options Options.
                 * @return Exit code.
                 */
-               virtual int run(const char *rom_filename) = 0;
+               virtual int run(const Options *options) = 0;
 
                /**
                 * Get the VBackend.
index 285ac46..ca1de4b 100644 (file)
@@ -51,6 +51,7 @@ namespace GensSdl {
 
 class SdlHandler;
 class VBackend;
+class Options;
 
 class EventLoopPrivate
 {
@@ -104,6 +105,9 @@ class EventLoopPrivate
                bool running;
                bool frameskip;
 
+               // Options.
+               const Options *options;
+
                union paused_t {
                        struct {
                                uint8_t manual  : 1;    // Manual pause.
@@ -114,9 +118,6 @@ class EventLoopPrivate
                };
                paused_t paused;        // Current 'paused' value.
 
-               // Automatically pause when the window loses focus?
-               bool autoPause;
-
                // Window has been exposed.
                // Video should be updated if emulation is paused.
                bool exposed;
diff --git a/src/gens-sdl/Options.cpp b/src/gens-sdl/Options.cpp
new file mode 100644 (file)
index 0000000..97cbe8f
--- /dev/null
@@ -0,0 +1,376 @@
+/***************************************************************************
+ * gens-sdl: Gens/GS II basic SDL frontend.                                *
+ * Options.cpp: Command line option parser.                                *
+ *                                                                         *
+ * Copyright (c) 2015 by David Korth.                                      *
+ *                                                                         *
+ * 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., *
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.           *
+ ***************************************************************************/
+
+#include "Options.hpp"
+
+// C includes. (C++ namespace)
+#include <cstring>
+#include <cerrno>
+#ifndef ECANCELED
+#define ECANCELED 158
+#endif
+
+// C++ includes.
+#include <string>
+using std::string;
+
+// popt
+#include <popt.h>
+
+namespace GensSdl {
+
+Options::Options()
+{
+       // Reset the options to the default values.
+       reset();
+}
+
+/**
+ * Reset all options to their default values.
+ */
+void Options::reset(void)
+{
+       rom_filename_required = true;
+
+       // TODO: Swap with empty strings?
+       rom_filename.clear();
+       tmss_rom_filename.clear();
+       tmss_enabled = false;
+
+       // Audio options.
+       sound_freq = 44100;
+       stereo = true;
+
+       // Emulation options.
+       sprite_limits = true;
+       auto_fix_checksum = true;
+
+       // UI options.
+       fps_counter = true;
+       auto_pause = false;
+       paused_effect = true;
+       bpp = 32;
+
+       // Special run modes.
+       run_crazy_effect = false;
+}
+
+// TODO: Improve these.
+static void print_prg_info(void)
+{
+       fprintf(stderr, "gens-sdl: Gens/GS II basic SDL frontend.\n");
+}
+
+static void print_gpl(void)
+{
+       fprintf(stderr,
+               "This program is free software; you can redistribute it and/or modify it\n"
+               "under the terms of the GNU General Public License as published by the\n"
+               "Free Software Foundation; either version 2 of the License, or (at your\n"
+               "option) any later version.\n"
+               "\n"
+               "This program is distributed in the hope that it will be useful, but\n"
+               "WITHOUT ANY WARRANTY; without even the implied warranty of\n"
+               "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n"
+               "GNU General Public License for more details.\n"
+               "\n"
+               "You should have received a copy of the GNU General Public License along\n"
+               "with this program; if not, write to the Free Software Foundation, Inc.,\n"
+               "51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n");
+}
+
+static void print_help(const poptContext con)
+{
+       print_prg_info();
+       fputc('\n', stderr);
+       // NOTE: poptPrintHelp() only prints the filename portion of argv[0].
+       poptPrintHelp(con, stderr, 0);
+}
+
+/**
+ * Parse command line arguments using popt.
+ * @param ctx gens_args to store arguments in.
+ * @param argc
+ * @param argv
+ * @return 0 if parsed successfully; non-zero on error.
+ * Some error codes:
+ * - -EINVAL: invalid arguments
+ * - -ECANCELED: operation canceled (TODO: define on MSVC?)
+ *   - occurs if user specifies something like --help, which exits immediately.
+ */
+int Options::parse(int argc, const char *argv[])
+{
+       if (!argv) {
+               // Invalid arguments.
+               return -EINVAL;
+       }
+
+       // Temporary internal option variables.
+       struct {
+               // popt is a C library, so we have to use
+               // C strings here initially. Also, we can't
+               // use bool; we have to use int.
+               const char *rom_filename;
+               const char *tmss_rom_filename;
+               // tmss_enabled is implied by tmss_rom_filename.
+
+               // Audio options.
+               int sound_freq;
+               int stereo;
+
+               // Emulation options.
+               int sprite_limits;
+               int auto_fix_checksum;
+
+               // UI options.
+               int fps_counter;
+               int auto_pause;
+               int paused_effect;
+               int bpp;
+
+               // Special run modes.
+               int run_crazy_effect;
+       } tmp;
+
+       // Set default values.
+       // TODO: Separate function for this?
+       memset(&tmp, 0, sizeof(tmp));
+       tmp.sound_freq = 44100;
+       tmp.stereo = 1;
+       tmp.sprite_limits = 1;
+       tmp.auto_fix_checksum = 1;
+       tmp.fps_counter = 1;
+       tmp.paused_effect = 1;
+       tmp.bpp = 32;
+
+       // NOTE: rom_filename is provided as a non-option parameter.
+       // It will get parsed later.
+
+       // popt: help options table.
+       struct poptOption helpOptionsTable[] = {
+               {"help", '?', POPT_ARG_NONE, NULL, '?', "Show this help message", NULL},
+               {"usage", 0, POPT_ARG_NONE, NULL, 'u', "Display brief usage message", NULL},
+               {"version", 'V', POPT_ARG_NONE, NULL, 'V', "Display version information", NULL},
+               POPT_TABLEEND
+       };
+
+       // popt: audio options table.
+       struct poptOption audioOptionsTable[] = {
+               {"frequency", 0, POPT_ARG_INT, &tmp.sound_freq, 0,
+                       "  Audio frequency.", "FREQ"},
+               {"mono", 0, POPT_ARG_VAL, &tmp.stereo, 0,
+                       "  Use monaural audio.", NULL},
+               {"stereo", 0, POPT_ARG_VAL, &tmp.stereo, 1,
+                       "  Use stereo audio.", NULL},
+               POPT_TABLEEND
+       };
+
+       // popt: emulation options table.
+       struct poptOption emulationOptionsTable[] = {
+               {"sprite-limits", 0, POPT_ARG_VAL, &tmp.sprite_limits, 1,
+                       "* Enable sprite limits.", NULL},
+               {"no-sprite-limits", 0, POPT_ARG_VAL, &tmp.sprite_limits, 0,
+                       "  Disable sprite limits.", NULL},
+               {"auto-fix-checksum", 0, POPT_ARG_VAL, &tmp.auto_fix_checksum, 1,
+                       "* Automatically fix checksums.", NULL},
+               {"no-auto-fix-checksum", 0, POPT_ARG_VAL, &tmp.auto_fix_checksum, 0,
+                       "  Don't automatically fix checksums.", NULL},
+               POPT_TABLEEND
+       };
+
+       // popt: UI options table.
+       struct poptOption uiOptionsTable[] = {
+               {"fps", 0, POPT_ARG_VAL, &tmp.fps_counter, 1,
+                       "* Enable the FPS counter.", NULL},
+               {"no-fps", 0, POPT_ARG_VAL, &tmp.fps_counter, 0,
+                       "  Disable the FPS counter.", NULL},
+               {"auto-pause", 0, POPT_ARG_VAL, &tmp.auto_pause, 1,
+                       "* Pause emulator when focus is lost.", NULL},
+               {"no-auto-pause", 0, POPT_ARG_VAL, &tmp.auto_pause, 0,
+                       "  Don't pause emulator when focus is lost.", NULL},
+               {"paused-effect", 0, POPT_ARG_VAL, &tmp.paused_effect, 1,
+                       "* Tint the window when paused.", NULL},
+               {"no-paused-effect", 0, POPT_ARG_VAL, &tmp.paused_effect, 0,
+                       "  Don't tint the window when paused.", NULL},
+               {"bpp", 0, POPT_ARG_INT, &tmp.bpp, 0,
+                       "  Set the internal color depth. (15, 16, 32)", "BPP"},
+               POPT_TABLEEND
+       };
+
+       // popt: Special run modes table.
+       struct poptOption runModesTable[] = {
+               {"crazy-effect", 0, POPT_ARG_VAL, &tmp.run_crazy_effect, 1,
+                       "  Run the \"Crazy\" Effect instead of loading a ROM.", NULL},
+               POPT_TABLEEND
+       };
+
+       // popt: main options table.
+       struct poptOption optionsTable[] = {
+               {"tmss-rom", 0, POPT_ARG_STRING, &tmp.tmss_rom_filename, 0,
+                       "TMSS ROM filename.", "FILENAME"},
+               {NULL, 0, POPT_ARG_INCLUDE_TABLE, audioOptionsTable, 0,
+                       "Audio options: (* indicates default)", NULL},
+               {NULL, 0, POPT_ARG_INCLUDE_TABLE, emulationOptionsTable, 0,
+                       "Emulation options: (* indicates default)", NULL},
+               {NULL, 0, POPT_ARG_INCLUDE_TABLE, uiOptionsTable, 0,
+                       "UI options: (* indicates default)", NULL},
+               {NULL, 0, POPT_ARG_INCLUDE_TABLE, runModesTable, 0,
+                       "Special run modes:", NULL},
+               {NULL, 0, POPT_ARG_INCLUDE_TABLE, helpOptionsTable, 0,
+                       "Help options:", NULL},
+               POPT_TABLEEND
+       };
+
+       // Create the popt context.
+       poptContext optCon = poptGetContext(NULL, argc, argv, optionsTable, 0);
+       poptSetOtherOptionHelp(optCon, "[rom_file]");
+       if (rom_filename_required && argc < 2) {
+               poptPrintUsage(optCon, stderr, 0);
+               return -EINVAL;
+       }
+
+       // popt: Alias '-h' to '-?'.
+       // NOTE: help_argv must be free()able, so it
+       // can't be static or allocated on the stack.
+       {
+               const char **help_argv = (const char**)malloc(sizeof(const char*) * 2);
+               help_argv[0] = "-?";
+               help_argv[1] = NULL;
+               struct poptAlias help_alias = {NULL, 'h', 1, help_argv};
+               poptAddAlias(optCon, help_alias, 0);
+       }
+
+       // Process options.
+       int c;
+       while ((c = poptGetNextOpt(optCon)) >= 0) {
+               switch (c) {
+                       case 'V':
+                               print_prg_info();
+                               fputc('\n', stderr);
+                               print_gpl();
+                               poptFreeContext(optCon);
+                               return -ECANCELED;
+
+                       case '?':
+                               print_help(optCon);
+                               poptFreeContext(optCon);
+                               return -ECANCELED;
+
+                       case 'u':
+                               poptPrintUsage(optCon, stderr, 0);
+                               poptFreeContext(optCon);
+                               return -ECANCELED;
+
+                       default:
+                               break;
+               }
+       }
+
+       if (c < -1) {
+               // An error occurred during option processing.
+               switch (c) {
+                       case POPT_ERROR_BADOPT:
+                               // Unrecognized option.
+                               fprintf(stderr, "%s: unrecognized option '%s'\n"
+                                       "Try `%s --help` for more information.\n",
+                                       argv[0], poptBadOption(optCon, POPT_BADOPTION_NOALIAS), argv[0]);
+                               break;
+                       default:
+                               // Other error.
+                               fprintf(stderr, "%s: '%s': %s\n"
+                                       "Try `%s --help` for more information.\n",
+                                       argv[0], poptBadOption(optCon, POPT_BADOPTION_NOALIAS),
+                                       poptStrerror(c), argv[0]);
+                               break;
+               }
+               poptFreeContext(optCon);
+               return -EINVAL;
+       }
+
+       // Process arguments to ensure that they're valid.
+
+       // TMSS ROM filename.
+       if (tmp.tmss_rom_filename != nullptr) {
+               // TMSS ROM filename was specified.
+               this->tmss_rom_filename = string(tmss_rom_filename);
+               this->tmss_enabled = true;
+       }
+
+       // Get the ROM filename.
+       // ROM filename is *not* required here.
+       // If the frontend can't run without a ROM,
+       // the caller will need to handle it.
+       tmp.rom_filename = poptGetArg(optCon);
+       if (tmp.rom_filename != nullptr) {
+               // ROM filename was specified.
+               this->rom_filename = string(tmp.rom_filename);
+       } else if (this->rom_filename_required && !tmp.run_crazy_effect) {
+               // A ROM is required, but wasn't specified.
+               // (If --crazy-effect is specified, this is ignored.)
+               fprintf(stderr, "%s: no ROM filename specified\n"
+                       "Try `%s --help` for more information.\n",
+                       argv[0], argv[0]);
+               poptFreeContext(optCon);
+               return -EINVAL;
+       }
+
+       // Check if too many filenames were specified.
+       if (poptPeekArg(optCon) != NULL) {
+               // Too many filenames were specified.
+               fprintf(stderr, "%s: too many parameters\n"
+                       "Try `%s --help` for more information.\n",
+                       argv[0], argv[0]);
+               poptFreeContext(optCon);
+               return -EINVAL;
+       }
+
+       // Copy other arguments.
+       // TODO: Verify that they're valid.
+
+       // Audio options.
+       this->sound_freq = tmp.sound_freq;
+       this->stereo = !!tmp.stereo;
+
+       // Emulation options.
+       this->sprite_limits = !!tmp.sprite_limits;
+       this->auto_fix_checksum = !!tmp.auto_fix_checksum;
+
+       // UI options.
+       this->fps_counter = !!tmp.fps_counter;
+       this->auto_pause = !!tmp.auto_pause;
+       this->paused_effect = !!tmp.paused_effect;
+
+       if (tmp.bpp == 15 || tmp.bpp == 16 || tmp.bpp == 32) {
+               this->bpp = (uint8_t)tmp.bpp;
+       } else {
+               // TODO: Show an error.
+               this->bpp = 32;
+       }
+
+       // Special run modes.
+       this->run_crazy_effect = !!tmp.run_crazy_effect;
+
+       // Done parsing arguments.
+       poptFreeContext(optCon);
+       return 0;
+}
+
+}
diff --git a/src/gens-sdl/Options.hpp b/src/gens-sdl/Options.hpp
new file mode 100644 (file)
index 0000000..da3b335
--- /dev/null
@@ -0,0 +1,90 @@
+/***************************************************************************
+ * gens-sdl: Gens/GS II basic SDL frontend.                                *
+ * Options.hpp: Command line option parser.                                *
+ *                                                                         *
+ * Copyright (c) 2015 by David Korth.                                      *
+ *                                                                         *
+ * 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., *
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.           *
+ ***************************************************************************/
+
+#ifndef __GENS_SDL_OPTIONS_HPP__
+#define __GENS_SDL_OPTIONS_HPP__
+
+// C++ includes.
+#include <string>
+
+namespace GensSdl {
+
+class Options
+{
+       public:
+               Options();
+
+       private:
+               // Q_DISABLE_COPY() equivalent.
+               // TODO: Add GensSdl-specific version of Q_DISABLE_COPY().
+               Options(const Options &);
+               Options &operator=(const Options &);
+
+       public:
+               /**
+                * Reset all options to their default values.
+                */
+               void reset(void);
+
+               /**
+                * Parse command line arguments.
+                * @param argc
+                * @param argv
+                * @return 0 on success; non-zero on error.
+                */
+               int parse(int argc, const char *argv[]);
+
+       public:
+               /**
+                * Is a ROM filename required?
+                * Set this before calling parse().
+                */
+               bool rom_filename_required;
+
+       public:
+               /** Command line parameters. **/
+               // TODO: Use accessors instead?
+               std::string rom_filename;       // ROM to load.
+               std::string tmss_rom_filename;  // TMSS ROM image.
+               bool tmss_enabled;              // Enable TMSS?
+
+               // Audio options.
+               int sound_freq;                 // Sound frequency.
+               bool stereo;                    // Stereo audio?
+
+               // Emulation options.
+               bool sprite_limits;             // Enable sprite limits?
+               bool auto_fix_checksum;         // Auto fix checksum?
+
+               // UI options.
+               bool fps_counter;               // Enable FPS counter?
+               bool auto_pause;                // Auto pause?
+               bool paused_effect;             // Paused effect?
+               uint8_t bpp;                    // Color depth. (15, 16, 32)
+
+               // Special run modes.
+               bool run_crazy_effect;          // Run the Crazy Effect
+                                               // instead of loading a ROM?
+};
+
+}
+
+#endif /* __GENS_SDL_OPTIONS_HPP__ */
index 3b261f0..8169e6d 100644 (file)
@@ -44,6 +44,9 @@ using GensSdl::VBackend;
 #include "EmuLoop.hpp"
 #include "CrazyEffectLoop.hpp"
 
+// Command line parameters.
+#include "Options.hpp"
+
 // OS-specific includes.
 #ifdef _WIN32
 // Windows
@@ -70,11 +73,7 @@ using std::vector;
 namespace GensSdl {
 
 /** Command line parameters. **/
-// TODO: Write a popt-based command line parser with
-// a struct containing all of the options.
-static const char *rom_filename = nullptr;
-// If true, don't emulate anything; just run the Crazy Effect.
-static bool runCrazyEffect = false;
+static Options *options = nullptr;
 
 // Event loop.
 static EventLoop *eventLoop = nullptr;
@@ -175,16 +174,16 @@ int run(void)
        // Register the LibGens OSD handler.
        lg_set_osd_fn(gsdl_osd);
 
-       if (runCrazyEffect) {
+       if (options->run_crazy_effect) {
                // Run the Crazy Effect.
-               GensSdl::eventLoop = new GensSdl::CrazyEffectLoop();
+               eventLoop = new CrazyEffectLoop();
        } else {
                // Start the emulation loop.
-               GensSdl::eventLoop = new GensSdl::EmuLoop();
+               eventLoop = new EmuLoop();
        }
        int ret = 0;
-       if (GensSdl::eventLoop) {
-               ret = GensSdl::eventLoop->run(rom_filename);
+       if (eventLoop) {
+               ret = eventLoop->run(options);
        }
 
        // Unregister the LibGens OSD handler.
@@ -208,13 +207,16 @@ int main(int argc, char *argv[])
        }
 #endif /* _WIN32 */
 
-       // TODO: Use popt; don't require a ROM filename if
-       // using "Crazy" Effect mode.
-       if (argc < 2) {
-               fprintf(stderr, "usage: %s [rom filename]\n", argv[0]);
-               return EXIT_FAILURE;
+       // Parse command line options.
+       GensSdl::options = new GensSdl::Options();
+       GensSdl::options->rom_filename_required = true;
+       int ret = GensSdl::options->parse(argc, (const char**)argv);
+       if (ret != 0) {
+               // Error parsing command line options.
+               // Options::parse() already printed an error message.
+               delete GensSdl::options;
+               return ret;
        }
-       GensSdl::rom_filename = argv[1];
 
        // Make sure we have a valid configuration directory.
        if (GensSdl::getConfigDir().empty()) {
@@ -223,12 +225,17 @@ int main(int argc, char *argv[])
        }
 
        // Initialize SDL.
-       int ret = SDL_Init(0);
+       ret = SDL_Init(0);
        if (ret < 0) {
                fprintf(stderr, "SDL initialization failed: %d - %s\n",
                        ret, SDL_GetError());
                return EXIT_FAILURE;
        }
 
-       return GensSdl::run();
+       ret = GensSdl::run();
+       // Command line options are no longer needed.
+       // TODO: Should we bother deleting it? The program's
+       // exiting here anyway...
+       delete GensSdl::options;
+       return ret;
 }