diff options
Diffstat (limited to 'docs/release/src/emu/emuopts.cpp')
-rw-r--r-- | docs/release/src/emu/emuopts.cpp | 1285 |
1 files changed, 1285 insertions, 0 deletions
diff --git a/docs/release/src/emu/emuopts.cpp b/docs/release/src/emu/emuopts.cpp new file mode 100644 index 00000000000..77e9f645f2f --- /dev/null +++ b/docs/release/src/emu/emuopts.cpp @@ -0,0 +1,1285 @@ +// license:BSD-3-Clause +// copyright-holders:Aaron Giles +/*************************************************************************** + + emuopts.cpp + + Options file and command line management. + +***************************************************************************/ + +#include "emu.h" +#include "emuopts.h" +#include "drivenum.h" +#include "softlist_dev.h" +#include "hashfile.h" + +#include <stack> + + +//************************************************************************** +// CORE EMULATOR OPTIONS +//************************************************************************** + +const options_entry emu_options::s_option_entries[] = +{ + // unadorned options - only a single one supported at the moment + { OPTION_SYSTEMNAME, nullptr, OPTION_STRING, nullptr }, + { OPTION_SOFTWARENAME, nullptr, OPTION_STRING, nullptr }, + + // config options + { nullptr, nullptr, OPTION_HEADER, "CORE CONFIGURATION OPTIONS" }, + { OPTION_READCONFIG ";rc", "1", OPTION_BOOLEAN, "enable loading of configuration files" }, + { OPTION_WRITECONFIG ";wc", "0", OPTION_BOOLEAN, "write configuration to (driver).ini on exit" }, + + // search path options + { nullptr, nullptr, OPTION_HEADER, "CORE SEARCH PATH OPTIONS" }, + { OPTION_HOMEPATH, ".", OPTION_STRING, "path to base folder for plugin data (read/write)" }, + { OPTION_MEDIAPATH ";rp;biospath;bp", "roms", OPTION_STRING, "path to ROM sets and hard disk images" }, + { OPTION_HASHPATH ";hash_directory;hash", "hash", OPTION_STRING, "path to software definition files" }, + { OPTION_SAMPLEPATH ";sp", "samples", OPTION_STRING, "path to audio sample sets" }, + { OPTION_ARTPATH, "artwork", OPTION_STRING, "path to artwork files" }, + { OPTION_CTRLRPATH, "ctrlr", OPTION_STRING, "path to controller definitions" }, + { OPTION_INIPATH, ".", OPTION_STRING, "path to ini files" }, // MESSUI + { OPTION_FONTPATH, ".", OPTION_STRING, "path to font files" }, + { OPTION_CHEATPATH, "cheat", OPTION_STRING, "path to cheat files" }, + { OPTION_CROSSHAIRPATH, "crosshair", OPTION_STRING, "path to crosshair files" }, + { OPTION_PLUGINSPATH, "plugins", OPTION_STRING, "path to plugin files" }, + { OPTION_LANGUAGEPATH, "language", OPTION_STRING, "path to UI translation files" }, + { OPTION_SWPATH, "software", OPTION_STRING, "path to loose software" }, + + // output directory options + { nullptr, nullptr, OPTION_HEADER, "CORE OUTPUT DIRECTORY OPTIONS" }, + { OPTION_CFG_DIRECTORY, "cfg", OPTION_STRING, "directory to save configurations" }, + { OPTION_NVRAM_DIRECTORY, "nvram", OPTION_STRING, "directory to save NVRAM contents" }, + { OPTION_INPUT_DIRECTORY, "inp", OPTION_STRING, "directory to save input device logs" }, + { OPTION_STATE_DIRECTORY, "sta", OPTION_STRING, "directory to save states" }, + { OPTION_SNAPSHOT_DIRECTORY, "snap", OPTION_STRING, "directory to save/load screenshots" }, + { OPTION_DIFF_DIRECTORY, "diff", OPTION_STRING, "directory to save hard drive image difference files" }, + { OPTION_COMMENT_DIRECTORY, "comments", OPTION_STRING, "directory to save debugger comments" }, + + // state/playback options + { nullptr, nullptr, OPTION_HEADER, "CORE STATE/PLAYBACK OPTIONS" }, + { OPTION_STATE, nullptr, OPTION_STRING, "saved state to load" }, + { OPTION_AUTOSAVE, "0", OPTION_BOOLEAN, "automatically restore state on start and save on exit for supported systems" }, + { OPTION_REWIND, "0", OPTION_BOOLEAN, "enable rewind savestates" }, + { OPTION_REWIND_CAPACITY "(1-2048)", "100", OPTION_INTEGER, "rewind buffer size in megabytes" }, + { OPTION_PLAYBACK ";pb", nullptr, OPTION_STRING, "playback an input file" }, + { OPTION_RECORD ";rec", nullptr, OPTION_STRING, "record an input file" }, + { OPTION_RECORD_TIMECODE, "0", OPTION_BOOLEAN, "record an input timecode file (requires -record option)" }, + { OPTION_EXIT_AFTER_PLAYBACK, "0", OPTION_BOOLEAN, "close the program at the end of playback" }, + + { OPTION_MNGWRITE, nullptr, OPTION_STRING, "optional filename to write a MNG movie of the current session" }, + { OPTION_AVIWRITE, nullptr, OPTION_STRING, "optional filename to write an AVI movie of the current session" }, + { OPTION_WAVWRITE, nullptr, OPTION_STRING, "optional filename to write a WAV file of the current session" }, + { OPTION_SNAPNAME, "%g/%i", OPTION_STRING, "override of the default snapshot/movie naming; %g == gamename, %i == index" }, + { OPTION_SNAPSIZE, "auto", OPTION_STRING, "specify snapshot/movie resolution (<width>x<height>) or 'auto' to use minimal size " }, + { OPTION_SNAPVIEW, "internal", OPTION_STRING, "specify snapshot/movie view or 'internal' to use internal pixel-aspect views" }, + { OPTION_SNAPBILINEAR, "1", OPTION_BOOLEAN, "specify if the snapshot/movie should have bilinear filtering applied" }, + { OPTION_STATENAME, "%g", OPTION_STRING, "override of the default state subfolder naming; %g == gamename" }, + { OPTION_BURNIN, "0", OPTION_BOOLEAN, "create burn-in snapshots for each screen" }, + + // performance options + { nullptr, nullptr, OPTION_HEADER, "CORE PERFORMANCE OPTIONS" }, + { OPTION_AUTOFRAMESKIP ";afs", "0", OPTION_BOOLEAN, "enable automatic frameskip adjustment to maintain emulation speed" }, + { OPTION_FRAMESKIP ";fs(0-10)", "0", OPTION_INTEGER, "set frameskip to fixed value, 0-10 (autoframeskip must be disabled)" }, + { OPTION_SECONDS_TO_RUN ";str", "0", OPTION_INTEGER, "number of emulated seconds to run before automatically exiting" }, + { OPTION_THROTTLE, "1", OPTION_BOOLEAN, "throttle emulation to keep system running in sync with real time" }, + { OPTION_SLEEP, "1", OPTION_BOOLEAN, "enable sleeping, which gives time back to other applications when idle" }, + { OPTION_SPEED "(0.01-100)", "1.0", OPTION_FLOAT, "controls the speed of gameplay, relative to realtime; smaller numbers are slower" }, + { OPTION_REFRESHSPEED ";rs", "0", OPTION_BOOLEAN, "automatically adjust emulation speed to keep the emulated refresh rate slower than the host screen" }, + + // render options + { nullptr, nullptr, OPTION_HEADER, "CORE RENDER OPTIONS" }, + { OPTION_KEEPASPECT ";ka", "1", OPTION_BOOLEAN, "maintain aspect ratio when scaling to fill output screen/window" }, + { OPTION_UNEVENSTRETCH ";ues", "1", OPTION_BOOLEAN, "allow non-integer ratios when scaling to fill output screen/window horizontally or vertically" }, + { OPTION_UNEVENSTRETCHX ";uesx", "0", OPTION_BOOLEAN, "allow non-integer ratios when scaling to fill output screen/window horizontally"}, + { OPTION_UNEVENSTRETCHY ";uesy", "0", OPTION_BOOLEAN, "allow non-integer ratios when scaling to fill otuput screen/window vertially"}, + { OPTION_AUTOSTRETCHXY ";asxy", "0", OPTION_BOOLEAN, "automatically apply -unevenstretchx/y based on source native orientation"}, + { OPTION_INTOVERSCAN ";ios", "0", OPTION_BOOLEAN, "allow overscan on integer scaled targets"}, + { OPTION_INTSCALEX ";sx", "0", OPTION_INTEGER, "set horizontal integer scale factor"}, + { OPTION_INTSCALEY ";sy", "0", OPTION_INTEGER, "set vertical integer scale factor"}, + + // rotation options + { nullptr, nullptr, OPTION_HEADER, "CORE ROTATION OPTIONS" }, + { OPTION_ROTATE, "1", OPTION_BOOLEAN, "rotate the game screen according to the game's orientation when needed" }, + { OPTION_ROR, "0", OPTION_BOOLEAN, "rotate screen clockwise 90 degrees" }, + { OPTION_ROL, "0", OPTION_BOOLEAN, "rotate screen counterclockwise 90 degrees" }, + { OPTION_AUTOROR, "0", OPTION_BOOLEAN, "automatically rotate screen clockwise 90 degrees if vertical" }, + { OPTION_AUTOROL, "0", OPTION_BOOLEAN, "automatically rotate screen counterclockwise 90 degrees if vertical" }, + { OPTION_FLIPX, "0", OPTION_BOOLEAN, "flip screen left-right" }, + { OPTION_FLIPY, "0", OPTION_BOOLEAN, "flip screen upside-down" }, + + // artwork options + { nullptr, nullptr, OPTION_HEADER, "CORE ARTWORK OPTIONS" }, + { OPTION_ARTWORK_CROP ";artcrop", "0", OPTION_BOOLEAN, "crop artwork so emulated screen image fills output screen/window in one axis" }, + { OPTION_USE_BACKDROPS ";backdrop", "1", OPTION_BOOLEAN, "enable backdrops if artwork is enabled and available" }, + { OPTION_USE_OVERLAYS ";overlay", "1", OPTION_BOOLEAN, "enable overlays if artwork is enabled and available" }, + { OPTION_USE_BEZELS ";bezel", "1", OPTION_BOOLEAN, "enable bezels if artwork is enabled and available" }, + { OPTION_USE_CPANELS ";cpanel", "1", OPTION_BOOLEAN, "enable cpanels if artwork is enabled and available" }, + { OPTION_USE_MARQUEES ";marquee", "1", OPTION_BOOLEAN, "enable marquees if artwork is enabled and available" }, + { OPTION_FALLBACK_ARTWORK, nullptr, OPTION_STRING, "fallback artwork if no external artwork or internal driver layout defined" }, + { OPTION_OVERRIDE_ARTWORK, nullptr, OPTION_STRING, "override artwork for external artwork and internal driver layout" }, + + // screen options + { nullptr, nullptr, OPTION_HEADER, "CORE SCREEN OPTIONS" }, + { OPTION_BRIGHTNESS "(0.1-2.0)", "1.0", OPTION_FLOAT, "default game screen brightness correction" }, + { OPTION_CONTRAST "(0.1-2.0)", "1.0", OPTION_FLOAT, "default game screen contrast correction" }, + { OPTION_GAMMA "(0.1-3.0)", "1.0", OPTION_FLOAT, "default game screen gamma correction" }, + { OPTION_PAUSE_BRIGHTNESS "(0.0-1.0)", "0.65", OPTION_FLOAT, "amount to scale the screen brightness when paused" }, + { OPTION_EFFECT, "none", OPTION_STRING, "name of a PNG file to use for visual effects, or 'none'" }, + + // vector options + { nullptr, nullptr, OPTION_HEADER, "CORE VECTOR OPTIONS" }, + { OPTION_BEAM_WIDTH_MIN, "1.0", OPTION_FLOAT, "set vector beam width minimum" }, + { OPTION_BEAM_WIDTH_MAX, "1.0", OPTION_FLOAT, "set vector beam width maximum" }, + { OPTION_BEAM_INTENSITY_WEIGHT, "0", OPTION_FLOAT, "set vector beam intensity weight " }, + { OPTION_FLICKER, "0", OPTION_FLOAT, "set vector flicker effect" }, + + // sound options + { nullptr, nullptr, OPTION_HEADER, "CORE SOUND OPTIONS" }, + { OPTION_SAMPLERATE ";sr(1000-1000000)", "48000", OPTION_INTEGER, "set sound output sample rate" }, + { OPTION_SAMPLES, "1", OPTION_BOOLEAN, "enable the use of external samples if available" }, + { OPTION_VOLUME ";vol", "0", OPTION_INTEGER, "sound volume in decibels (-32 min, 0 max)" }, + + // input options + { nullptr, nullptr, OPTION_HEADER, "CORE INPUT OPTIONS" }, + { OPTION_COIN_LOCKOUT ";coinlock", "1", OPTION_BOOLEAN, "ignore coin inputs if coin lockout ouput is active" }, + { OPTION_CTRLR, nullptr, OPTION_STRING, "preconfigure for specified controller" }, + { OPTION_MOUSE, "0", OPTION_BOOLEAN, "enable mouse input" }, + { OPTION_JOYSTICK ";joy", "1", OPTION_BOOLEAN, "enable joystick input" }, + { OPTION_LIGHTGUN ";gun", "0", OPTION_BOOLEAN, "enable lightgun input" }, + { OPTION_MULTIKEYBOARD ";multikey", "0", OPTION_BOOLEAN, "enable separate input from each keyboard device (if present)" }, + { OPTION_MULTIMOUSE, "0", OPTION_BOOLEAN, "enable separate input from each mouse device (if present)" }, + { OPTION_STEADYKEY ";steady", "0", OPTION_BOOLEAN, "enable steadykey support" }, + { OPTION_UI_ACTIVE, "0", OPTION_BOOLEAN, "enable user interface on top of emulated keyboard (if present)" }, + { OPTION_OFFSCREEN_RELOAD ";reload", "0", OPTION_BOOLEAN, "convert lightgun button 2 into offscreen reload" }, + { OPTION_JOYSTICK_MAP ";joymap", "auto", OPTION_STRING, "explicit joystick map, or auto to auto-select" }, + { OPTION_JOYSTICK_DEADZONE ";joy_deadzone;jdz(0.00-1)", "0.3", OPTION_FLOAT, "center deadzone range for joystick where change is ignored (0.0 center, 1.0 end)" }, + { OPTION_JOYSTICK_SATURATION ";joy_saturation;jsat(0.00-1)", "0.85", OPTION_FLOAT, "end of axis saturation range for joystick where change is ignored (0.0 center, 1.0 end)" }, + { OPTION_NATURAL_KEYBOARD ";nat", "0", OPTION_BOOLEAN, "specifies whether to use a natural keyboard or not" }, + { OPTION_JOYSTICK_CONTRADICTORY ";joy_contradictory","0", OPTION_BOOLEAN, "enable contradictory direction digital joystick input at the same time" }, + { OPTION_COIN_IMPULSE, "0", OPTION_INTEGER, "set coin impulse time (n<0 disable impulse, n==0 obey driver, 0<n set time n)" }, + + // input autoenable options + { nullptr, nullptr, OPTION_HEADER, "CORE INPUT AUTOMATIC ENABLE OPTIONS" }, + { OPTION_PADDLE_DEVICE ";paddle", "keyboard", OPTION_STRING, "enable (none|keyboard|mouse|lightgun|joystick) if a paddle control is present" }, + { OPTION_ADSTICK_DEVICE ";adstick", "keyboard", OPTION_STRING, "enable (none|keyboard|mouse|lightgun|joystick) if an analog joystick control is present" }, + { OPTION_PEDAL_DEVICE ";pedal", "keyboard", OPTION_STRING, "enable (none|keyboard|mouse|lightgun|joystick) if a pedal control is present" }, + { OPTION_DIAL_DEVICE ";dial", "keyboard", OPTION_STRING, "enable (none|keyboard|mouse|lightgun|joystick) if a dial control is present" }, + { OPTION_TRACKBALL_DEVICE ";trackball", "keyboard", OPTION_STRING, "enable (none|keyboard|mouse|lightgun|joystick) if a trackball control is present" }, + { OPTION_LIGHTGUN_DEVICE, "keyboard", OPTION_STRING, "enable (none|keyboard|mouse|lightgun|joystick) if a lightgun control is present" }, + { OPTION_POSITIONAL_DEVICE, "keyboard", OPTION_STRING, "enable (none|keyboard|mouse|lightgun|joystick) if a positional control is present" }, + { OPTION_MOUSE_DEVICE, "mouse", OPTION_STRING, "enable (none|keyboard|mouse|lightgun|joystick) if a mouse control is present" }, + + // debugging options + { nullptr, nullptr, OPTION_HEADER, "CORE DEBUGGING OPTIONS" }, + { OPTION_VERBOSE ";v", "0", OPTION_BOOLEAN, "display additional diagnostic information" }, + { OPTION_LOG, "0", OPTION_BOOLEAN, "generate an error.log file" }, + { OPTION_OSLOG, "0", OPTION_BOOLEAN, "output error.log data to system diagnostic output (debugger or standard error)" }, + { OPTION_DEBUG ";d", "0", OPTION_BOOLEAN, "enable/disable debugger" }, + { OPTION_UPDATEINPAUSE, "0", OPTION_BOOLEAN, "keep calling video updates while in pause" }, + { OPTION_DEBUGSCRIPT, nullptr, OPTION_STRING, "script for debugger" }, + + // comm options + { nullptr, nullptr, OPTION_HEADER, "CORE COMM OPTIONS" }, + { OPTION_COMM_LOCAL_HOST, "0.0.0.0", OPTION_STRING, "local address to bind to" }, + { OPTION_COMM_LOCAL_PORT, "15112", OPTION_STRING, "local port to bind to" }, + { OPTION_COMM_REMOTE_HOST, "127.0.0.1", OPTION_STRING, "remote address to connect to" }, + { OPTION_COMM_REMOTE_PORT, "15112", OPTION_STRING, "remote port to connect to" }, + { OPTION_COMM_FRAME_SYNC, "0", OPTION_BOOLEAN, "sync frames" }, + + // misc options + { nullptr, nullptr, OPTION_HEADER, "CORE MISC OPTIONS" }, + { OPTION_DRC, "1", OPTION_BOOLEAN, "enable DRC CPU core if available" }, + { OPTION_DRC_USE_C, "0", OPTION_BOOLEAN, "force DRC to use C backend" }, + { OPTION_DRC_LOG_UML, "0", OPTION_BOOLEAN, "write DRC UML disassembly log" }, + { OPTION_DRC_LOG_NATIVE, "0", OPTION_BOOLEAN, "write DRC native disassembly log" }, + { OPTION_BIOS, nullptr, OPTION_STRING, "select the system BIOS to use" }, + { OPTION_CHEAT ";c", "0", OPTION_BOOLEAN, "enable cheat subsystem" }, + { OPTION_SKIP_GAMEINFO, "0", OPTION_BOOLEAN, "skip displaying the system information screen at startup" }, + { OPTION_UI_FONT, "default", OPTION_STRING, "specify a font to use" }, + { OPTION_UI, "cabinet", OPTION_STRING, "type of UI (simple|cabinet)" }, + { OPTION_RAMSIZE ";ram", nullptr, OPTION_STRING, "size of RAM (if supported by driver)" }, + { OPTION_CONFIRM_QUIT, "0", OPTION_BOOLEAN, "ask for confirmation before exiting" }, + { OPTION_UI_MOUSE, "1", OPTION_BOOLEAN, "display UI mouse cursor" }, + { OPTION_LANGUAGE ";lang", "English", OPTION_STRING, "set UI display language" }, + { OPTION_NVRAM_SAVE ";nvwrite", "1", OPTION_BOOLEAN, "save NVRAM data on exit" }, + + { nullptr, nullptr, OPTION_HEADER, "SCRIPTING OPTIONS" }, + { OPTION_AUTOBOOT_COMMAND ";ab", nullptr, OPTION_STRING, "command to execute after machine boot" }, + { OPTION_AUTOBOOT_DELAY, "0", OPTION_INTEGER, "delay before executing autoboot command (seconds)" }, + { OPTION_AUTOBOOT_SCRIPT ";script", nullptr, OPTION_STRING, "Lua script to execute after machine boot" }, + { OPTION_CONSOLE, "0", OPTION_BOOLEAN, "enable emulator Lua console" }, + { OPTION_PLUGINS, "1", OPTION_BOOLEAN, "enable Lua plugin support" }, + { OPTION_PLUGIN, nullptr, OPTION_STRING, "list of plugins to enable" }, + { OPTION_NO_PLUGIN, nullptr, OPTION_STRING, "list of plugins to disable" }, + + { nullptr, nullptr, OPTION_HEADER, "HTTP SERVER OPTIONS" }, + { OPTION_HTTP, "0", OPTION_BOOLEAN, "enable HTTP server" }, + { OPTION_HTTP_PORT, "8080", OPTION_INTEGER, "HTTP server port" }, + { OPTION_HTTP_ROOT, "web", OPTION_STRING, "HTTP server document root" }, + + { nullptr } +}; + + + +//************************************************************************** +// CUSTOM OPTION ENTRIES AND SUPPORT CLASSES +//************************************************************************** + +namespace +{ + // custom option entry for the system name + class system_name_option_entry : public core_options::entry + { + public: + system_name_option_entry(emu_options &host) + : entry(OPTION_SYSTEMNAME) + , m_host(host) + { + } + + virtual const char *value() const override + { + // This is returning an empty string instead of nullptr to signify that + // specifying the value is a meaningful operation. The option types that + // return nullptr are option types that cannot have a value (e.g. - commands) + // + // See comments in core_options::entry::value() and core_options::simple_entry::value() + return m_host.system() ? m_host.system()->name : ""; + } + + protected: + virtual void internal_set_value(std::string &&newvalue) override + { + m_host.set_system_name(std::move(newvalue)); + } + + private: + emu_options &m_host; + }; + + // custom option entry for the software name + class software_name_option_entry : public core_options::entry + { + public: + software_name_option_entry(emu_options &host) + : entry(OPTION_SOFTWARENAME) + , m_host(host) + { + } + + protected: + virtual void internal_set_value(std::string &&newvalue) override + { + m_host.set_software(std::move(newvalue)); + } + + private: + emu_options &m_host; + }; + + // custom option entry for slots + class slot_option_entry : public core_options::entry + { + public: + slot_option_entry(const char *name, slot_option &host) + : entry(name) + , m_host(host) + { + } + + virtual const char *value() const override + { + const char *result = nullptr; + if (m_host.specified()) + { + // m_temp is a temporary variable used to keep the specified value + // so the result can be returned as 'const char *'. Obviously, this + // value will be trampled upon if value() is called again. This doesn't + // happen in practice + // + // In reality, I want to really return std::optional<std::string> here + m_temp = m_host.specified_value(); + result = m_temp.c_str(); + } + return result; + } + + protected: + virtual void internal_set_value(std::string &&newvalue) override + { + m_host.specify(std::move(newvalue), false); + } + + private: + slot_option & m_host; + mutable std::string m_temp; + }; + + // custom option entry for images + class image_option_entry : public core_options::entry + { + public: + image_option_entry(std::vector<std::string> &&names, image_option &host) + : entry(std::move(names)) + , m_host(host) + { + } + + virtual const char *value() const override + { + return m_host.value().c_str(); + } + + protected: + virtual void internal_set_value(std::string &&newvalue) override + { + m_host.specify(std::move(newvalue), false); + } + + private: + image_option &m_host; + }; + + // existing option tracker class; used by slot/image calculus to identify existing + // options for later purging + template<typename T> + class existing_option_tracker + { + public: + existing_option_tracker(const std::unordered_map<std::string, T> &map) + { + m_vec.reserve(map.size()); + for (const auto &entry : map) + m_vec.push_back(&entry.first); + } + + template<typename TStr> + void remove(const TStr &str) + { + auto iter = std::find_if( + m_vec.begin(), + m_vec.end(), + [&str](const auto &x) { return *x == str; }); + if (iter != m_vec.end()) + m_vec.erase(iter); + } + + std::vector<const std::string *>::iterator begin() { return m_vec.begin(); } + std::vector<const std::string *>::iterator end() { return m_vec.end(); } + + private: + std::vector<const std::string *> m_vec; + }; + + + //------------------------------------------------- + // get_full_option_names + //------------------------------------------------- + + std::vector<std::string> get_full_option_names(const device_image_interface &image) + { + std::vector<std::string> result; + bool same_name = image.instance_name() == image.brief_instance_name(); + + result.push_back(image.instance_name()); + if (!same_name) + result.push_back(image.brief_instance_name()); + + if (image.instance_name() != image.cannonical_instance_name()) + { + result.push_back(image.cannonical_instance_name()); + if (!same_name) + result.push_back(image.brief_instance_name() + "1"); + } + return result; + } + + + //------------------------------------------------- + // conditionally_peg_priority + //------------------------------------------------- + + void conditionally_peg_priority(core_options::entry::weak_ptr &entry, bool peg_priority) + { + // if the [image|slot] entry was specified outside of the context of the options sytem, we need + // to peg the priority of any associated core_options::entry at the maximum priority + if (peg_priority && !entry.expired()) + entry.lock()->set_priority(OPTION_PRIORITY_MAXIMUM); + } +} + + +//************************************************************************** +// EMU OPTIONS +//************************************************************************** + +//------------------------------------------------- +// emu_options - constructor +//------------------------------------------------- + +emu_options::emu_options(option_support support) + : m_support(support) + , m_system(nullptr) + , m_coin_impulse(0) + , m_joystick_contradictory(false) + , m_sleep(true) + , m_refresh_speed(false) + , m_ui(UI_CABINET) +{ + // add entries + if (support == option_support::FULL || support == option_support::GENERAL_AND_SYSTEM) + add_entry(std::make_shared<system_name_option_entry>(*this)); + if (support == option_support::FULL) + add_entry(std::make_shared<software_name_option_entry>(*this)); + add_entries(emu_options::s_option_entries); + + // adding handlers to keep copies of frequently requested options in member variables + set_value_changed_handler(OPTION_COIN_IMPULSE, [this](const char *value) { m_coin_impulse = int_value(OPTION_COIN_IMPULSE); }); + set_value_changed_handler(OPTION_JOYSTICK_CONTRADICTORY, [this](const char *value) { m_joystick_contradictory = bool_value(OPTION_JOYSTICK_CONTRADICTORY); }); + set_value_changed_handler(OPTION_SLEEP, [this](const char *value) { m_sleep = bool_value(OPTION_SLEEP); }); + set_value_changed_handler(OPTION_REFRESHSPEED, [this](const char *value) { m_refresh_speed = bool_value(OPTION_REFRESHSPEED); }); + set_value_changed_handler(OPTION_UI, [this](const std::string &value) + { + if (value == "simple") + m_ui = UI_SIMPLE; + else + m_ui = UI_CABINET; + }); +} + + +//------------------------------------------------- +// emu_options - destructor +//------------------------------------------------- + +emu_options::~emu_options() +{ +} + + +//------------------------------------------------- +// system_name +//------------------------------------------------- + +const char *emu_options::system_name() const +{ + return m_system ? m_system->name : ""; +} + + +//------------------------------------------------- +// set_system_name - called to set the system +// name; will adjust slot/image options as appropriate +//------------------------------------------------- + +void emu_options::set_system_name(std::string &&new_system_name) +{ + const game_driver *new_system = nullptr; + + // we are making an attempt - record what we're attempting + m_attempted_system_name = std::move(new_system_name); + + // was a system name specified? + if (!m_attempted_system_name.empty()) + { + // if so, first extract the base name (the reason for this is drag-and-drop on Windows; a side + // effect is a command line like 'mame pacman.foo' will work correctly, but so be it) + std::string new_system_base_name = core_filename_extract_base(m_attempted_system_name, true); + + // perform the lookup (and error if it cannot be found) + int index = driver_list::find(new_system_base_name.c_str()); + if (index < 0) + throw options_error_exception("Unknown system '%s'", m_attempted_system_name); + new_system = &driver_list::driver(index); + } + + // did we change anything? + if (new_system != m_system) + { + // if so, specify the new system and update (if we're fully supporting slot/image options) + m_system = new_system; + m_software_name.clear(); + if (m_support == option_support::FULL) + update_slot_and_image_options(); + } +} + + +//------------------------------------------------- +// set_system_name - called to set the system +// name; will adjust slot/image options as appropriate +//------------------------------------------------- + +void emu_options::set_system_name(const std::string &new_system_name) +{ + set_system_name(std::string(new_system_name)); +} + + +//------------------------------------------------- +// update_slot_and_image_options +//------------------------------------------------- + +void emu_options::update_slot_and_image_options() +{ + bool changed; + do + { + changed = false; + + // first we add and remove slot options depending on what has been configured in the + // device, bringing m_slot_options up to a state where it matches machine_config + if (add_and_remove_slot_options()) + changed = true; + + // second, we perform an analgous operation with m_image_options + if (add_and_remove_image_options()) + changed = true; + + // if we changed anything, we should reevaluate existing options + if (changed) + reevaluate_default_card_software(); + } while (changed); +} + + +//------------------------------------------------- +// add_and_remove_slot_options - add any missing +// and/or purge extraneous slot options +//------------------------------------------------- + +bool emu_options::add_and_remove_slot_options() +{ + bool changed = false; + + // first, create a list of existing slot options; this is so we can purge + // any stray slot options that are no longer pertinent when we're done + existing_option_tracker<::slot_option> existing(m_slot_options); + + // it is perfectly legal for this to be called without a system; we + // need to check for that condition! + if (m_system) + { + // create the configuration + machine_config config(*m_system, *this); + + for (const device_slot_interface &slot : slot_interface_iterator(config.root_device())) + { + // come up with the cannonical name of the slot + const char *slot_option_name = slot.slot_name(); + + // erase this option from existing (so we don't purge it later) + existing.remove(slot_option_name); + + // do we need to add this option? + if (!has_slot_option(slot_option_name)) + { + // we do - add it to m_slot_options + auto pair = std::make_pair(slot_option_name, ::slot_option(*this, slot.default_option())); + ::slot_option &new_option(m_slot_options.emplace(std::move(pair)).first->second); + changed = true; + + // for non-fixed slots, this slot needs representation in the options collection + if (!slot.fixed()) + { + // first device? add the header as to be pretty + const char *header = "SLOT DEVICES"; + if (!header_exists(header)) + add_header(header); + + // create a new entry in the options + auto new_entry = new_option.setup_option_entry(slot_option_name); + + // and add it + add_entry(std::move(new_entry), header); + } + } + + } + } + + // at this point we need to purge stray slot options that may no longer be pertinent + for (auto &opt_name : existing) + { + auto iter = m_slot_options.find(*opt_name); + assert(iter != m_slot_options.end()); + + // if this is represented in core_options, remove it + if (iter->second.option_entry()) + remove_entry(*iter->second.option_entry()); + + // remove this option + m_slot_options.erase(iter); + changed = true; + } + + return changed; +} + + +//------------------------------------------------- +// add_and_remove_slot_options - add any missing +// and/or purge extraneous slot options +//------------------------------------------------- + +bool emu_options::add_and_remove_image_options() +{ + // The logic for image options is superficially similar to the logic for slot options, but + // there is one larger piece of complexity. The image instance names (returned by the + // image_instance() call and surfaced in the UI) may change simply because we've added more + // devices. This is because the instance_name() for a singular cartridge device might be + // "cartridge" starting out, but become "cartridge1" when another cartridge device is added. + // + // To get around this behavior, our internal data structures work in terms of what is + // returned by cannonical_instance_name(), which will be something like "cartridge1" both + // for a singular cartridge device and the first cartridge in a multi cartridge system. + // + // The need for this behavior was identified by Tafoid when the following command line + // regressed: + // + // mame snes bsxsore -cart2 bszelda + // + // Before we were accounting for this behavior, 'bsxsore' got stored in "cartridge" and + // the association got lost when the second cartridge was added. + + bool changed = false; + + // first, create a list of existing image options; this is so we can purge + // any stray slot options that are no longer pertinent when we're done; we + // have to do this for both "flavors" of name + existing_option_tracker<::image_option> existing(m_image_options_cannonical); + + // wipe the non-cannonical image options; we're going to rebuild it + m_image_options.clear(); + + // it is perfectly legal for this to be called without a system; we + // need to check for that condition! + if (m_system) + { + // create the configuration + machine_config config(*m_system, *this); + + // iterate through all image devices + for (device_image_interface &image : image_interface_iterator(config.root_device())) + { + const std::string &cannonical_name(image.cannonical_instance_name()); + + // erase this option from existing (so we don't purge it later) + existing.remove(cannonical_name); + + // do we need to add this option? + auto iter = m_image_options_cannonical.find(cannonical_name); + ::image_option *this_option = iter != m_image_options_cannonical.end() ? &iter->second : nullptr; + if (!this_option) + { + // we do - add it to both m_image_options_cannonical and m_image_options + auto pair = std::make_pair(cannonical_name, ::image_option(*this, image.cannonical_instance_name())); + this_option = &m_image_options_cannonical.emplace(std::move(pair)).first->second; + changed = true; + + // if this image is user loadable, we have to surface it in the core_options + if (image.user_loadable()) + { + // first device? add the header as to be pretty + const char *header = "IMAGE DEVICES"; + if (!header_exists(header)) + add_header(header); + + // name this options + auto names = get_full_option_names(image); + + // create a new entry in the options + auto new_entry = this_option->setup_option_entry(std::move(names)); + + // and add it + add_entry(std::move(new_entry), header); + } + } + + // whether we added it or we didn't, we have to add it to the m_image_option map + m_image_options[image.instance_name()] = this_option; + } + } + + // at this point we need to purge stray image options that may no longer be pertinent + for (auto &opt_name : existing) + { + auto iter = m_image_options_cannonical.find(*opt_name); + assert(iter != m_image_options_cannonical.end()); + + // if this is represented in core_options, remove it + if (iter->second.option_entry()) + remove_entry(*iter->second.option_entry()); + + // remove this option + m_image_options_cannonical.erase(iter); + changed = true; + } + + return changed; +} + + +//------------------------------------------------- +// reevaluate_default_card_software - based on recent +// changes in what images are mounted, give drivers +// a chance to specify new default slot options +//------------------------------------------------- + +void emu_options::reevaluate_default_card_software() +{ + // if we don't have a system specified, this is + // a meaningless operation + if (!m_system) + return; + + bool found; + do + { + // set up the machine_config + machine_config config(*m_system, *this); + found = false; + + // iterate through all slot devices + for (device_slot_interface &slot : slot_interface_iterator(config.root_device())) + { + // retrieve info about the device instance + auto &slot_opt(slot_option(slot.slot_name())); + + // device_slot_interface::get_default_card_software() allows a device that + // implements both device_slot_interface and device_image_interface to + // probe an image and specify the card device that should be loaded + // + // In the repeated cycle of adding slots and slot devices, this gives a chance + // for devices to "plug in" default software list items. Of course, the fact + // that this is all shuffling options is brittle and roundabout, but such is + // the nature of software lists. + // + // In reality, having some sort of hook into the pipeline of slot/device evaluation + // makes sense, but the fact that it is joined at the hip to device_image_interface + // and device_slot_interface is unfortunate + std::string default_card_software = get_default_card_software(slot); + if (slot_opt.default_card_software() != default_card_software) + { + slot_opt.set_default_card_software(std::move(default_card_software)); + + // calling set_default_card_software() can cause a cascade of slot/image + // evaluations; we need to bail out of this loop because the iterator + // may be bad + found = true; + break; + } + } + } while (found); +} + + +//------------------------------------------------- +// get_default_card_software +//------------------------------------------------- + +std::string emu_options::get_default_card_software(device_slot_interface &slot) +{ + std::string image_path; + std::function<bool(util::core_file &, std::string&)> get_hashfile_extrainfo; + + // figure out if an image option has been specified, and if so, get the image path out of the options + device_image_interface *image = dynamic_cast<device_image_interface *>(&slot); + if (image) + { + image_path = image_option(image->instance_name()).value(); + + get_hashfile_extrainfo = [image, this](util::core_file &file, std::string &extrainfo) + { + util::hash_collection hashes = image->calculate_hash_on_file(file); + + return hashfile_extrainfo( + hash_path(), + image->device().mconfig().gamedrv(), + hashes, + extrainfo); + }; + } + + // create the hook + get_default_card_software_hook hook(image_path, std::move(get_hashfile_extrainfo)); + + // and invoke the slot's implementation of get_default_card_software() + return slot.get_default_card_software(hook); +} + + +//------------------------------------------------- +// set_software - called to load "unqualified" +// software out of a software list (e.g. - "mame nes 'zelda'") +//------------------------------------------------- + +void emu_options::set_software(std::string &&new_software) +{ + // identify any options as a result of softlists + software_options softlist_opts = evaluate_initial_softlist_options(new_software); + + while (!softlist_opts.slot.empty() || !softlist_opts.image.empty()) + { + // track how many options we have + size_t before_size = softlist_opts.slot.size() + softlist_opts.image.size(); + + // keep a list of deferred options, in case anything is applied + // out of order + software_options deferred_opts; + + // distribute slot options + for (auto &slot_opt : softlist_opts.slot) + { + auto iter = m_slot_options.find(slot_opt.first); + if (iter != m_slot_options.end()) + iter->second.specify(std::move(slot_opt.second)); + else + deferred_opts.slot[slot_opt.first] = std::move(slot_opt.second); + } + + // distribute image options + for (auto &image_opt : softlist_opts.image) + { + auto iter = m_image_options.find(image_opt.first); + if (iter != m_image_options.end()) + iter->second->specify(std::move(image_opt.second)); + else + deferred_opts.image[image_opt.first] = std::move(image_opt.second); + } + + // keep any deferred options for the next round + softlist_opts = std::move(deferred_opts); + + // do we have any pending options after failing to distribute any? + size_t after_size = softlist_opts.slot.size() + softlist_opts.image.size(); + if ((after_size > 0) && after_size >= before_size) + throw options_error_exception("Could not assign software option"); + } + + // we've succeeded; update the set name + m_software_name = std::move(new_software); +} + + +//------------------------------------------------- +// evaluate_initial_softlist_options +//------------------------------------------------- + +emu_options::software_options emu_options::evaluate_initial_softlist_options(const std::string &software_identifier) +{ + software_options results; + + // load software specified at the command line (if any of course) + if (!software_identifier.empty()) + { + // we have software; first identify the proper game_driver + if (!m_system) + throw options_error_exception("Cannot specify software without specifying system"); + + // and set up a configuration + machine_config config(*m_system, *this); + software_list_device_iterator iter(config.root_device()); + if (iter.count() == 0) + throw emu_fatalerror(EMU_ERR_FATALERROR, "Error: unknown option: %s\n", software_identifier.c_str()); + + // and finally set up the stack + std::stack<std::string> software_identifier_stack; + software_identifier_stack.push(software_identifier); + + // we need to keep evaluating softlist identifiers until the stack is empty + while (!software_identifier_stack.empty()) + { + // pop the identifier + std::string current_software_identifier = std::move(software_identifier_stack.top()); + software_identifier_stack.pop(); + + // and parse it + std::string list_name, software_name; + auto colon_pos = current_software_identifier.find_first_of(':'); + if (colon_pos != std::string::npos) + { + list_name = current_software_identifier.substr(0, colon_pos); + software_name = current_software_identifier.substr(colon_pos + 1); + } + else + { + software_name = current_software_identifier; + } + + // loop through all softlist devices, and try to find one capable of handling the requested software + bool found = false; + bool compatible = false; + for (software_list_device &swlistdev : iter) + { + if (list_name.empty() || (list_name == swlistdev.list_name())) + { + const software_info *swinfo = swlistdev.find(software_name); + if (swinfo != nullptr) + { + // loop through all parts + for (const software_part &swpart : swinfo->parts()) + { + // only load compatible software this way + if (swlistdev.is_compatible(swpart) == SOFTWARE_IS_COMPATIBLE) + { + // we need to find a mountable image slot, but we need to ensure it is a slot + // for which we have not already distributed a part to + device_image_interface *image = software_list_device::find_mountable_image( + config, + swpart, + [&results](const device_image_interface &candidate) { return results.image.count(candidate.instance_name()) == 0; }); + + // did we find a slot to put this part into? + if (image != nullptr) + { + // we've resolved this software + results.image[image->instance_name()] = string_format("%s:%s:%s", swlistdev.list_name(), software_name, swpart.name()); + + // does this software part have a requirement on another part? + const char *requirement = swpart.feature("requirement"); + if (requirement) + software_identifier_stack.push(requirement); + } + compatible = true; + } + found = true; + } + + // identify other shared features specified as '<<slot name>>_default' + // + // example from SMS: + // + // <software name = "alexbmx"> + // ... + // <sharedfeat name = "ctrl1_default" value = "paddle" /> + // </software> + for (const feature_list_item &fi : swinfo->shared_info()) + { + const std::string default_suffix = "_default"; + if (fi.name().size() > default_suffix.size() + && fi.name().compare(fi.name().size() - default_suffix.size(), default_suffix.size(), default_suffix) == 0) + { + std::string slot_name = fi.name().substr(0, fi.name().size() - default_suffix.size()); + results.slot[slot_name] = fi.value(); + } + } + } + } + if (compatible) + break; + } + + if (!compatible) + { + software_list_device::display_matches(config, nullptr, software_name); + + // The text of this options_error_exception() is then passed to osd_printf_error() in cli_frontend::execute(). Therefore, it needs + // to be human readable text. We want to snake through a message about software incompatibility while being silent if that is not + // the case. + // + // Arguably, anything related to user-visible text should really be done within src/frontend. The invocation of + // software_list_device::display_matches() should really be done there as well + if (!found) + throw options_error_exception(""); + else + throw options_error_exception("Software '%s' is incompatible with system '%s'\n", software_name, m_system->name); + } + } + } + return results; +} + + +//------------------------------------------------- +// find_slot_option +//------------------------------------------------- + +const slot_option *emu_options::find_slot_option(const std::string &device_name) const +{ + auto iter = m_slot_options.find(device_name); + return iter != m_slot_options.end() ? &iter->second : nullptr; +} + +slot_option *emu_options::find_slot_option(const std::string &device_name) +{ + auto iter = m_slot_options.find(device_name); + return iter != m_slot_options.end() ? &iter->second : nullptr; +} + + + +//------------------------------------------------- +// slot_option +//------------------------------------------------- + +const slot_option &emu_options::slot_option(const std::string &device_name) const +{ + const ::slot_option *opt = find_slot_option(device_name); + assert(opt && "Attempt to access non-existent slot option"); + return *opt; +} + +slot_option &emu_options::slot_option(const std::string &device_name) +{ + ::slot_option *opt = find_slot_option(device_name); + assert(opt && "Attempt to access non-existent slot option"); + return *opt; +} + + +//------------------------------------------------- +// image_option +//------------------------------------------------- + +const image_option &emu_options::image_option(const std::string &device_name) const +{ + auto iter = m_image_options.find(device_name); + assert(iter != m_image_options.end() && "Attempt to access non-existent image option"); + return *iter->second; +} + +image_option &emu_options::image_option(const std::string &device_name) +{ + auto iter = m_image_options.find(device_name); + assert(iter != m_image_options.end() && "Attempt to access non-existent image option"); + return *iter->second; +} + + +//------------------------------------------------- +// command_argument_processed +//------------------------------------------------- + +void emu_options::command_argument_processed() +{ + // some command line arguments require that the system name be set, so we can get slot options + if (command_arguments().size() == 1 && !core_iswildstr(command_arguments()[0].c_str()) && + (command() == "listdevices" || (command() == "listslots") || (command() == "listmedia"))) + { + set_system_name(command_arguments()[0]); + } +} + + +//************************************************************************** +// SLOT OPTIONS +//************************************************************************** + +//------------------------------------------------- +// slot_option ctor +//------------------------------------------------- + +slot_option::slot_option(emu_options &host, const char *default_value) + : m_host(host) + , m_specified(false) + , m_default_value(default_value ? default_value : "") +{ +} + + +//------------------------------------------------- +// slot_option::value +//------------------------------------------------- + +const std::string &slot_option::value() const +{ + // There are a number of ways that the value can be determined; there + // is a specific order of precedence: + // + // 1. Highest priority is whatever may have been specified by the user (whether it + // was specified at the command line, an INI file, or in the UI). We keep track + // of whether these values were specified this way + // + // Take note that slots have a notion of being "selectable". Slots that are not + // marked as selectable cannot be specified with this technique + // + // 2. Next highest is what is returned from get_default_card_software() + // + // 3. Last in priority is what was specified as the slot default. This comes from + // device setup + if (m_specified) + return m_specified_value; + else if (!m_default_card_software.empty()) + return m_default_card_software; + else + return m_default_value; +} + + +//------------------------------------------------- +// slot_option::specified_value +//------------------------------------------------- + +std::string slot_option::specified_value() const +{ + std::string result; + if (m_specified) + { + result = m_specified_bios.empty() + ? m_specified_value + : util::string_format("%s,bios=%s", m_specified_value, m_specified_bios); + } + return result; +} + + +//------------------------------------------------- +// slot_option::specify +//------------------------------------------------- + +void slot_option::specify(std::string &&text, bool peg_priority) +{ + // record the old value; we may need to trigger an update + const std::string old_value = value(); + + // we need to do some elementary parsing here + const char *bios_arg = ",bios="; + const size_t pos = text.find(bios_arg); + if (pos != std::string::npos) + { + m_specified = true; + m_specified_value = text.substr(0, pos); + m_specified_bios = text.substr(pos + strlen(bios_arg)); + } + else + { + m_specified = true; + m_specified_value = std::move(text); + m_specified_bios = ""; + } + + conditionally_peg_priority(m_entry, peg_priority); + + // we may have changed + possibly_changed(old_value); +} + + +//------------------------------------------------- +// slot_option::specify +//------------------------------------------------- + +void slot_option::specify(const std::string &text, bool peg_priority) +{ + specify(std::string(text), peg_priority); +} + + +//------------------------------------------------- +// slot_option::set_default_card_software +//------------------------------------------------- + +void slot_option::set_default_card_software(std::string &&s) +{ + // record the old value; we may need to trigger an update + const std::string old_value = value(); + + // update the default card software + m_default_card_software = std::move(s); + + // we may have changed + possibly_changed(old_value); +} + + +//------------------------------------------------- +// slot_option::possibly_changed +//------------------------------------------------- + +void slot_option::possibly_changed(const std::string &old_value) +{ + if (value() != old_value) + m_host.update_slot_and_image_options(); +} + + +//------------------------------------------------- +// slot_option::set_bios +//------------------------------------------------- + +void slot_option::set_bios(std::string &&text) +{ + if (!m_specified) + { + m_specified = true; + m_specified_value = value(); + } + m_specified_bios = std::move(text); +} + + +//------------------------------------------------- +// slot_option::setup_option_entry +//------------------------------------------------- + +core_options::entry::shared_ptr slot_option::setup_option_entry(const char *name) +{ + // this should only be called once + assert(m_entry.expired()); + + // create the entry and return it + core_options::entry::shared_ptr entry = std::make_shared<slot_option_entry>(name, *this); + m_entry = entry; + return entry; +} + + +//************************************************************************** +// IMAGE OPTIONS +//************************************************************************** + +//------------------------------------------------- +// image_option ctor +//------------------------------------------------- + +image_option::image_option(emu_options &host, const std::string &cannonical_instance_name) + : m_host(host) + , m_canonical_instance_name(cannonical_instance_name) +{ +} + + +//------------------------------------------------- +// image_option::specify +//------------------------------------------------- + +void image_option::specify(const std::string &value, bool peg_priority) +{ + if (value != m_value) + { + m_value = value; + m_host.reevaluate_default_card_software(); + } + conditionally_peg_priority(m_entry, peg_priority); +} + +void image_option::specify(std::string &&value, bool peg_priority) +{ + if (value != m_value) + { + m_value = std::move(value); + m_host.reevaluate_default_card_software(); + } + conditionally_peg_priority(m_entry, peg_priority); +} + + +//------------------------------------------------- +// image_option::setup_option_entry +//------------------------------------------------- + +core_options::entry::shared_ptr image_option::setup_option_entry(std::vector<std::string> &&names) +{ + // this should only be called once + assert(m_entry.expired()); + + // create the entry and return it + core_options::entry::shared_ptr entry = std::make_shared<image_option_entry>(std::move(names), *this); + m_entry = entry; + return entry; +} |