1 // license:BSD-3-Clause
2 // copyright-holders:Aaron Giles
3 /***************************************************************************
4 
5     emuopts.cpp
6 
7     Options file and command line management.
8 
9 ***************************************************************************/
10 
11 #include "emu.h"
12 #include "emuopts.h"
13 #include "drivenum.h"
14 #include "softlist_dev.h"
15 #include "hashfile.h"
16 
17 #include <stack>
18 
19 
20 //**************************************************************************
21 //  CORE EMULATOR OPTIONS
22 //**************************************************************************
23 
24 const options_entry emu_options::s_option_entries[] =
25 {
26 	// unadorned options - only a single one supported at the moment
27 	{ OPTION_SYSTEMNAME,                                 nullptr,     OPTION_STRING,     nullptr },
28 	{ OPTION_SOFTWARENAME,                               nullptr,     OPTION_STRING,     nullptr },
29 
30 	// config options
31 	{ nullptr,                                           nullptr,     OPTION_HEADER,     "CORE CONFIGURATION OPTIONS" },
32 	{ OPTION_READCONFIG ";rc",                           "1",         OPTION_BOOLEAN,    "enable loading of configuration files" },
33 	{ OPTION_WRITECONFIG ";wc",                          "0",         OPTION_BOOLEAN,    "write configuration to (driver).ini on exit" },
34 
35 	// search path options
36 	{ nullptr,                                           nullptr,     OPTION_HEADER,     "CORE SEARCH PATH OPTIONS" },
37 	{ OPTION_HOMEPATH,                                   ".",         OPTION_STRING,     "path to base folder for plugin data (read/write)" },
38 	{ OPTION_MEDIAPATH ";rp;biospath;bp",                "roms",      OPTION_STRING,     "path to ROM sets and hard disk images" },
39 	{ OPTION_HASHPATH ";hash_directory;hash",            "hash",      OPTION_STRING,     "path to software definition files" },
40 	{ OPTION_SAMPLEPATH ";sp",                           "samples",   OPTION_STRING,     "path to audio sample sets" },
41 	{ OPTION_ARTPATH,                                    "artwork",   OPTION_STRING,     "path to artwork files" },
42 	{ OPTION_CTRLRPATH,                                  "ctrlr",     OPTION_STRING,     "path to controller definitions" },
43 	{ OPTION_INIPATH,                                    ".;ini;ini/presets",     OPTION_STRING,     "path to ini files" },
44 	{ OPTION_FONTPATH,                                   ".",         OPTION_STRING,     "path to font files" },
45 	{ OPTION_CHEATPATH,                                  "cheat",     OPTION_STRING,     "path to cheat files" },
46 	{ OPTION_CROSSHAIRPATH,                              "crosshair", OPTION_STRING,     "path to crosshair files" },
47 	{ OPTION_PLUGINSPATH,                                "plugins",   OPTION_STRING,     "path to plugin files" },
48 	{ OPTION_LANGUAGEPATH,                               "language",  OPTION_STRING,     "path to UI translation files" },
49 	{ OPTION_SWPATH,                                     "software",  OPTION_STRING,     "path to loose software" },
50 
51 	// output directory options
52 	{ nullptr,                                           nullptr,     OPTION_HEADER,     "CORE OUTPUT DIRECTORY OPTIONS" },
53 	{ OPTION_CFG_DIRECTORY,                              "cfg",       OPTION_STRING,     "directory to save configurations" },
54 	{ OPTION_NVRAM_DIRECTORY,                            "nvram",     OPTION_STRING,     "directory to save NVRAM contents" },
55 	{ OPTION_INPUT_DIRECTORY,                            "inp",       OPTION_STRING,     "directory to save input device logs" },
56 	{ OPTION_STATE_DIRECTORY,                            "sta",       OPTION_STRING,     "directory to save states" },
57 	{ OPTION_SNAPSHOT_DIRECTORY,                         "snap",      OPTION_STRING,     "directory to save/load screenshots" },
58 	{ OPTION_DIFF_DIRECTORY,                             "diff",      OPTION_STRING,     "directory to save hard drive image difference files" },
59 	{ OPTION_COMMENT_DIRECTORY,                          "comments",  OPTION_STRING,     "directory to save debugger comments" },
60 
61 	// state/playback options
62 	{ nullptr,                                           nullptr,     OPTION_HEADER,     "CORE STATE/PLAYBACK OPTIONS" },
63 	{ OPTION_STATE,                                      nullptr,     OPTION_STRING,     "saved state to load" },
64 	{ OPTION_AUTOSAVE,                                   "0",         OPTION_BOOLEAN,    "automatically restore state on start and save on exit for supported systems" },
65 	{ OPTION_REWIND,                                     "0",         OPTION_BOOLEAN,    "enable rewind savestates" },
66 	{ OPTION_REWIND_CAPACITY "(1-2048)",                 "100",       OPTION_INTEGER,    "rewind buffer size in megabytes" },
67 	{ OPTION_PLAYBACK ";pb",                             nullptr,     OPTION_STRING,     "playback an input file" },
68 	{ OPTION_RECORD ";rec",                              nullptr,     OPTION_STRING,     "record an input file" },
69 	{ OPTION_RECORD_TIMECODE,                            "0",         OPTION_BOOLEAN,    "record an input timecode file (requires -record option)" },
70 	{ OPTION_EXIT_AFTER_PLAYBACK,                        "0",         OPTION_BOOLEAN,    "close the program at the end of playback" },
71 
72 	{ OPTION_MNGWRITE,                                   nullptr,     OPTION_STRING,     "optional filename to write a MNG movie of the current session" },
73 	{ OPTION_AVIWRITE,                                   nullptr,     OPTION_STRING,     "optional filename to write an AVI movie of the current session" },
74 	{ OPTION_WAVWRITE,                                   nullptr,     OPTION_STRING,     "optional filename to write a WAV file of the current session" },
75 	{ OPTION_SNAPNAME,                                   "%g/%i",     OPTION_STRING,     "override of the default snapshot/movie naming; %g == gamename, %i == index" },
76 	{ OPTION_SNAPSIZE,                                   "auto",      OPTION_STRING,     "specify snapshot/movie resolution (<width>x<height>) or 'auto' to use minimal size " },
77 	{ OPTION_SNAPVIEW,                                   "internal",  OPTION_STRING,     "specify snapshot/movie view or 'internal' to use internal pixel-aspect views" },
78 	{ OPTION_SNAPBILINEAR,                               "1",         OPTION_BOOLEAN,    "specify if the snapshot/movie should have bilinear filtering applied" },
79 	{ OPTION_STATENAME,                                  "%g",        OPTION_STRING,     "override of the default state subfolder naming; %g == gamename" },
80 	{ OPTION_BURNIN,                                     "0",         OPTION_BOOLEAN,    "create burn-in snapshots for each screen" },
81 
82 	// performance options
83 	{ nullptr,                                           nullptr,     OPTION_HEADER,     "CORE PERFORMANCE OPTIONS" },
84 	{ OPTION_AUTOFRAMESKIP ";afs",                       "0",         OPTION_BOOLEAN,    "enable automatic frameskip adjustment to maintain emulation speed" },
85 	{ OPTION_FRAMESKIP ";fs(0-10)",                      "0",         OPTION_INTEGER,    "set frameskip to fixed value, 0-10 (upper limit with autoframeskip)" },
86 	{ OPTION_SECONDS_TO_RUN ";str",                      "0",         OPTION_INTEGER,    "number of emulated seconds to run before automatically exiting" },
87 	{ OPTION_THROTTLE,                                   "1",         OPTION_BOOLEAN,    "throttle emulation to keep system running in sync with real time" },
88 	{ OPTION_SLEEP,                                      "1",         OPTION_BOOLEAN,    "enable sleeping, which gives time back to other applications when idle" },
89 	{ OPTION_SPEED "(0.01-100)",                         "1.0",       OPTION_FLOAT,      "controls the speed of gameplay, relative to realtime; smaller numbers are slower" },
90 	{ OPTION_REFRESHSPEED ";rs",                         "0",         OPTION_BOOLEAN,    "automatically adjust emulation speed to keep the emulated refresh rate slower than the host screen" },
91 	{ OPTION_LOWLATENCY ";lolat",                        "0",         OPTION_BOOLEAN,    "draws new frame before throttling to reduce input latency" },
92 
93 	// render options
94 	{ nullptr,                                           nullptr,     OPTION_HEADER,     "CORE RENDER OPTIONS" },
95 	{ OPTION_KEEPASPECT ";ka",                           "1",         OPTION_BOOLEAN,    "maintain aspect ratio when scaling to fill output screen/window" },
96 	{ OPTION_UNEVENSTRETCH ";ues",                       "1",         OPTION_BOOLEAN,    "allow non-integer ratios when scaling to fill output screen/window horizontally or vertically" },
97 	{ OPTION_UNEVENSTRETCHX ";uesx",                     "0",         OPTION_BOOLEAN,    "allow non-integer ratios when scaling to fill output screen/window horizontally"},
98 	{ OPTION_UNEVENSTRETCHY ";uesy",                     "0",         OPTION_BOOLEAN,    "allow non-integer ratios when scaling to fill otuput screen/window vertially"},
99 	{ OPTION_AUTOSTRETCHXY ";asxy",                      "0",         OPTION_BOOLEAN,    "automatically apply -unevenstretchx/y based on source native orientation"},
100 	{ OPTION_INTOVERSCAN ";ios",                         "0",         OPTION_BOOLEAN,    "allow overscan on integer scaled targets"},
101 	{ OPTION_INTSCALEX ";sx",                            "0",         OPTION_INTEGER,    "set horizontal integer scale factor"},
102 	{ OPTION_INTSCALEY ";sy",                            "0",         OPTION_INTEGER,    "set vertical integer scale factor"},
103 
104 	// rotation options
105 	{ nullptr,                                           nullptr,     OPTION_HEADER,     "CORE ROTATION OPTIONS" },
106 	{ OPTION_ROTATE,                                     "1",         OPTION_BOOLEAN,    "rotate the game screen according to the game's orientation when needed" },
107 	{ OPTION_ROR,                                        "0",         OPTION_BOOLEAN,    "rotate screen clockwise 90 degrees" },
108 	{ OPTION_ROL,                                        "0",         OPTION_BOOLEAN,    "rotate screen counterclockwise 90 degrees" },
109 	{ OPTION_AUTOROR,                                    "0",         OPTION_BOOLEAN,    "automatically rotate screen clockwise 90 degrees if vertical" },
110 	{ OPTION_AUTOROL,                                    "0",         OPTION_BOOLEAN,    "automatically rotate screen counterclockwise 90 degrees if vertical" },
111 	{ OPTION_FLIPX,                                      "0",         OPTION_BOOLEAN,    "flip screen left-right" },
112 	{ OPTION_FLIPY,                                      "0",         OPTION_BOOLEAN,    "flip screen upside-down" },
113 
114 	// artwork options
115 	{ nullptr,                                           nullptr,     OPTION_HEADER,     "CORE ARTWORK OPTIONS" },
116 	{ OPTION_ARTWORK_CROP ";artcrop",                    "0",         OPTION_BOOLEAN,    "crop artwork so emulated screen image fills output screen/window in one axis" },
117 	{ OPTION_FALLBACK_ARTWORK,                           nullptr,     OPTION_STRING,     "fallback artwork if no external artwork or internal driver layout defined" },
118 	{ OPTION_OVERRIDE_ARTWORK,                           nullptr,     OPTION_STRING,     "override artwork for external artwork and internal driver layout" },
119 
120 	// screen options
121 	{ nullptr,                                           nullptr,     OPTION_HEADER,     "CORE SCREEN OPTIONS" },
122 	{ OPTION_BRIGHTNESS "(0.1-2.0)",                     "1.0",       OPTION_FLOAT,      "default game screen brightness correction" },
123 	{ OPTION_CONTRAST "(0.1-2.0)",                       "1.0",       OPTION_FLOAT,      "default game screen contrast correction" },
124 	{ OPTION_GAMMA "(0.1-3.0)",                          "1.0",       OPTION_FLOAT,      "default game screen gamma correction" },
125 	{ OPTION_PAUSE_BRIGHTNESS "(0.0-1.0)",               "0.65",      OPTION_FLOAT,      "amount to scale the screen brightness when paused" },
126 	{ OPTION_EFFECT,                                     "none",      OPTION_STRING,     "name of a PNG file to use for visual effects, or 'none'" },
127 
128 	// vector options
129 	{ nullptr,                                           nullptr,     OPTION_HEADER,     "CORE VECTOR OPTIONS" },
130 	{ OPTION_BEAM_WIDTH_MIN,                             "1.0",       OPTION_FLOAT,      "set vector beam width minimum" },
131 	{ OPTION_BEAM_WIDTH_MAX,                             "1.0",       OPTION_FLOAT,      "set vector beam width maximum" },
132 	{ OPTION_BEAM_DOT_SIZE,                              "1.0",       OPTION_FLOAT,      "set vector beam size for dots" },
133 	{ OPTION_BEAM_INTENSITY_WEIGHT,                      "0",         OPTION_FLOAT,      "set vector beam intensity weight " },
134 	{ OPTION_FLICKER,                                    "0",         OPTION_FLOAT,      "set vector flicker effect" },
135 
136 	// sound options
137 	{ nullptr,                                           nullptr,     OPTION_HEADER,     "CORE SOUND OPTIONS" },
138 	{ OPTION_SAMPLERATE ";sr(1000-1000000)",             "48000",     OPTION_INTEGER,    "set sound output sample rate" },
139 	{ OPTION_SAMPLES,                                    "1",         OPTION_BOOLEAN,    "enable the use of external samples if available" },
140 	{ OPTION_VOLUME ";vol",                              "0",         OPTION_INTEGER,    "sound volume in decibels (-32 min, 0 max)" },
141 	{ OPTION_SPEAKER_REPORT,                             "0",         OPTION_INTEGER,    "print report of speaker ouput maxima (0=none, or 1-4 for more detail)" },
142 
143 	// input options
144 	{ nullptr,                                           nullptr,     OPTION_HEADER,     "CORE INPUT OPTIONS" },
145 	{ OPTION_COIN_LOCKOUT ";coinlock",                   "1",         OPTION_BOOLEAN,    "ignore coin inputs if coin lockout output is active" },
146 	{ OPTION_CTRLR,                                      nullptr,     OPTION_STRING,     "preconfigure for specified controller" },
147 	{ OPTION_MOUSE,                                      "0",         OPTION_BOOLEAN,    "enable mouse input" },
148 	{ OPTION_JOYSTICK ";joy",                            "1",         OPTION_BOOLEAN,    "enable joystick input" },
149 	{ OPTION_LIGHTGUN ";gun",                            "0",         OPTION_BOOLEAN,    "enable lightgun input" },
150 	{ OPTION_MULTIKEYBOARD ";multikey",                  "0",         OPTION_BOOLEAN,    "enable separate input from each keyboard device (if present)" },
151 	{ OPTION_MULTIMOUSE,                                 "0",         OPTION_BOOLEAN,    "enable separate input from each mouse device (if present)" },
152 	{ OPTION_STEADYKEY ";steady",                        "0",         OPTION_BOOLEAN,    "enable steadykey support" },
153 	{ OPTION_UI_ACTIVE,                                  "0",         OPTION_BOOLEAN,    "enable user interface on top of emulated keyboard (if present)" },
154 	{ OPTION_OFFSCREEN_RELOAD ";reload",                 "0",         OPTION_BOOLEAN,    "convert lightgun button 2 into offscreen reload" },
155 	{ OPTION_JOYSTICK_MAP ";joymap",                     "auto",      OPTION_STRING,     "explicit joystick map, or auto to auto-select" },
156 	{ 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)" },
157 	{ 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)" },
158 	{ OPTION_NATURAL_KEYBOARD ";nat",                    "0",         OPTION_BOOLEAN,    "specifies whether to use a natural keyboard or not" },
159 	{ OPTION_JOYSTICK_CONTRADICTORY ";joy_contradictory","0",         OPTION_BOOLEAN,    "enable contradictory direction digital joystick input at the same time" },
160 	{ OPTION_COIN_IMPULSE,                               "0",         OPTION_INTEGER,    "set coin impulse time (n<0 disable impulse, n==0 obey driver, 0<n set time n)" },
161 
162 	// input autoenable options
163 	{ nullptr,                                           nullptr,     OPTION_HEADER,     "CORE INPUT AUTOMATIC ENABLE OPTIONS" },
164 	{ OPTION_PADDLE_DEVICE ";paddle",                    "keyboard",  OPTION_STRING,     "enable (none|keyboard|mouse|lightgun|joystick) if a paddle control is present" },
165 	{ OPTION_ADSTICK_DEVICE ";adstick",                  "keyboard",  OPTION_STRING,     "enable (none|keyboard|mouse|lightgun|joystick) if an analog joystick control is present" },
166 	{ OPTION_PEDAL_DEVICE ";pedal",                      "keyboard",  OPTION_STRING,     "enable (none|keyboard|mouse|lightgun|joystick) if a pedal control is present" },
167 	{ OPTION_DIAL_DEVICE ";dial",                        "keyboard",  OPTION_STRING,     "enable (none|keyboard|mouse|lightgun|joystick) if a dial control is present" },
168 	{ OPTION_TRACKBALL_DEVICE ";trackball",              "keyboard",  OPTION_STRING,     "enable (none|keyboard|mouse|lightgun|joystick) if a trackball control is present" },
169 	{ OPTION_LIGHTGUN_DEVICE,                            "keyboard",  OPTION_STRING,     "enable (none|keyboard|mouse|lightgun|joystick) if a lightgun control is present" },
170 	{ OPTION_POSITIONAL_DEVICE,                          "keyboard",  OPTION_STRING,     "enable (none|keyboard|mouse|lightgun|joystick) if a positional control is present" },
171 	{ OPTION_MOUSE_DEVICE,                               "mouse",     OPTION_STRING,     "enable (none|keyboard|mouse|lightgun|joystick) if a mouse control is present" },
172 
173 	// debugging options
174 	{ nullptr,                                           nullptr,     OPTION_HEADER,     "CORE DEBUGGING OPTIONS" },
175 	{ OPTION_VERBOSE ";v",                               "0",         OPTION_BOOLEAN,    "display additional diagnostic information" },
176 	{ OPTION_LOG,                                        "0",         OPTION_BOOLEAN,    "generate an error.log file" },
177 	{ OPTION_OSLOG,                                      "0",         OPTION_BOOLEAN,    "output error.log data to system diagnostic output (debugger or standard error)" },
178 	{ OPTION_DEBUG ";d",                                 "0",         OPTION_BOOLEAN,    "enable/disable debugger" },
179 	{ OPTION_UPDATEINPAUSE,                              "0",         OPTION_BOOLEAN,    "keep calling video updates while in pause" },
180 	{ OPTION_DEBUGSCRIPT,                                nullptr,     OPTION_STRING,     "script for debugger" },
181 	{ OPTION_DEBUGLOG,                                   "0",         OPTION_BOOLEAN,    "write debug console output to debug.log" },
182 
183 	// comm options
184 	{ nullptr,                                           nullptr,     OPTION_HEADER,     "CORE COMM OPTIONS" },
185 	{ OPTION_COMM_LOCAL_HOST,                            "0.0.0.0",   OPTION_STRING,     "local address to bind to" },
186 	{ OPTION_COMM_LOCAL_PORT,                            "15112",     OPTION_STRING,     "local port to bind to" },
187 	{ OPTION_COMM_REMOTE_HOST,                           "127.0.0.1", OPTION_STRING,     "remote address to connect to" },
188 	{ OPTION_COMM_REMOTE_PORT,                           "15112",     OPTION_STRING,     "remote port to connect to" },
189 	{ OPTION_COMM_FRAME_SYNC,                            "0",         OPTION_BOOLEAN,    "sync frames" },
190 
191 	// misc options
192 	{ nullptr,                                           nullptr,     OPTION_HEADER,     "CORE MISC OPTIONS" },
193 	{ OPTION_DRC,                                        "1",         OPTION_BOOLEAN,    "enable DRC CPU core if available" },
194 	{ OPTION_DRC_USE_C,                                  "0",         OPTION_BOOLEAN,    "force DRC to use C backend" },
195 	{ OPTION_DRC_LOG_UML,                                "0",         OPTION_BOOLEAN,    "write DRC UML disassembly log" },
196 	{ OPTION_DRC_LOG_NATIVE,                             "0",         OPTION_BOOLEAN,    "write DRC native disassembly log" },
197 	{ OPTION_BIOS,                                       nullptr,     OPTION_STRING,     "select the system BIOS to use" },
198 	{ OPTION_CHEAT ";c",                                 "0",         OPTION_BOOLEAN,    "enable cheat subsystem" },
199 	{ OPTION_SKIP_GAMEINFO,                              "0",         OPTION_BOOLEAN,    "skip displaying the system information screen at startup" },
200 	{ OPTION_UI_FONT,                                    "default",   OPTION_STRING,     "specify a font to use" },
201 	{ OPTION_UI,                                         "cabinet",   OPTION_STRING,     "type of UI (simple|cabinet)" },
202 	{ OPTION_RAMSIZE ";ram",                             nullptr,     OPTION_STRING,     "size of RAM (if supported by driver)" },
203 	{ OPTION_CONFIRM_QUIT,                               "0",         OPTION_BOOLEAN,    "ask for confirmation before exiting" },
204 	{ OPTION_UI_MOUSE,                                   "1",         OPTION_BOOLEAN,    "display UI mouse cursor" },
205 	{ OPTION_LANGUAGE ";lang",                           "English",   OPTION_STRING,     "set UI display language" },
206 	{ OPTION_NVRAM_SAVE ";nvwrite",                      "1",         OPTION_BOOLEAN,    "save NVRAM data on exit" },
207 
208 	{ nullptr,                                           nullptr,     OPTION_HEADER,     "SCRIPTING OPTIONS" },
209 	{ OPTION_AUTOBOOT_COMMAND ";ab",                     nullptr,     OPTION_STRING,     "command to execute after machine boot" },
210 	{ OPTION_AUTOBOOT_DELAY,                             "0",         OPTION_INTEGER,    "delay before executing autoboot command (seconds)" },
211 	{ OPTION_AUTOBOOT_SCRIPT ";script",                  nullptr,     OPTION_STRING,     "Lua script to execute after machine boot" },
212 	{ OPTION_CONSOLE,                                    "0",         OPTION_BOOLEAN,    "enable emulator Lua console" },
213 	{ OPTION_PLUGINS,                                    "1",         OPTION_BOOLEAN,    "enable Lua plugin support" },
214 	{ OPTION_PLUGIN,                                     nullptr,     OPTION_STRING,     "list of plugins to enable" },
215 	{ OPTION_NO_PLUGIN,                                  nullptr,     OPTION_STRING,     "list of plugins to disable" },
216 
217 	{ nullptr,                                           nullptr,     OPTION_HEADER,     "HTTP SERVER OPTIONS" },
218 	{ OPTION_HTTP,                                       "0",         OPTION_BOOLEAN,    "enable HTTP server" },
219 	{ OPTION_HTTP_PORT,                                  "8080",      OPTION_INTEGER,    "HTTP server port" },
220 	{ OPTION_HTTP_ROOT,                                  "web",       OPTION_STRING,     "HTTP server document root" },
221 
222 	{ nullptr }
223 };
224 
225 
226 
227 //**************************************************************************
228 //  CUSTOM OPTION ENTRIES AND SUPPORT CLASSES
229 //**************************************************************************
230 
231 namespace
232 {
233 	// custom option entry for the system name
234 	class system_name_option_entry : public core_options::entry
235 	{
236 	public:
system_name_option_entry(emu_options & host)237 		system_name_option_entry(emu_options &host)
238 			: entry(OPTION_SYSTEMNAME)
239 			, m_host(host)
240 		{
241 		}
242 
value() const243 		virtual const char *value() const noexcept override
244 		{
245 			// This is returning an empty string instead of nullptr to signify that
246 			// specifying the value is a meaningful operation.  The option types that
247 			// return nullptr are option types that cannot have a value (e.g. - commands)
248 			//
249 			// See comments in core_options::entry::value() and core_options::simple_entry::value()
250 			return m_host.system() ? m_host.system()->name : "";
251 		}
252 
253 	protected:
internal_set_value(std::string && newvalue)254 		virtual void internal_set_value(std::string &&newvalue) override
255 		{
256 			m_host.set_system_name(std::move(newvalue));
257 		}
258 
259 	private:
260 		emu_options &m_host;
261 	};
262 
263 	// custom option entry for the software name
264 	class software_name_option_entry : public core_options::entry
265 	{
266 	public:
software_name_option_entry(emu_options & host)267 		software_name_option_entry(emu_options &host)
268 			: entry(OPTION_SOFTWARENAME)
269 			, m_host(host)
270 		{
271 		}
272 
273 	protected:
internal_set_value(std::string && newvalue)274 		virtual void internal_set_value(std::string &&newvalue) override
275 		{
276 			m_host.set_software(std::move(newvalue));
277 		}
278 
279 	private:
280 		emu_options &m_host;
281 	};
282 
283 	// custom option entry for slots
284 	class slot_option_entry : public core_options::entry
285 	{
286 	public:
slot_option_entry(const char * name,slot_option & host)287 		slot_option_entry(const char *name, slot_option &host)
288 			: entry(name)
289 			, m_host(host)
290 		{
291 		}
292 
value() const293 		virtual const char *value() const noexcept override
294 		{
295 			const char *result = nullptr;
296 			if (m_host.specified())
297 			{
298 				// m_temp is a temporary variable used to keep the specified value
299 				// so the result can be returned as 'const char *'.  Obviously, this
300 				// value will be trampled upon if value() is called again.  This doesn't
301 				// happen in practice
302 				//
303 				// In reality, I want to really return std::optional<std::string> here
304 				// FIXME: the std::string assignment can throw exceptions, and returning std::optional<std::string> also isn't safe in noexcept
305 				m_temp = m_host.specified_value();
306 				result = m_temp.c_str();
307 			}
308 			return result;
309 		}
310 
311 	protected:
internal_set_value(std::string && newvalue)312 		virtual void internal_set_value(std::string &&newvalue) override
313 		{
314 			m_host.specify(std::move(newvalue), false);
315 		}
316 
317 	private:
318 		slot_option &       m_host;
319 		mutable std::string m_temp;
320 	};
321 
322 	// custom option entry for images
323 	class image_option_entry : public core_options::entry
324 	{
325 	public:
image_option_entry(std::vector<std::string> && names,image_option & host)326 		image_option_entry(std::vector<std::string> &&names, image_option &host)
327 			: entry(std::move(names))
328 			, m_host(host)
329 		{
330 		}
331 
value() const332 		virtual const char *value() const noexcept override
333 		{
334 			return m_host.value().c_str();
335 		}
336 
337 	protected:
internal_set_value(std::string && newvalue)338 		virtual void internal_set_value(std::string &&newvalue) override
339 		{
340 			m_host.specify(std::move(newvalue), false);
341 		}
342 
343 	private:
344 		image_option &m_host;
345 	};
346 
347 	// existing option tracker class; used by slot/image calculus to identify existing
348 	// options for later purging
349 	template<typename T>
350 	class existing_option_tracker
351 	{
352 	public:
existing_option_tracker(const std::unordered_map<std::string,T> & map)353 		existing_option_tracker(const std::unordered_map<std::string, T> &map)
354 		{
355 			m_vec.reserve(map.size());
356 			for (const auto &entry : map)
357 				m_vec.push_back(&entry.first);
358 		}
359 
360 		template<typename TStr>
remove(const TStr & str)361 		void remove(const TStr &str)
362 		{
363 			auto iter = std::find_if(
364 				m_vec.begin(),
365 				m_vec.end(),
366 				[&str](const auto &x) { return *x == str; });
367 			if (iter != m_vec.end())
368 				m_vec.erase(iter);
369 		}
370 
begin()371 		std::vector<const std::string *>::iterator begin() { return m_vec.begin(); }
end()372 		std::vector<const std::string *>::iterator end() { return m_vec.end(); }
373 
374 	private:
375 		std::vector<const std::string *> m_vec;
376 	};
377 
378 
379 	//-------------------------------------------------
380 	//  get_full_option_names
381 	//-------------------------------------------------
382 
get_full_option_names(const device_image_interface & image)383 	std::vector<std::string> get_full_option_names(const device_image_interface &image)
384 	{
385 		std::vector<std::string> result;
386 		bool same_name = image.instance_name() == image.brief_instance_name();
387 
388 		result.push_back(image.instance_name());
389 		if (!same_name)
390 			result.push_back(image.brief_instance_name());
391 
392 		if (image.instance_name() != image.canonical_instance_name())
393 		{
394 			result.push_back(image.canonical_instance_name());
395 			if (!same_name)
396 				result.push_back(image.brief_instance_name() + "1");
397 		}
398 		return result;
399 	}
400 
401 
402 	//-------------------------------------------------
403 	//  conditionally_peg_priority
404 	//-------------------------------------------------
405 
conditionally_peg_priority(core_options::entry::weak_ptr & entry,bool peg_priority)406 	void conditionally_peg_priority(core_options::entry::weak_ptr &entry, bool peg_priority)
407 	{
408 		// if the [image|slot] entry was specified outside of the context of the options sytem, we need
409 		// to peg the priority of any associated core_options::entry at the maximum priority
410 		if (peg_priority && !entry.expired())
411 			entry.lock()->set_priority(OPTION_PRIORITY_MAXIMUM);
412 	}
413 }
414 
415 
416 //**************************************************************************
417 //  EMU OPTIONS
418 //**************************************************************************
419 
420 //-------------------------------------------------
421 //  emu_options - constructor
422 //-------------------------------------------------
423 
emu_options(option_support support)424 emu_options::emu_options(option_support support)
425 	: m_support(support)
426 	, m_system(nullptr)
427 	, m_coin_impulse(0)
428 	, m_joystick_contradictory(false)
429 	, m_sleep(true)
430 	, m_refresh_speed(false)
431 	, m_ui(UI_CABINET)
432 {
433 	// add entries
434 	if (support == option_support::FULL || support == option_support::GENERAL_AND_SYSTEM)
435 		add_entry(std::make_shared<system_name_option_entry>(*this));
436 	if (support == option_support::FULL)
437 		add_entry(std::make_shared<software_name_option_entry>(*this));
438 	add_entries(emu_options::s_option_entries);
439 
440 	// adding handlers to keep copies of frequently requested options in member variables
441 	set_value_changed_handler(OPTION_COIN_IMPULSE,              [this](const char *value) { m_coin_impulse = int_value(OPTION_COIN_IMPULSE); });
442 	set_value_changed_handler(OPTION_JOYSTICK_CONTRADICTORY,    [this](const char *value) { m_joystick_contradictory = bool_value(OPTION_JOYSTICK_CONTRADICTORY); });
443 	set_value_changed_handler(OPTION_SLEEP,                     [this](const char *value) { m_sleep = bool_value(OPTION_SLEEP); });
444 	set_value_changed_handler(OPTION_REFRESHSPEED,              [this](const char *value) { m_refresh_speed = bool_value(OPTION_REFRESHSPEED); });
445 	set_value_changed_handler(OPTION_UI, [this](const std::string &value)
446 	{
447 		if (value == "simple")
448 			m_ui = UI_SIMPLE;
449 		else
450 			m_ui = UI_CABINET;
451 	});
452 }
453 
454 
455 //-------------------------------------------------
456 //  emu_options - destructor
457 //-------------------------------------------------
458 
~emu_options()459 emu_options::~emu_options()
460 {
461 }
462 
463 
464 //-------------------------------------------------
465 //  system_name
466 //-------------------------------------------------
467 
system_name() const468 const char *emu_options::system_name() const
469 {
470 	return m_system ? m_system->name : "";
471 }
472 
473 
474 //-------------------------------------------------
475 //  set_system_name - called to set the system
476 //  name; will adjust slot/image options as appropriate
477 //-------------------------------------------------
478 
set_system_name(std::string && new_system_name)479 void emu_options::set_system_name(std::string &&new_system_name)
480 {
481 	const game_driver *new_system = nullptr;
482 
483 	// we are making an attempt - record what we're attempting
484 	m_attempted_system_name = std::move(new_system_name);
485 
486 	// was a system name specified?
487 	if (!m_attempted_system_name.empty())
488 	{
489 		// if so, first extract the base name (the reason for this is drag-and-drop on Windows; a side
490 		// effect is a command line like 'mame pacman.foo' will work correctly, but so be it)
491 		std::string new_system_base_name = core_filename_extract_base(m_attempted_system_name, true);
492 
493 		// perform the lookup (and error if it cannot be found)
494 		int index = driver_list::find(new_system_base_name.c_str());
495 		if (index < 0)
496 			throw options_error_exception("Unknown system '%s'", m_attempted_system_name);
497 		new_system = &driver_list::driver(index);
498 	}
499 
500 	// did we change anything?
501 	if (new_system != m_system)
502 	{
503 		// if so, specify the new system and update (if we're fully supporting slot/image options)
504 		m_system = new_system;
505 		m_software_name.clear();
506 		if (m_support == option_support::FULL)
507 			update_slot_and_image_options();
508 	}
509 }
510 
511 
512 //-------------------------------------------------
513 //  set_system_name - called to set the system
514 //  name; will adjust slot/image options as appropriate
515 //-------------------------------------------------
516 
set_system_name(const std::string & new_system_name)517 void emu_options::set_system_name(const std::string &new_system_name)
518 {
519 	set_system_name(std::string(new_system_name));
520 }
521 
522 
523 //-------------------------------------------------
524 //  update_slot_and_image_options
525 //-------------------------------------------------
526 
update_slot_and_image_options()527 void emu_options::update_slot_and_image_options()
528 {
529 	bool changed;
530 	do
531 	{
532 		changed = false;
533 
534 		// first we add and remove slot options depending on what has been configured in the
535 		// device, bringing m_slot_options up to a state where it matches machine_config
536 		if (add_and_remove_slot_options())
537 			changed = true;
538 
539 		// second, we perform an analgous operation with m_image_options
540 		if (add_and_remove_image_options())
541 			changed = true;
542 
543 		// if we changed anything, we should reevaluate existing options
544 		if (changed)
545 			reevaluate_default_card_software();
546 	} while (changed);
547 }
548 
549 
550 //-------------------------------------------------
551 //  add_and_remove_slot_options - add any missing
552 //  and/or purge extraneous slot options
553 //-------------------------------------------------
554 
add_and_remove_slot_options()555 bool emu_options::add_and_remove_slot_options()
556 {
557 	bool changed = false;
558 
559 	// first, create a list of existing slot options; this is so we can purge
560 	// any stray slot options that are no longer pertinent when we're done
561 	existing_option_tracker<::slot_option> existing(m_slot_options);
562 
563 	// it is perfectly legal for this to be called without a system; we
564 	// need to check for that condition!
565 	if (m_system)
566 	{
567 		// create the configuration
568 		machine_config config(*m_system, *this);
569 
570 		for (const device_slot_interface &slot : slot_interface_iterator(config.root_device()))
571 		{
572 			// come up with the canonical name of the slot
573 			const char *slot_option_name = slot.slot_name();
574 
575 			// erase this option from existing (so we don't purge it later)
576 			existing.remove(slot_option_name);
577 
578 			// do we need to add this option?
579 			if (!has_slot_option(slot_option_name))
580 			{
581 				// we do - add it to m_slot_options
582 				auto pair = std::make_pair(slot_option_name, ::slot_option(*this, slot.default_option()));
583 				::slot_option &new_option(m_slot_options.emplace(std::move(pair)).first->second);
584 				changed = true;
585 
586 				// for non-fixed slots, this slot needs representation in the options collection
587 				if (!slot.fixed())
588 				{
589 					// first device? add the header as to be pretty
590 					const char *header = "SLOT DEVICES";
591 					if (!header_exists(header))
592 						add_header(header);
593 
594 					// create a new entry in the options
595 					auto new_entry = new_option.setup_option_entry(slot_option_name);
596 
597 					// and add it
598 					add_entry(std::move(new_entry), header);
599 				}
600 			}
601 
602 		}
603 	}
604 
605 	// at this point we need to purge stray slot options that may no longer be pertinent
606 	for (auto &opt_name : existing)
607 	{
608 		auto iter = m_slot_options.find(*opt_name);
609 		assert(iter != m_slot_options.end());
610 
611 		// if this is represented in core_options, remove it
612 		if (iter->second.option_entry())
613 			remove_entry(*iter->second.option_entry());
614 
615 		// remove this option
616 		m_slot_options.erase(iter);
617 		changed = true;
618 	}
619 
620 	return changed;
621 }
622 
623 
624 //-------------------------------------------------
625 //  add_and_remove_slot_options - add any missing
626 //  and/or purge extraneous slot options
627 //-------------------------------------------------
628 
add_and_remove_image_options()629 bool emu_options::add_and_remove_image_options()
630 {
631 	// The logic for image options is superficially similar to the logic for slot options, but
632 	// there is one larger piece of complexity.  The image instance names (returned by the
633 	// image_instance() call and surfaced in the UI) may change simply because we've added more
634 	// devices.  This is because the instance_name() for a singular cartridge device might be
635 	// "cartridge" starting out, but become "cartridge1" when another cartridge device is added.
636 	//
637 	// To get around this behavior, our internal data structures work in terms of what is
638 	// returned by canonical_instance_name(), which will be something like "cartridge1" both
639 	// for a singular cartridge device and the first cartridge in a multi cartridge system.
640 	//
641 	// The need for this behavior was identified by Tafoid when the following command line
642 	// regressed:
643 	//
644 	//      mame snes bsxsore -cart2 bszelda
645 	//
646 	// Before we were accounting for this behavior, 'bsxsore' got stored in "cartridge" and
647 	// the association got lost when the second cartridge was added.
648 
649 	bool changed = false;
650 
651 	// first, create a list of existing image options; this is so we can purge
652 	// any stray slot options that are no longer pertinent when we're done; we
653 	// have to do this for both "flavors" of name
654 	existing_option_tracker<::image_option> existing(m_image_options_canonical);
655 
656 	// wipe the non-canonical image options; we're going to rebuild it
657 	m_image_options.clear();
658 
659 	// it is perfectly legal for this to be called without a system; we
660 	// need to check for that condition!
661 	if (m_system)
662 	{
663 		// create the configuration
664 		machine_config config(*m_system, *this);
665 
666 		// iterate through all image devices
667 		for (device_image_interface &image : image_interface_iterator(config.root_device()))
668 		{
669 			const std::string &canonical_name(image.canonical_instance_name());
670 
671 			// erase this option from existing (so we don't purge it later)
672 			existing.remove(canonical_name);
673 
674 			// do we need to add this option?
675 			auto iter = m_image_options_canonical.find(canonical_name);
676 			::image_option *this_option = iter != m_image_options_canonical.end() ? &iter->second : nullptr;
677 			if (!this_option)
678 			{
679 				// we do - add it to both m_image_options_canonical and m_image_options
680 				auto pair = std::make_pair(canonical_name, ::image_option(*this, image.canonical_instance_name()));
681 				this_option = &m_image_options_canonical.emplace(std::move(pair)).first->second;
682 				changed = true;
683 
684 				// if this image is user loadable, we have to surface it in the core_options
685 				if (image.user_loadable())
686 				{
687 					// first device? add the header as to be pretty
688 					const char *header = "IMAGE DEVICES";
689 					if (!header_exists(header))
690 						add_header(header);
691 
692 					// name this options
693 					auto names = get_full_option_names(image);
694 
695 					// create a new entry in the options
696 					auto new_entry = this_option->setup_option_entry(std::move(names));
697 
698 					// and add it
699 					add_entry(std::move(new_entry), header);
700 				}
701 			}
702 
703 			// whether we added it or we didn't, we have to add it to the m_image_option map
704 			m_image_options[image.instance_name()] = this_option;
705 		}
706 	}
707 
708 	// at this point we need to purge stray image options that may no longer be pertinent
709 	for (auto &opt_name : existing)
710 	{
711 		auto iter = m_image_options_canonical.find(*opt_name);
712 		assert(iter != m_image_options_canonical.end());
713 
714 		// if this is represented in core_options, remove it
715 		if (iter->second.option_entry())
716 			remove_entry(*iter->second.option_entry());
717 
718 		// remove this option
719 		m_image_options_canonical.erase(iter);
720 		changed = true;
721 	}
722 
723 	return changed;
724 }
725 
726 
727 //-------------------------------------------------
728 //  reevaluate_default_card_software - based on recent
729 //  changes in what images are mounted, give drivers
730 //  a chance to specify new default slot options
731 //-------------------------------------------------
732 
reevaluate_default_card_software()733 void emu_options::reevaluate_default_card_software()
734 {
735 	// if we don't have a system specified, this is
736 	// a meaningless operation
737 	if (!m_system)
738 		return;
739 
740 	bool found;
741 	do
742 	{
743 		// set up the machine_config
744 		machine_config config(*m_system, *this);
745 		found = false;
746 
747 		// iterate through all slot devices
748 		for (device_slot_interface &slot : slot_interface_iterator(config.root_device()))
749 		{
750 			// retrieve info about the device instance
751 			auto &slot_opt(slot_option(slot.slot_name()));
752 
753 			// device_slot_interface::get_default_card_software() allows a device that
754 			// implements both device_slot_interface and device_image_interface to
755 			// probe an image and specify the card device that should be loaded
756 			//
757 			// In the repeated cycle of adding slots and slot devices, this gives a chance
758 			// for devices to "plug in" default software list items.  Of course, the fact
759 			// that this is all shuffling options is brittle and roundabout, but such is
760 			// the nature of software lists.
761 			//
762 			// In reality, having some sort of hook into the pipeline of slot/device evaluation
763 			// makes sense, but the fact that it is joined at the hip to device_image_interface
764 			// and device_slot_interface is unfortunate
765 			std::string default_card_software = get_default_card_software(slot);
766 			if (slot_opt.default_card_software() != default_card_software)
767 			{
768 				slot_opt.set_default_card_software(std::move(default_card_software));
769 
770 				// calling set_default_card_software() can cause a cascade of slot/image
771 				// evaluations; we need to bail out of this loop because the iterator
772 				// may be bad
773 				found = true;
774 				break;
775 			}
776 		}
777 	} while (found);
778 }
779 
780 
781 //-------------------------------------------------
782 //  get_default_card_software
783 //-------------------------------------------------
784 
get_default_card_software(device_slot_interface & slot)785 std::string emu_options::get_default_card_software(device_slot_interface &slot)
786 {
787 	std::string image_path;
788 	std::function<bool(util::core_file &, std::string&)> get_hashfile_extrainfo;
789 
790 	// figure out if an image option has been specified, and if so, get the image path out of the options
791 	device_image_interface *image = dynamic_cast<device_image_interface *>(&slot);
792 	if (image)
793 	{
794 		image_path = image_option(image->instance_name()).value();
795 
796 		get_hashfile_extrainfo = [image, this](util::core_file &file, std::string &extrainfo)
797 		{
798 			util::hash_collection hashes = image->calculate_hash_on_file(file);
799 
800 			return hashfile_extrainfo(
801 					hash_path(),
802 					image->device().mconfig().gamedrv(),
803 					hashes,
804 					extrainfo);
805 		};
806 	}
807 
808 	// create the hook
809 	get_default_card_software_hook hook(image_path, std::move(get_hashfile_extrainfo));
810 
811 	// and invoke the slot's implementation of get_default_card_software()
812 	return slot.get_default_card_software(hook);
813 }
814 
815 
816 //-------------------------------------------------
817 //  set_software - called to load "unqualified"
818 //  software out of a software list (e.g. - "mame nes 'zelda'")
819 //-------------------------------------------------
820 
set_software(std::string && new_software)821 void emu_options::set_software(std::string &&new_software)
822 {
823 	// identify any options as a result of softlists
824 	software_options softlist_opts = evaluate_initial_softlist_options(new_software);
825 
826 	while (!softlist_opts.slot.empty() || !softlist_opts.image.empty())
827 	{
828 		// track how many options we have
829 		size_t before_size = softlist_opts.slot.size() + softlist_opts.image.size();
830 
831 		// keep a list of deferred options, in case anything is applied
832 		// out of order
833 		software_options deferred_opts;
834 
835 		// distribute slot options
836 		for (auto &slot_opt : softlist_opts.slot)
837 		{
838 			auto iter = m_slot_options.find(slot_opt.first);
839 			if (iter != m_slot_options.end())
840 				iter->second.specify(std::move(slot_opt.second));
841 			else
842 				deferred_opts.slot[slot_opt.first] = std::move(slot_opt.second);
843 		}
844 
845 		// distribute image options
846 		for (auto &image_opt : softlist_opts.image)
847 		{
848 			auto iter = m_image_options.find(image_opt.first);
849 			if (iter != m_image_options.end())
850 				iter->second->specify(std::move(image_opt.second));
851 			else
852 				deferred_opts.image[image_opt.first] = std::move(image_opt.second);
853 		}
854 
855 		// keep any deferred options for the next round
856 		softlist_opts = std::move(deferred_opts);
857 
858 		// do we have any pending options after failing to distribute any?
859 		size_t after_size = softlist_opts.slot.size() + softlist_opts.image.size();
860 		if ((after_size > 0) && after_size >= before_size)
861 			throw options_error_exception("Could not assign software option");
862 	}
863 
864 	// we've succeeded; update the set name
865 	m_software_name = std::move(new_software);
866 }
867 
868 
869 //-------------------------------------------------
870 //  evaluate_initial_softlist_options
871 //-------------------------------------------------
872 
evaluate_initial_softlist_options(const std::string & software_identifier)873 emu_options::software_options emu_options::evaluate_initial_softlist_options(const std::string &software_identifier)
874 {
875 	software_options results;
876 
877 	// load software specified at the command line (if any of course)
878 	if (!software_identifier.empty())
879 	{
880 		// we have software; first identify the proper game_driver
881 		if (!m_system)
882 			throw options_error_exception("Cannot specify software without specifying system");
883 
884 		// and set up a configuration
885 		machine_config config(*m_system, *this);
886 		software_list_device_iterator iter(config.root_device());
887 		if (iter.count() == 0)
888 			throw emu_fatalerror(EMU_ERR_FATALERROR, "Error: unknown option: %s\n", software_identifier.c_str());
889 
890 		// and finally set up the stack
891 		std::stack<std::string> software_identifier_stack;
892 		software_identifier_stack.push(software_identifier);
893 
894 		// we need to keep evaluating softlist identifiers until the stack is empty
895 		while (!software_identifier_stack.empty())
896 		{
897 			// pop the identifier
898 			std::string current_software_identifier = std::move(software_identifier_stack.top());
899 			software_identifier_stack.pop();
900 
901 			// and parse it
902 			std::string list_name, software_name;
903 			auto colon_pos = current_software_identifier.find_first_of(':');
904 			if (colon_pos != std::string::npos)
905 			{
906 				list_name = current_software_identifier.substr(0, colon_pos);
907 				software_name = current_software_identifier.substr(colon_pos + 1);
908 			}
909 			else
910 			{
911 				software_name = current_software_identifier;
912 			}
913 
914 			// loop through all softlist devices, and try to find one capable of handling the requested software
915 			bool found = false;
916 			bool compatible = false;
917 			for (software_list_device &swlistdev : iter)
918 			{
919 				if (list_name.empty() || (list_name == swlistdev.list_name()))
920 				{
921 					const software_info *swinfo = swlistdev.find(software_name);
922 					if (swinfo != nullptr)
923 					{
924 						// loop through all parts
925 						for (const software_part &swpart : swinfo->parts())
926 						{
927 							// only load compatible software this way
928 							if (swlistdev.is_compatible(swpart) == SOFTWARE_IS_COMPATIBLE)
929 							{
930 								// we need to find a mountable image slot, but we need to ensure it is a slot
931 								// for which we have not already distributed a part to
932 								device_image_interface *image = software_list_device::find_mountable_image(
933 									config,
934 									swpart,
935 									[&results](const device_image_interface &candidate) { return results.image.count(candidate.instance_name()) == 0; });
936 
937 								// did we find a slot to put this part into?
938 								if (image != nullptr)
939 								{
940 									// we've resolved this software
941 									results.image[image->instance_name()] = string_format("%s:%s:%s", swlistdev.list_name(), software_name, swpart.name());
942 
943 									// does this software part have a requirement on another part?
944 									const char *requirement = swpart.feature("requirement");
945 									if (requirement)
946 										software_identifier_stack.push(requirement);
947 								}
948 								compatible = true;
949 							}
950 							found = true;
951 						}
952 
953 						// identify other shared features specified as '<<slot name>>_default'
954 						//
955 						// example from SMS:
956 						//
957 						//  <software name = "alexbmx">
958 						//      ...
959 						//      <sharedfeat name = "ctrl1_default" value = "paddle" />
960 						//  </software>
961 						for (const feature_list_item &fi : swinfo->shared_info())
962 						{
963 							const std::string default_suffix = "_default";
964 							if (fi.name().size() > default_suffix.size()
965 								&& fi.name().compare(fi.name().size() - default_suffix.size(), default_suffix.size(), default_suffix) == 0)
966 							{
967 								std::string slot_name = fi.name().substr(0, fi.name().size() - default_suffix.size());
968 								results.slot[slot_name] = fi.value();
969 							}
970 						}
971 					}
972 				}
973 				if (compatible)
974 					break;
975 			}
976 
977 			if (!compatible)
978 			{
979 				software_list_device::display_matches(config, nullptr, software_name);
980 
981 				// The text of this options_error_exception() is then passed to osd_printf_error() in cli_frontend::execute().  Therefore, it needs
982 				// to be human readable text.  We want to snake through a message about software incompatibility while being silent if that is not
983 				// the case.
984 				//
985 				// Arguably, anything related to user-visible text should really be done within src/frontend.  The invocation of
986 				// software_list_device::display_matches() should really be done there as well
987 				if (!found)
988 					throw options_error_exception("");
989 				else
990 					throw options_error_exception("Software '%s' is incompatible with system '%s'\n", software_name, m_system->name);
991 			}
992 		}
993 	}
994 	return results;
995 }
996 
997 
998 //-------------------------------------------------
999 //  find_slot_option
1000 //-------------------------------------------------
1001 
find_slot_option(const std::string & device_name) const1002 const slot_option *emu_options::find_slot_option(const std::string &device_name) const
1003 {
1004 	auto iter = m_slot_options.find(device_name);
1005 	return iter != m_slot_options.end() ? &iter->second : nullptr;
1006 }
1007 
find_slot_option(const std::string & device_name)1008 slot_option *emu_options::find_slot_option(const std::string &device_name)
1009 {
1010 	auto iter = m_slot_options.find(device_name);
1011 	return iter != m_slot_options.end() ? &iter->second : nullptr;
1012 }
1013 
1014 
1015 
1016 //-------------------------------------------------
1017 //  slot_option
1018 //-------------------------------------------------
1019 
slot_option(const std::string & device_name) const1020 const slot_option &emu_options::slot_option(const std::string &device_name) const
1021 {
1022 	const ::slot_option *opt = find_slot_option(device_name);
1023 	assert(opt && "Attempt to access non-existent slot option");
1024 	return *opt;
1025 }
1026 
slot_option(const std::string & device_name)1027 slot_option &emu_options::slot_option(const std::string &device_name)
1028 {
1029 	::slot_option *opt = find_slot_option(device_name);
1030 	assert(opt && "Attempt to access non-existent slot option");
1031 	return *opt;
1032 }
1033 
1034 
1035 //-------------------------------------------------
1036 //  image_option
1037 //-------------------------------------------------
1038 
image_option(const std::string & device_name) const1039 const image_option &emu_options::image_option(const std::string &device_name) const
1040 {
1041 	auto iter = m_image_options.find(device_name);
1042 	assert(iter != m_image_options.end() && "Attempt to access non-existent image option");
1043 	return *iter->second;
1044 }
1045 
image_option(const std::string & device_name)1046 image_option &emu_options::image_option(const std::string &device_name)
1047 {
1048 	auto iter = m_image_options.find(device_name);
1049 	assert(iter != m_image_options.end() && "Attempt to access non-existent image option");
1050 	return *iter->second;
1051 }
1052 
1053 
1054 //-------------------------------------------------
1055 //  command_argument_processed
1056 //-------------------------------------------------
1057 
command_argument_processed()1058 void emu_options::command_argument_processed()
1059 {
1060 	// some command line arguments require that the system name be set, so we can get slot options
1061 	if (command_arguments().size() == 1 && !core_iswildstr(command_arguments()[0].c_str()) &&
1062 		(command() == "listdevices" || (command() == "listslots") || (command() == "listmedia") || (command() == "listsoftware")))
1063 	{
1064 		set_system_name(command_arguments()[0]);
1065 	}
1066 }
1067 
1068 
1069 //**************************************************************************
1070 //  SLOT OPTIONS
1071 //**************************************************************************
1072 
1073 //-------------------------------------------------
1074 //  slot_option ctor
1075 //-------------------------------------------------
1076 
slot_option(emu_options & host,const char * default_value)1077 slot_option::slot_option(emu_options &host, const char *default_value)
1078 	: m_host(host)
1079 	, m_specified(false)
1080 	, m_default_value(default_value ? default_value : "")
1081 {
1082 }
1083 
1084 
1085 //-------------------------------------------------
1086 //  slot_option::value
1087 //-------------------------------------------------
1088 
value() const1089 const std::string &slot_option::value() const
1090 {
1091 	// There are a number of ways that the value can be determined; there
1092 	// is a specific order of precedence:
1093 	//
1094 	//  1.  Highest priority is whatever may have been specified by the user (whether it
1095 	//      was specified at the command line, an INI file, or in the UI).  We keep track
1096 	//      of whether these values were specified this way
1097 	//
1098 	//      Take note that slots have a notion of being "selectable".  Slots that are not
1099 	//      marked as selectable cannot be specified with this technique
1100 	//
1101 	//  2.  Next highest is what is returned from get_default_card_software()
1102 	//
1103 	//  3.  Last in priority is what was specified as the slot default.  This comes from
1104 	//      device setup
1105 	if (m_specified)
1106 		return m_specified_value;
1107 	else if (!m_default_card_software.empty())
1108 		return m_default_card_software;
1109 	else
1110 		return m_default_value;
1111 }
1112 
1113 
1114 //-------------------------------------------------
1115 //  slot_option::specified_value
1116 //-------------------------------------------------
1117 
specified_value() const1118 std::string slot_option::specified_value() const
1119 {
1120 	std::string result;
1121 	if (m_specified)
1122 	{
1123 		result = m_specified_bios.empty()
1124 			? m_specified_value
1125 			: util::string_format("%s,bios=%s", m_specified_value, m_specified_bios);
1126 	}
1127 	return result;
1128 }
1129 
1130 
1131 //-------------------------------------------------
1132 //  slot_option::specify
1133 //-------------------------------------------------
1134 
specify(std::string && text,bool peg_priority)1135 void slot_option::specify(std::string &&text, bool peg_priority)
1136 {
1137 	// record the old value; we may need to trigger an update
1138 	const std::string old_value = value();
1139 
1140 	// we need to do some elementary parsing here
1141 	const char *bios_arg = ",bios=";
1142 	const size_t pos = text.find(bios_arg);
1143 	if (pos != std::string::npos)
1144 	{
1145 		m_specified = true;
1146 		m_specified_value = text.substr(0, pos);
1147 		m_specified_bios = text.substr(pos + strlen(bios_arg));
1148 	}
1149 	else
1150 	{
1151 		m_specified = true;
1152 		m_specified_value = std::move(text);
1153 		m_specified_bios = "";
1154 	}
1155 
1156 	conditionally_peg_priority(m_entry, peg_priority);
1157 
1158 	// we may have changed
1159 	possibly_changed(old_value);
1160 }
1161 
1162 
1163 //-------------------------------------------------
1164 //  slot_option::specify
1165 //-------------------------------------------------
1166 
specify(const std::string & text,bool peg_priority)1167 void slot_option::specify(const std::string &text, bool peg_priority)
1168 {
1169 	specify(std::string(text), peg_priority);
1170 }
1171 
1172 
1173 //-------------------------------------------------
1174 //  slot_option::set_default_card_software
1175 //-------------------------------------------------
1176 
set_default_card_software(std::string && s)1177 void slot_option::set_default_card_software(std::string &&s)
1178 {
1179 	// record the old value; we may need to trigger an update
1180 	const std::string old_value = value();
1181 
1182 	// update the default card software
1183 	m_default_card_software = std::move(s);
1184 
1185 	// we may have changed
1186 	possibly_changed(old_value);
1187 }
1188 
1189 
1190 //-------------------------------------------------
1191 //  slot_option::possibly_changed
1192 //-------------------------------------------------
1193 
possibly_changed(const std::string & old_value)1194 void slot_option::possibly_changed(const std::string &old_value)
1195 {
1196 	if (value() != old_value)
1197 		m_host.update_slot_and_image_options();
1198 }
1199 
1200 
1201 //-------------------------------------------------
1202 //  slot_option::set_bios
1203 //-------------------------------------------------
1204 
set_bios(std::string && text)1205 void slot_option::set_bios(std::string &&text)
1206 {
1207 	if (!m_specified)
1208 	{
1209 		m_specified = true;
1210 		m_specified_value = value();
1211 	}
1212 	m_specified_bios = std::move(text);
1213 }
1214 
1215 
1216 //-------------------------------------------------
1217 //  slot_option::setup_option_entry
1218 //-------------------------------------------------
1219 
setup_option_entry(const char * name)1220 core_options::entry::shared_ptr slot_option::setup_option_entry(const char *name)
1221 {
1222 	// this should only be called once
1223 	assert(m_entry.expired());
1224 
1225 	// create the entry and return it
1226 	core_options::entry::shared_ptr entry = std::make_shared<slot_option_entry>(name, *this);
1227 	m_entry = entry;
1228 	return entry;
1229 }
1230 
1231 
1232 //**************************************************************************
1233 //  IMAGE OPTIONS
1234 //**************************************************************************
1235 
1236 //-------------------------------------------------
1237 //  image_option ctor
1238 //-------------------------------------------------
1239 
image_option(emu_options & host,const std::string & canonical_instance_name)1240 image_option::image_option(emu_options &host, const std::string &canonical_instance_name)
1241 	: m_host(host)
1242 	, m_canonical_instance_name(canonical_instance_name)
1243 {
1244 }
1245 
1246 
1247 //-------------------------------------------------
1248 //  image_option::specify
1249 //-------------------------------------------------
1250 
specify(const std::string & value,bool peg_priority)1251 void image_option::specify(const std::string &value, bool peg_priority)
1252 {
1253 	if (value != m_value)
1254 	{
1255 		m_value = value;
1256 		m_host.reevaluate_default_card_software();
1257 	}
1258 	conditionally_peg_priority(m_entry, peg_priority);
1259 }
1260 
specify(std::string && value,bool peg_priority)1261 void image_option::specify(std::string &&value, bool peg_priority)
1262 {
1263 	if (value != m_value)
1264 	{
1265 		m_value = std::move(value);
1266 		m_host.reevaluate_default_card_software();
1267 	}
1268 	conditionally_peg_priority(m_entry, peg_priority);
1269 }
1270 
1271 
1272 //-------------------------------------------------
1273 //  image_option::setup_option_entry
1274 //-------------------------------------------------
1275 
setup_option_entry(std::vector<std::string> && names)1276 core_options::entry::shared_ptr image_option::setup_option_entry(std::vector<std::string> &&names)
1277 {
1278 	// this should only be called once
1279 	assert(m_entry.expired());
1280 
1281 	// create the entry and return it
1282 	core_options::entry::shared_ptr entry = std::make_shared<image_option_entry>(std::move(names), *this);
1283 	m_entry = entry;
1284 	return entry;
1285 }
1286