summaryrefslogtreecommitdiffstats
path: root/docs/release/src/emu
diff options
context:
space:
mode:
Diffstat (limited to 'docs/release/src/emu')
-rw-r--r--docs/release/src/emu/digfx.h206
-rw-r--r--docs/release/src/emu/emuopts.cpp1300
-rw-r--r--docs/release/src/emu/emuopts.h547
-rw-r--r--docs/release/src/emu/romload.cpp1503
-rw-r--r--docs/release/src/emu/validity.cpp2848
-rw-r--r--docs/release/src/emu/video.cpp1275
-rw-r--r--docs/release/src/emu/video.h192
7 files changed, 7871 insertions, 0 deletions
diff --git a/docs/release/src/emu/digfx.h b/docs/release/src/emu/digfx.h
new file mode 100644
index 00000000000..0e5e2eb16e1
--- /dev/null
+++ b/docs/release/src/emu/digfx.h
@@ -0,0 +1,206 @@
+// license:BSD-3-Clause
+// copyright-holders:Nicola Salmoria, Aaron Giles, Alex W. Jackson
+/***************************************************************************
+
+ digfx.h
+
+ Device graphics interfaces.
+
+***************************************************************************/
+
+#pragma once
+
+#ifndef __EMU_H__
+#error Dont include this file directly; include emu.h instead.
+#endif
+
+#ifndef MAME_EMU_DIGFX_H
+#define MAME_EMU_DIGFX_H
+
+
+
+//**************************************************************************
+// CONSTANTS
+//**************************************************************************
+
+constexpr u8 MAX_GFX_ELEMENTS = 32;
+constexpr u16 MAX_GFX_PLANES = 8;
+// HBMAME - needed by monaco
+constexpr u16 MAX_GFX_SIZE = 64;
+
+
+
+//**************************************************************************
+// GRAPHICS LAYOUT MACROS
+//**************************************************************************
+
+#define EXTENDED_XOFFS { 0 }
+#define EXTENDED_YOFFS { 0 }
+
+#define GFX_RAW 0x12345678
+#define GFXLAYOUT_RAW( name, width, height, linemod, charmod ) \
+const gfx_layout name = { width, height, RGN_FRAC(1,1), 8, { GFX_RAW }, { 0 }, { linemod }, charmod };
+// When planeoffset[0] is set to GFX_RAW, the gfx data is left as-is, with no conversion.
+// No buffer is allocated for the decoded data, and gfxdata is set to point to the source
+// data.
+// yoffset[0] is the line modulo (*8) and charincrement the char modulo (*8). They are *8
+// for consistency with the usual behaviour, but the bottom 3 bits are not used.
+//
+// This special mode can be used for graphics that are already in 8bpp linear format,
+// or for unusual formats that don't fit our generic model and need to be decoded using
+// custom code. See blend_gfx() in atarigen.c for an example of the latter usage.
+
+
+// these macros describe gfx_layouts in terms of fractions of a region
+// they can be used for total, planeoffset, xoffset, yoffset
+#define RGN_FRAC(num,den) (0x80000000 | (((num) & 0x0f) << 27) | (((den) & 0x0f) << 23))
+#define IS_FRAC(offset) ((offset) & 0x80000000)
+#define FRAC_NUM(offset) (((offset) >> 27) & 0x0f)
+#define FRAC_DEN(offset) (((offset) >> 23) & 0x0f)
+#define FRAC_OFFSET(offset) ((offset) & 0x007fffff)
+
+// these macros are useful in gfx_layouts
+#define STEP2(START,STEP) (START),(START)+(STEP)
+#define STEP4(START,STEP) STEP2(START,STEP),STEP2((START)+2*(STEP),STEP)
+#define STEP8(START,STEP) STEP4(START,STEP),STEP4((START)+4*(STEP),STEP)
+#define STEP16(START,STEP) STEP8(START,STEP),STEP8((START)+8*(STEP),STEP)
+#define STEP32(START,STEP) STEP16(START,STEP),STEP16((START)+16*(STEP),STEP)
+#define STEP64(START,STEP) STEP32(START,STEP),STEP32((START)+32*(STEP),STEP)
+#define STEP128(START,STEP) STEP64(START,STEP),STEP64((START)+64*(STEP),STEP)
+#define STEP256(START,STEP) STEP128(START,STEP),STEP128((START)+128*(STEP),STEP)
+#define STEP512(START,STEP) STEP256(START,STEP),STEP256((START)+256*(STEP),STEP)
+#define STEP1024(START,STEP) STEP512(START,STEP),STEP512((START)+512*(STEP),STEP)
+#define STEP2048(START,STEP) STEP1024(START,STEP),STEP1024((START)+1024*(STEP),STEP)
+
+#define STEP2_INV(START,STEP) (START)+(STEP),(START)
+#define STEP4_INV(START,STEP) STEP2_INV(START+2*STEP,STEP),STEP2_INV(START,STEP)
+
+//**************************************************************************
+// GRAPHICS INFO MACROS
+//**************************************************************************
+
+// optional horizontal and vertical scaling factors
+#define GFXENTRY_XSCALEMASK 0x000000ff
+#define GFXENTRY_YSCALEMASK 0x0000ff00
+#define GFXENTRY_XSCALE(x) ((((x)-1) << 0) & GFXENTRY_XSCALEMASK)
+#define GFXENTRY_YSCALE(x) ((((x)-1) << 8) & GFXENTRY_YSCALEMASK)
+#define GFXENTRY_GETXSCALE(x) ((((x) & GFXENTRY_XSCALEMASK) >> 0) + 1)
+#define GFXENTRY_GETYSCALE(x) ((((x) & GFXENTRY_YSCALEMASK) >> 8) + 1)
+
+// GFXENTRY_RAM means region tag refers to a RAM share instead of a ROM region
+#define GFXENTRY_ROM 0x00000000
+#define GFXENTRY_RAM 0x00010000
+#define GFXENTRY_ISROM(x) (((x) & GFXENTRY_RAM) == 0)
+#define GFXENTRY_ISRAM(x) (((x) & GFXENTRY_RAM) != 0)
+
+// GFXENTRY_DEVICE means region tag is relative to this device instead of its owner
+#define GFXENTRY_DEVICE 0x00020000
+#define GFXENTRY_ISDEVICE(x) (((x) & GFXENTRY_DEVICE) != 0)
+
+// GFXENTRY_REVERSE reverses the bit order in the layout (0-7 = LSB-MSB instead of MSB-LSB)
+#define GFXENTRY_REVERSE 0x00040000
+#define GFXENTRY_ISREVERSE(x) (((x) & GFXENTRY_REVERSE) != 0)
+
+
+// these macros are used for declaring gfx_decode_entry info arrays
+#define GFXDECODE_START( name ) const gfx_decode_entry name[] = {
+#define GFXDECODE_END { 0 } };
+
+// use these to declare a gfx_decode_entry array as a member of a device class
+#define DECLARE_GFXDECODE_MEMBER( name ) static const gfx_decode_entry name[]
+#define GFXDECODE_MEMBER( name ) const gfx_decode_entry name[] = {
+// common gfx_decode_entry macros
+#define GFXDECODE_ENTRYX(region,offset,layout,start,colors,flags) { region, offset, &layout, start, colors, flags },
+#define GFXDECODE_ENTRY(region,offset,layout,start,colors) { region, offset, &layout, start, colors, 0 },
+
+// specialized gfx_decode_entry macros
+#define GFXDECODE_RAM(region,offset,layout,start,colors) { region, offset, &layout, start, colors, GFXENTRY_RAM },
+#define GFXDECODE_DEVICE(region,offset,layout,start,colors) { region, offset, &layout, start, colors, GFXENTRY_DEVICE },
+#define GFXDECODE_DEVICE_RAM(region,offset,layout,start,colors) { region, offset, &layout, start, colors, GFXENTRY_DEVICE | GFXENTRY_RAM },
+#define GFXDECODE_SCALE(region,offset,layout,start,colors,x,y) { region, offset, &layout, start, colors, GFXENTRY_XSCALE(x) | GFXENTRY_YSCALE(y) },
+#define GFXDECODE_REVERSEBITS(region,offset,layout,start,colors) { region, offset, &layout, start, colors, GFXENTRY_REVERSE },
+
+
+
+//**************************************************************************
+// TYPE DEFINITIONS
+//**************************************************************************
+
+struct gfx_layout
+{
+ u32 xoffs(int x) const { return (extxoffs != nullptr) ? extxoffs[x] : xoffset[x]; }
+ u32 yoffs(int y) const { return (extyoffs != nullptr) ? extyoffs[y] : yoffset[y]; }
+
+ u16 width; // pixel width of each element
+ u16 height; // pixel height of each element
+ u32 total; // total number of elements, or RGN_FRAC()
+ u16 planes; // number of bitplanes
+ u32 planeoffset[MAX_GFX_PLANES]; // bit offset of each bitplane
+ u32 xoffset[MAX_GFX_SIZE]; // bit offset of each horizontal pixel
+ u32 yoffset[MAX_GFX_SIZE]; // bit offset of each vertical pixel
+ u32 charincrement; // distance between two consecutive elements (in bits)
+ const u32 * extxoffs; // extended X offset array for really big layouts
+ const u32 * extyoffs; // extended Y offset array for really big layouts
+};
+
+struct gfx_decode_entry
+{
+ const char * memory_region; // memory region where the data resides
+ u32 start; // offset of beginning of data to decode
+ const gfx_layout *gfxlayout; // pointer to gfx_layout describing the layout; nullptr marks the end of the array
+ u16 color_codes_start; // offset in the color lookup table where color codes start
+ u16 total_color_codes; // total number of color codes
+ u32 flags; // flags and optional scaling factors
+};
+
+// ======================> device_gfx_interface
+
+class device_gfx_interface : public device_interface
+{
+public:
+ static const gfx_decode_entry empty[];
+
+ // construction/destruction
+ device_gfx_interface(const machine_config &mconfig, device_t &device,
+ const gfx_decode_entry *gfxinfo = nullptr, const char *palette_tag = finder_base::DUMMY_TAG);
+ virtual ~device_gfx_interface();
+
+ // configuration
+ void set_info(const gfx_decode_entry *gfxinfo) { m_gfxdecodeinfo = gfxinfo; }
+ template <typename T> void set_palette(T &&tag) { m_palette.set_tag(std::forward<T>(tag)); }
+
+ void set_palette_disable(bool disable);
+
+ // getters
+ device_palette_interface &palette() const { assert(m_palette); return *m_palette; }
+ gfx_element *gfx(u8 index) const { assert(index < MAX_GFX_ELEMENTS); return m_gfx[index].get(); }
+
+ // decoding
+ void decode_gfx(const gfx_decode_entry *gfxdecodeinfo);
+ void decode_gfx() { decode_gfx(m_gfxdecodeinfo); }
+
+ void set_gfx(u8 index, std::unique_ptr<gfx_element> &&element) { assert(index < MAX_GFX_ELEMENTS); m_gfx[index] = std::move(element); }
+
+protected:
+ // interface-level overrides
+ virtual void interface_validity_check(validity_checker &valid) const override;
+ virtual void interface_pre_start() override;
+ virtual void interface_post_start() override;
+
+private:
+ optional_device<device_palette_interface> m_palette; // configured tag for palette device
+ std::unique_ptr<gfx_element> m_gfx[MAX_GFX_ELEMENTS]; // array of pointers to graphic sets
+
+ // configuration
+ const gfx_decode_entry * m_gfxdecodeinfo; // pointer to array of gfx decode information
+ bool m_palette_is_disabled; // no palette associated with this gfx decode
+
+ // internal state
+ bool m_decoded; // have we processed our decode info yet?
+};
+
+// iterator
+typedef device_interface_enumerator<device_gfx_interface> gfx_interface_enumerator;
+
+
+#endif /* MAME_EMU_DIGFX_H */
diff --git a/docs/release/src/emu/emuopts.cpp b/docs/release/src/emu/emuopts.cpp
new file mode 100644
index 00000000000..7211a7cafcb
--- /dev/null
+++ b/docs/release/src/emu/emuopts.cpp
@@ -0,0 +1,1300 @@
+// 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 "corestr.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_PLUGINDATAPATH, ".", 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" },
+ { OPTION_SHARE_DIRECTORY, "share", OPTION_STRING, "directory to share with emulated machines" },
+
+ // 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, "auto", OPTION_STRING, "snapshot/movie view - 'auto' for default, or 'native' for per-screen 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 (upper limit with autoframeskip)" },
+ { 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" },
+ { OPTION_LOWLATENCY ";lolat", "0", OPTION_BOOLEAN, "draws new frame before throttling to reduce input latency" },
+
+ // 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_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_DOT_SIZE, "1.0", OPTION_FLOAT, "set vector beam size for dots" },
+ { 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)" },
+ { OPTION_COMPRESSOR, "1", OPTION_BOOLEAN, "enable compressor for sound" },
+ { OPTION_SPEAKER_REPORT "(0-4)", "0", OPTION_INTEGER, "print report of speaker ouput maxima (0=none, or 1-4 for more detail)" },
+
+ // input options
+ { nullptr, nullptr, OPTION_HEADER, "CORE INPUT OPTIONS" },
+ { OPTION_COIN_LOCKOUT ";coinlock", "1", OPTION_BOOLEAN, "ignore coin inputs if coin lockout output 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" },
+ { OPTION_DEBUGLOG, "0", OPTION_BOOLEAN, "write debug console output to debug.log" },
+
+ // 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 noexcept 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 noexcept 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
+ // FIXME: the std::string assignment can throw exceptions, and returning std::optional<std::string> also isn't safe in noexcept
+ 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 noexcept 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.canonical_instance_name())
+ {
+ result.push_back(image.canonical_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();
+ }
+}
+
+
+//-------------------------------------------------
+// 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_enumerator(config.root_device()))
+ {
+ // come up with the canonical 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 canonical_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_canonical);
+
+ // wipe the non-canonical 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_enumerator(config.root_device()))
+ {
+ const std::string &canonical_name(image.canonical_instance_name());
+
+ // erase this option from existing (so we don't purge it later)
+ existing.remove(canonical_name);
+
+ // do we need to add this option?
+ auto iter = m_image_options_canonical.find(canonical_name);
+ ::image_option *this_option = iter != m_image_options_canonical.end() ? &iter->second : nullptr;
+ if (!this_option)
+ {
+ // we do - add it to both m_image_options_canonical and m_image_options
+ auto pair = std::make_pair(canonical_name, ::image_option(*this, image.canonical_instance_name()));
+ this_option = &m_image_options_canonical.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_canonical.find(*opt_name);
+ assert(iter != m_image_options_canonical.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_canonical.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_enumerator(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_enumerator iter(config.root_device());
+ if (iter.count() == 0)
+ throw emu_fatalerror(EMU_ERR_FATALERROR, "Error: unknown option: %s\n", software_identifier);
+
+ // 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 software_info_item &fi : swinfo->shared_features())
+ {
+ 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") || (command() == "listsoftware")))
+ {
+ 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 std::string::size_type 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(std::string_view 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 std::string_view::size_type pos = text.find(bios_arg);
+ if (pos != std::string_view::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 = text;
+ m_specified_bios = "";
+ }
+
+ conditionally_peg_priority(m_entry, peg_priority);
+
+ // we may have changed
+ possibly_changed(old_value);
+}
+
+
+//-------------------------------------------------
+// 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 &canonical_instance_name)
+ : m_host(host)
+ , m_canonical_instance_name(canonical_instance_name)
+{
+}
+
+
+//-------------------------------------------------
+// image_option::specify
+//-------------------------------------------------
+
+void image_option::specify(std::string_view 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;
+}
diff --git a/docs/release/src/emu/emuopts.h b/docs/release/src/emu/emuopts.h
new file mode 100644
index 00000000000..59f651d946f
--- /dev/null
+++ b/docs/release/src/emu/emuopts.h
@@ -0,0 +1,547 @@
+// license:BSD-3-Clause
+// copyright-holders:Aaron Giles
+/***************************************************************************
+
+ emuopts.h
+
+ Options file and command line management.
+
+***************************************************************************/
+
+#ifndef MAME_EMU_EMUOPTS_H
+#define MAME_EMU_EMUOPTS_H
+
+#pragma once
+
+#include "options.h"
+
+#define OPTION_PRIORITY_CMDLINE OPTION_PRIORITY_HIGH + 1
+// core options
+#define OPTION_SYSTEMNAME core_options::unadorned(0)
+#define OPTION_SOFTWARENAME core_options::unadorned(1)
+
+// core configuration options
+#define OPTION_READCONFIG "readconfig"
+#define OPTION_WRITECONFIG "writeconfig"
+
+// core search path options
+#define OPTION_PLUGINDATAPATH "homepath"
+#define OPTION_MEDIAPATH "rompath"
+#define OPTION_HASHPATH "hashpath"
+#define OPTION_SAMPLEPATH "samplepath"
+#define OPTION_ARTPATH "artpath"
+#define OPTION_CTRLRPATH "ctrlrpath"
+#define OPTION_INIPATH "inipath"
+#define OPTION_FONTPATH "fontpath"
+#define OPTION_CHEATPATH "cheatpath"
+#define OPTION_CROSSHAIRPATH "crosshairpath"
+#define OPTION_PLUGINSPATH "pluginspath"
+#define OPTION_LANGUAGEPATH "languagepath"
+#define OPTION_SWPATH "swpath"
+
+// core directory options
+#define OPTION_CFG_DIRECTORY "cfg_directory"
+#define OPTION_NVRAM_DIRECTORY "nvram_directory"
+#define OPTION_INPUT_DIRECTORY "input_directory"
+#define OPTION_STATE_DIRECTORY "state_directory"
+#define OPTION_SNAPSHOT_DIRECTORY "snapshot_directory"
+#define OPTION_DIFF_DIRECTORY "diff_directory"
+#define OPTION_COMMENT_DIRECTORY "comment_directory"
+#define OPTION_SHARE_DIRECTORY "share_directory"
+
+// core state/playback options
+#define OPTION_STATE "state"
+#define OPTION_AUTOSAVE "autosave"
+#define OPTION_REWIND "rewind"
+#define OPTION_REWIND_CAPACITY "rewind_capacity"
+#define OPTION_PLAYBACK "playback"
+#define OPTION_RECORD "record"
+#define OPTION_RECORD_TIMECODE "record_timecode"
+#define OPTION_EXIT_AFTER_PLAYBACK "exit_after_playback"
+#define OPTION_MNGWRITE "mngwrite"
+#define OPTION_AVIWRITE "aviwrite"
+#define OPTION_WAVWRITE "wavwrite"
+#define OPTION_SNAPNAME "snapname"
+#define OPTION_SNAPSIZE "snapsize"
+#define OPTION_SNAPVIEW "snapview"
+#define OPTION_SNAPBILINEAR "snapbilinear"
+#define OPTION_STATENAME "statename"
+#define OPTION_BURNIN "burnin"
+
+// core performance options
+#define OPTION_AUTOFRAMESKIP "autoframeskip"
+#define OPTION_FRAMESKIP "frameskip"
+#define OPTION_SECONDS_TO_RUN "seconds_to_run"
+#define OPTION_THROTTLE "throttle"
+#define OPTION_SLEEP "sleep"
+#define OPTION_SPEED "speed"
+#define OPTION_REFRESHSPEED "refreshspeed"
+#define OPTION_LOWLATENCY "lowlatency"
+
+// core render options
+#define OPTION_KEEPASPECT "keepaspect"
+#define OPTION_UNEVENSTRETCH "unevenstretch"
+#define OPTION_UNEVENSTRETCHX "unevenstretchx"
+#define OPTION_UNEVENSTRETCHY "unevenstretchy"
+#define OPTION_AUTOSTRETCHXY "autostretchxy"
+#define OPTION_INTOVERSCAN "intoverscan"
+#define OPTION_INTSCALEX "intscalex"
+#define OPTION_INTSCALEY "intscaley"
+
+// core rotation options
+#define OPTION_ROTATE "rotate"
+#define OPTION_ROR "ror"
+#define OPTION_ROL "rol"
+#define OPTION_AUTOROR "autoror"
+#define OPTION_AUTOROL "autorol"
+#define OPTION_FLIPX "flipx"
+#define OPTION_FLIPY "flipy"
+
+// core artwork options
+#define OPTION_ARTWORK_CROP "artwork_crop"
+#define OPTION_FALLBACK_ARTWORK "fallback_artwork"
+#define OPTION_OVERRIDE_ARTWORK "override_artwork"
+
+// core screen options
+#define OPTION_BRIGHTNESS "brightness"
+#define OPTION_CONTRAST "contrast"
+#define OPTION_GAMMA "gamma"
+#define OPTION_PAUSE_BRIGHTNESS "pause_brightness"
+#define OPTION_EFFECT "effect"
+
+// core vector options
+#define OPTION_BEAM_WIDTH_MIN "beam_width_min"
+#define OPTION_BEAM_WIDTH_MAX "beam_width_max"
+#define OPTION_BEAM_DOT_SIZE "beam_dot_size"
+#define OPTION_BEAM_INTENSITY_WEIGHT "beam_intensity_weight"
+#define OPTION_FLICKER "flicker"
+
+// core sound options
+#define OPTION_SAMPLERATE "samplerate"
+#define OPTION_SAMPLES "samples"
+#define OPTION_VOLUME "volume"
+#define OPTION_COMPRESSOR "compressor"
+#define OPTION_SPEAKER_REPORT "speaker_report"
+
+// core input options
+#define OPTION_COIN_LOCKOUT "coin_lockout"
+#define OPTION_CTRLR "ctrlr"
+#define OPTION_MOUSE "mouse"
+#define OPTION_JOYSTICK "joystick"
+#define OPTION_LIGHTGUN "lightgun"
+#define OPTION_MULTIKEYBOARD "multikeyboard"
+#define OPTION_MULTIMOUSE "multimouse"
+#define OPTION_STEADYKEY "steadykey"
+#define OPTION_UI_ACTIVE "ui_active"
+#define OPTION_OFFSCREEN_RELOAD "offscreen_reload"
+#define OPTION_JOYSTICK_MAP "joystick_map"
+#define OPTION_JOYSTICK_DEADZONE "joystick_deadzone"
+#define OPTION_JOYSTICK_SATURATION "joystick_saturation"
+#define OPTION_NATURAL_KEYBOARD "natural"
+#define OPTION_JOYSTICK_CONTRADICTORY "joystick_contradictory"
+#define OPTION_COIN_IMPULSE "coin_impulse"
+
+// input autoenable options
+#define OPTION_PADDLE_DEVICE "paddle_device"
+#define OPTION_ADSTICK_DEVICE "adstick_device"
+#define OPTION_PEDAL_DEVICE "pedal_device"
+#define OPTION_DIAL_DEVICE "dial_device"
+#define OPTION_TRACKBALL_DEVICE "trackball_device"
+#define OPTION_LIGHTGUN_DEVICE "lightgun_device"
+#define OPTION_POSITIONAL_DEVICE "positional_device"
+#define OPTION_MOUSE_DEVICE "mouse_device"
+
+// core debugging options
+#define OPTION_LOG "log"
+#define OPTION_DEBUG "debug"
+#define OPTION_VERBOSE "verbose"
+#define OPTION_OSLOG "oslog"
+#define OPTION_UPDATEINPAUSE "update_in_pause"
+#define OPTION_DEBUGSCRIPT "debugscript"
+#define OPTION_DEBUGLOG "debuglog"
+
+// core misc options
+#define OPTION_DRC "drc"
+#define OPTION_DRC_USE_C "drc_use_c"
+#define OPTION_DRC_LOG_UML "drc_log_uml"
+#define OPTION_DRC_LOG_NATIVE "drc_log_native"
+#define OPTION_BIOS "bios"
+#define OPTION_CHEAT "cheat"
+#define OPTION_SKIP_GAMEINFO "skip_gameinfo"
+#define OPTION_UI_FONT "uifont"
+#define OPTION_UI "ui"
+#define OPTION_RAMSIZE "ramsize"
+#define OPTION_NVRAM_SAVE "nvram_save"
+
+// core comm options
+#define OPTION_COMM_LOCAL_HOST "comm_localhost"
+#define OPTION_COMM_LOCAL_PORT "comm_localport"
+#define OPTION_COMM_REMOTE_HOST "comm_remotehost"
+#define OPTION_COMM_REMOTE_PORT "comm_remoteport"
+#define OPTION_COMM_FRAME_SYNC "comm_framesync"
+
+#define OPTION_CONFIRM_QUIT "confirm_quit"
+#define OPTION_UI_MOUSE "ui_mouse"
+
+#define OPTION_AUTOBOOT_COMMAND "autoboot_command"
+#define OPTION_AUTOBOOT_DELAY "autoboot_delay"
+#define OPTION_AUTOBOOT_SCRIPT "autoboot_script"
+
+#define OPTION_CONSOLE "console"
+#define OPTION_PLUGINS "plugins"
+#define OPTION_PLUGIN "plugin"
+#define OPTION_NO_PLUGIN "noplugin"
+
+#define OPTION_LANGUAGE "language"
+
+#define OPTION_HTTP "http"
+#define OPTION_HTTP_PORT "http_port"
+#define OPTION_HTTP_ROOT "http_root"
+
+//**************************************************************************
+// TYPE DEFINITIONS
+//**************************************************************************
+
+class game_driver;
+class device_slot_interface;
+class emu_options;
+
+class slot_option
+{
+public:
+ slot_option(emu_options &host, const char *default_value);
+ slot_option(const slot_option &that) = delete;
+ slot_option(slot_option &&that) = default;
+
+ // accessors
+ const std::string &value() const;
+ std::string specified_value() const;
+ const std::string &bios() const { return m_specified_bios; }
+ const std::string &default_card_software() const { return m_default_card_software; }
+ bool specified() const { return m_specified; }
+ core_options::entry::shared_ptr option_entry() const { return m_entry.lock(); }
+
+ // seters
+ void specify(std::string_view text, bool peg_priority = true);
+ void specify(std::string &&text, bool peg_priority = true);
+ void specify(const char *text, bool peg_priority = true) { specify(std::string_view(text), peg_priority); }
+ void set_bios(std::string &&text);
+ void set_default_card_software(std::string &&s);
+
+ // instantiates an option entry (don't call outside of emuopts.cpp)
+ core_options::entry::shared_ptr setup_option_entry(const char *name);
+
+private:
+ void possibly_changed(const std::string &old_value);
+
+ emu_options & m_host;
+ bool m_specified;
+ std::string m_specified_value;
+ std::string m_specified_bios;
+ std::string m_default_card_software;
+ std::string m_default_value;
+ core_options::entry::weak_ptr m_entry;
+};
+
+
+class image_option
+{
+public:
+ image_option(emu_options &host, const std::string &canonical_instance_name);
+ image_option(const image_option &that) = delete;
+ image_option(image_option &&that) = default;
+
+ // accessors
+ const std::string &canonical_instance_name() const { return m_canonical_instance_name; }
+ const std::string &value() const { return m_value; }
+ core_options::entry::shared_ptr option_entry() const { return m_entry.lock(); }
+
+ // mutators
+ void specify(std::string_view value, bool peg_priority = true);
+ void specify(std::string &&value, bool peg_priority = true);
+ void specify(const char *value, bool peg_priority = true) { specify(std::string_view(value), peg_priority); }
+
+ // instantiates an option entry (don't call outside of emuopts.cpp)
+ core_options::entry::shared_ptr setup_option_entry(std::vector<std::string> &&names);
+
+private:
+ emu_options & m_host;
+ std::string m_canonical_instance_name;
+ std::string m_value;
+ core_options::entry::weak_ptr m_entry;
+};
+
+
+class emu_options : public core_options
+{
+ friend class slot_option;
+ friend class image_option;
+public:
+ enum ui_option
+ {
+ UI_CABINET,
+ UI_SIMPLE
+ };
+
+ enum class option_support
+ {
+ FULL, // full option support
+ GENERAL_AND_SYSTEM, // support for general options and system (no softlist)
+ GENERAL_ONLY // only support for general options
+ };
+
+ // construction/destruction
+ emu_options(option_support support = option_support::FULL);
+ ~emu_options();
+
+ // mutation
+ void set_system_name(const char *new_system_name) { set_system_name(std::string(new_system_name)); }
+ void set_system_name(std::string_view new_system_name) { set_system_name(std::string(new_system_name)); }
+ void set_system_name(std::string &&new_system_name);
+ void set_software(std::string &&new_software);
+
+ // core options
+ const game_driver *system() const { return m_system; }
+ const char *system_name() const;
+ const std::string &attempted_system_name() const { return m_attempted_system_name; }
+ const std::string &software_name() const { return m_software_name; }
+
+ // core configuration options
+ bool read_config() const { return bool_value(OPTION_READCONFIG); }
+ bool write_config() const { return bool_value(OPTION_WRITECONFIG); }
+
+ // core search path options
+ const char *plugin_data_path() const { return value(OPTION_PLUGINDATAPATH); }
+ const char *media_path() const { return value(OPTION_MEDIAPATH); }
+ const char *hash_path() const { return value(OPTION_HASHPATH); }
+ const char *sample_path() const { return value(OPTION_SAMPLEPATH); }
+ const char *art_path() const { return value(OPTION_ARTPATH); }
+ const char *ctrlr_path() const { return value(OPTION_CTRLRPATH); }
+ const char *ini_path() const { return value(OPTION_INIPATH); }
+ const char *font_path() const { return value(OPTION_FONTPATH); }
+ const char *cheat_path() const { return value(OPTION_CHEATPATH); }
+ const char *crosshair_path() const { return value(OPTION_CROSSHAIRPATH); }
+ const char *plugins_path() const { return value(OPTION_PLUGINSPATH); }
+ const char *language_path() const { return value(OPTION_LANGUAGEPATH); }
+ const char *sw_path() const { return value(OPTION_SWPATH); }
+
+ // core directory options
+ const char *cfg_directory() const { return value(OPTION_CFG_DIRECTORY); }
+ const char *nvram_directory() const { return value(OPTION_NVRAM_DIRECTORY); }
+ const char *input_directory() const { return value(OPTION_INPUT_DIRECTORY); }
+ const char *state_directory() const { return value(OPTION_STATE_DIRECTORY); }
+ const char *snapshot_directory() const { return value(OPTION_SNAPSHOT_DIRECTORY); }
+ const char *diff_directory() const { return value(OPTION_DIFF_DIRECTORY); }
+ const char *comment_directory() const { return value(OPTION_COMMENT_DIRECTORY); }
+ const char *share_directory() const { return value(OPTION_SHARE_DIRECTORY); }
+
+ // core state/playback options
+ const char *state() const { return value(OPTION_STATE); }
+ bool autosave() const { return bool_value(OPTION_AUTOSAVE); }
+ int rewind() const { return bool_value(OPTION_REWIND); }
+ int rewind_capacity() const { return int_value(OPTION_REWIND_CAPACITY); }
+ const char *playback() const { return value(OPTION_PLAYBACK); }
+ const char *record() const { return value(OPTION_RECORD); }
+ bool record_timecode() const { return bool_value(OPTION_RECORD_TIMECODE); }
+ bool exit_after_playback() const { return bool_value(OPTION_EXIT_AFTER_PLAYBACK); }
+ const char *mng_write() const { return value(OPTION_MNGWRITE); }
+ const char *avi_write() const { return value(OPTION_AVIWRITE); }
+ const char *wav_write() const { return value(OPTION_WAVWRITE); }
+ const char *snap_name() const { return value(OPTION_SNAPNAME); }
+ const char *snap_size() const { return value(OPTION_SNAPSIZE); }
+ const char *snap_view() const { return value(OPTION_SNAPVIEW); }
+ bool snap_bilinear() const { return bool_value(OPTION_SNAPBILINEAR); }
+ const char *state_name() const { return value(OPTION_STATENAME); }
+ bool burnin() const { return bool_value(OPTION_BURNIN); }
+
+ // core performance options
+ bool auto_frameskip() const { return bool_value(OPTION_AUTOFRAMESKIP); }
+ int frameskip() const { return int_value(OPTION_FRAMESKIP); }
+ int seconds_to_run() const { return int_value(OPTION_SECONDS_TO_RUN); }
+ bool throttle() const { return bool_value(OPTION_THROTTLE); }
+ bool sleep() const { return m_sleep; }
+ float speed() const { return float_value(OPTION_SPEED); }
+ bool refresh_speed() const { return m_refresh_speed; }
+ bool low_latency() const { return bool_value(OPTION_LOWLATENCY); }
+
+ // core render options
+ bool keep_aspect() const { return bool_value(OPTION_KEEPASPECT); }
+ bool uneven_stretch() const { return bool_value(OPTION_UNEVENSTRETCH); }
+ bool uneven_stretch_x() const { return bool_value(OPTION_UNEVENSTRETCHX); }
+ bool uneven_stretch_y() const { return bool_value(OPTION_UNEVENSTRETCHY); }
+ bool auto_stretch_xy() const { return bool_value(OPTION_AUTOSTRETCHXY); }
+ bool int_overscan() const { return bool_value(OPTION_INTOVERSCAN); }
+ int int_scale_x() const { return int_value(OPTION_INTSCALEX); }
+ int int_scale_y() const { return int_value(OPTION_INTSCALEY); }
+
+ // core rotation options
+ bool rotate() const { return bool_value(OPTION_ROTATE); }
+ bool ror() const { return bool_value(OPTION_ROR); }
+ bool rol() const { return bool_value(OPTION_ROL); }
+ bool auto_ror() const { return bool_value(OPTION_AUTOROR); }
+ bool auto_rol() const { return bool_value(OPTION_AUTOROL); }
+ bool flipx() const { return bool_value(OPTION_FLIPX); }
+ bool flipy() const { return bool_value(OPTION_FLIPY); }
+
+ // core artwork options
+ bool artwork_crop() const { return bool_value(OPTION_ARTWORK_CROP); }
+ const char *fallback_artwork() const { return value(OPTION_FALLBACK_ARTWORK); }
+ const char *override_artwork() const { return value(OPTION_OVERRIDE_ARTWORK); }
+
+ // core screen options
+ float brightness() const { return float_value(OPTION_BRIGHTNESS); }
+ float contrast() const { return float_value(OPTION_CONTRAST); }
+ float gamma() const { return float_value(OPTION_GAMMA); }
+ float pause_brightness() const { return float_value(OPTION_PAUSE_BRIGHTNESS); }
+ const char *effect() const { return value(OPTION_EFFECT); }
+
+ // core vector options
+ float beam_width_min() const { return float_value(OPTION_BEAM_WIDTH_MIN); }
+ float beam_width_max() const { return float_value(OPTION_BEAM_WIDTH_MAX); }
+ float beam_dot_size() const { return float_value(OPTION_BEAM_DOT_SIZE); }
+ float beam_intensity_weight() const { return float_value(OPTION_BEAM_INTENSITY_WEIGHT); }
+ float flicker() const { return float_value(OPTION_FLICKER); }
+
+ // core sound options
+ int sample_rate() const { return int_value(OPTION_SAMPLERATE); }
+ bool samples() const { return bool_value(OPTION_SAMPLES); }
+ int volume() const { return int_value(OPTION_VOLUME); }
+ bool compressor() const { return bool_value(OPTION_COMPRESSOR); }
+ int speaker_report() const { return int_value(OPTION_SPEAKER_REPORT); }
+
+ // core input options
+ bool coin_lockout() const { return bool_value(OPTION_COIN_LOCKOUT); }
+ const char *ctrlr() const { return value(OPTION_CTRLR); }
+ bool mouse() const { return bool_value(OPTION_MOUSE); }
+ bool joystick() const { return bool_value(OPTION_JOYSTICK); }
+ bool lightgun() const { return bool_value(OPTION_LIGHTGUN); }
+ bool multi_keyboard() const { return bool_value(OPTION_MULTIKEYBOARD); }
+ bool multi_mouse() const { return bool_value(OPTION_MULTIMOUSE); }
+ const char *paddle_device() const { return value(OPTION_PADDLE_DEVICE); }
+ const char *adstick_device() const { return value(OPTION_ADSTICK_DEVICE); }
+ const char *pedal_device() const { return value(OPTION_PEDAL_DEVICE); }
+ const char *dial_device() const { return value(OPTION_DIAL_DEVICE); }
+ const char *trackball_device() const { return value(OPTION_TRACKBALL_DEVICE); }
+ const char *lightgun_device() const { return value(OPTION_LIGHTGUN_DEVICE); }
+ const char *positional_device() const { return value(OPTION_POSITIONAL_DEVICE); }
+ const char *mouse_device() const { return value(OPTION_MOUSE_DEVICE); }
+ const char *joystick_map() const { return value(OPTION_JOYSTICK_MAP); }
+ float joystick_deadzone() const { return float_value(OPTION_JOYSTICK_DEADZONE); }
+ float joystick_saturation() const { return float_value(OPTION_JOYSTICK_SATURATION); }
+ bool steadykey() const { return bool_value(OPTION_STEADYKEY); }
+ bool ui_active() const { return bool_value(OPTION_UI_ACTIVE); }
+ bool offscreen_reload() const { return bool_value(OPTION_OFFSCREEN_RELOAD); }
+ bool natural_keyboard() const { return bool_value(OPTION_NATURAL_KEYBOARD); }
+ bool joystick_contradictory() const { return m_joystick_contradictory; }
+ int coin_impulse() const { return m_coin_impulse; }
+
+ // core debugging options
+ bool log() const { return bool_value(OPTION_LOG); }
+ bool debug() const { return bool_value(OPTION_DEBUG); }
+ bool verbose() const { return bool_value(OPTION_VERBOSE); }
+ bool oslog() const { return bool_value(OPTION_OSLOG); }
+ const char *debug_script() const { return value(OPTION_DEBUGSCRIPT); }
+ bool update_in_pause() const { return bool_value(OPTION_UPDATEINPAUSE); }
+ bool debuglog() const { return bool_value(OPTION_DEBUGLOG); }
+
+ // core misc options
+ bool drc() const { return bool_value(OPTION_DRC); }
+ bool drc_use_c() const { return bool_value(OPTION_DRC_USE_C); }
+ bool drc_log_uml() const { return bool_value(OPTION_DRC_LOG_UML); }
+ bool drc_log_native() const { return bool_value(OPTION_DRC_LOG_NATIVE); }
+ const char *bios() const { return value(OPTION_BIOS); }
+ bool cheat() const { return bool_value(OPTION_CHEAT); }
+ bool skip_gameinfo() const { return bool_value(OPTION_SKIP_GAMEINFO); }
+ const char *ui_font() const { return value(OPTION_UI_FONT); }
+ ui_option ui() const { return m_ui; }
+ const char *ram_size() const { return value(OPTION_RAMSIZE); }
+ bool nvram_save() const { return bool_value(OPTION_NVRAM_SAVE); }
+
+ // core comm options
+ const char *comm_localhost() const { return value(OPTION_COMM_LOCAL_HOST); }
+ const char *comm_localport() const { return value(OPTION_COMM_LOCAL_PORT); }
+ const char *comm_remotehost() const { return value(OPTION_COMM_REMOTE_HOST); }
+ const char *comm_remoteport() const { return value(OPTION_COMM_REMOTE_PORT); }
+ bool comm_framesync() const { return bool_value(OPTION_COMM_FRAME_SYNC); }
+
+
+ bool confirm_quit() const { return bool_value(OPTION_CONFIRM_QUIT); }
+ bool ui_mouse() const { return bool_value(OPTION_UI_MOUSE); }
+
+ const char *autoboot_command() const { return value(OPTION_AUTOBOOT_COMMAND); }
+ int autoboot_delay() const { return int_value(OPTION_AUTOBOOT_DELAY); }
+ const char *autoboot_script() const { return value(OPTION_AUTOBOOT_SCRIPT); }
+
+ bool console() const { return bool_value(OPTION_CONSOLE); }
+
+ bool plugins() const { return bool_value(OPTION_PLUGINS); }
+
+ const char *plugin() const { return value(OPTION_PLUGIN); }
+ const char *no_plugin() const { return value(OPTION_NO_PLUGIN); }
+
+ const char *language() const { return value(OPTION_LANGUAGE); }
+
+ // Web server specific options
+ bool http() const { return bool_value(OPTION_HTTP); }
+ short http_port() const { return int_value(OPTION_HTTP_PORT); }
+ const char *http_root() const { return value(OPTION_HTTP_ROOT); }
+
+ // slots and devices - the values for these are stored outside of the core_options
+ // structure
+ const ::slot_option &slot_option(const std::string &device_name) const;
+ ::slot_option &slot_option(const std::string &device_name);
+ const ::slot_option *find_slot_option(const std::string &device_name) const;
+ ::slot_option *find_slot_option(const std::string &device_name);
+ bool has_slot_option(const std::string &device_name) const { return find_slot_option(device_name) ? true : false; }
+ const ::image_option &image_option(const std::string &device_name) const;
+ ::image_option &image_option(const std::string &device_name);
+ bool has_image_option(const std::string &device_name) const { return m_image_options.find(device_name) != m_image_options.end(); }
+
+protected:
+ virtual void command_argument_processed() override;
+
+private:
+ struct software_options
+ {
+ std::unordered_map<std::string, std::string> slot;
+ std::unordered_map<std::string, std::string> image;
+ };
+
+ // slot/image/softlist calculus
+ software_options evaluate_initial_softlist_options(const std::string &software_identifier);
+ void update_slot_and_image_options();
+ bool add_and_remove_slot_options();
+ bool add_and_remove_image_options();
+ void reevaluate_default_card_software();
+ std::string get_default_card_software(device_slot_interface &slot);
+
+ // static list of options entries
+ static const options_entry s_option_entries[];
+
+ // the basics
+ option_support m_support;
+ const game_driver * m_system;
+
+ // slots and devices
+ std::unordered_map<std::string, ::slot_option> m_slot_options;
+ std::unordered_map<std::string, ::image_option> m_image_options_canonical;
+ std::unordered_map<std::string, ::image_option *> m_image_options;
+
+ // cached options, for scenarios where parsing core_options is too slow
+ int m_coin_impulse;
+ bool m_joystick_contradictory;
+ bool m_sleep;
+ bool m_refresh_speed;
+ ui_option m_ui;
+
+ // special option; the system name we tried to specify
+ std::string m_attempted_system_name;
+
+ // special option; the software set name that we did specify
+ std::string m_software_name;
+};
+
+// takes an existing emu_options and adds system specific options
+void osd_setup_osd_specific_emu_options(emu_options &opts);
+
+#endif // MAME_EMU_EMUOPTS_H
diff --git a/docs/release/src/emu/romload.cpp b/docs/release/src/emu/romload.cpp
new file mode 100644
index 00000000000..97fa85b4487
--- /dev/null
+++ b/docs/release/src/emu/romload.cpp
@@ -0,0 +1,1503 @@
+// license:BSD-3-Clause
+// copyright-holders:Nicola Salmoria,Paul Priest,Aaron Giles,Vas Crabb
+/*********************************************************************
+
+ romload.cpp
+
+ ROM loading functions.
+
+*********************************************************************/
+
+#include "emu.h"
+#include "romload.h"
+
+#include "corestr.h"
+#include "emuopts.h"
+#include "drivenum.h"
+#include "softlist_dev.h"
+#include "ui/uimain.h"
+
+#include <algorithm>
+#include <set>
+
+
+#define LOG_LOAD 0
+#define LOG(...) do { if (LOG_LOAD) debugload(__VA_ARGS__); } while(0)
+
+
+/***************************************************************************
+ CONSTANTS
+***************************************************************************/
+
+#define TEMPBUFFER_MAX_SIZE (1024 * 1024 * 1024)
+
+/***************************************************************************
+ HELPERS
+****************************************************************************/
+
+namespace {
+
+auto next_parent_system(game_driver const &system)
+{
+ return
+ [sys = &system, roms = std::vector<rom_entry>()] () mutable -> rom_entry const *
+ {
+ if (!sys)
+ return nullptr;
+ int const parent(driver_list::find(sys->parent));
+ if (0 > parent)
+ {
+ sys = nullptr;
+ return nullptr;
+ }
+ else
+ {
+ sys = &driver_list::driver(parent);
+ roms = rom_build_entries(sys->rom);
+ return &roms[0];
+ }
+ };
+}
+
+
+auto next_parent_software(std::vector<software_info const *> const &parents)
+{
+ auto part(parents.front()->parts().end());
+ return
+ [&parents, current = parents.cbegin(), part, end = part] () mutable -> const rom_entry *
+ {
+ if (part == end)
+ {
+ if (parents.end() == current)
+ return nullptr;
+ part = (*current)->parts().cbegin();
+ end = (*current)->parts().cend();
+ do { ++current; } while ((parents.cend() != current) && (*current)->parts().empty());
+ }
+ return &(*part++).romdata()[0];
+ };
+}
+
+
+std::vector<std::string> make_software_searchpath(software_list_device &swlist, software_info const &swinfo, std::vector<software_info const *> &parents)
+{
+ std::vector<std::string> result;
+
+ // search <rompath>/<list>/<software> following parents
+ for (software_info const *i = &swinfo; i; )
+ {
+ if (std::find(parents.begin(), parents.end(), i) != parents.end())
+ break;
+ parents.emplace_back(i);
+ result.emplace_back(util::string_format("%s" PATH_SEPARATOR "%s", swlist.list_name(), i->shortname()));
+ i = i->parentname().empty() ? nullptr : swlist.find(i->parentname());
+ }
+
+ // search <rompath>/<software> following parents
+ for (software_info const *i : parents)
+ result.emplace_back(i->shortname());
+
+ return result;
+}
+
+
+std::error_condition do_open_disk(const emu_options &options, std::initializer_list<std::reference_wrapper<const std::vector<std::string> > > searchpath, const rom_entry *romp, chd_file &chd, std::function<const rom_entry * ()> next_parent)
+{
+ // hashes are fixed, but we might need to try multiple filenames
+ std::set<std::string> tried;
+ const util::hash_collection hashes(romp->hashdata());
+ std::string filename, fullpath;
+ const rom_entry *parent(nullptr);
+ std::error_condition result(std::errc::no_such_file_or_directory);
+ while (romp && result)
+ {
+ filename = romp->name() + ".chd";
+ if (tried.insert(filename).second)
+ {
+ // piggyback on emu_file to find the disk image file
+ std::unique_ptr<emu_file> imgfile;
+ for (const std::vector<std::string> &paths : searchpath)
+ {
+ imgfile.reset(new emu_file(options.media_path(), paths, OPEN_FLAG_READ));
+ imgfile->set_restrict_to_mediapath(1);
+ const std::error_condition filerr(imgfile->open(filename, OPEN_FLAG_READ));
+ if (!filerr)
+ break;
+ else
+ imgfile.reset();
+ }
+
+ // if we couldn't open a candidate file, report an error; otherwise reopen it as a CHD
+ if (imgfile)
+ {
+ fullpath = imgfile->fullpath();
+ imgfile.reset();
+ result = chd.open(fullpath);
+ }
+ }
+
+ // walk the parents looking for a CHD with the same hashes but a different name
+ if (result)
+ {
+ while (romp)
+ {
+ // find a file in a disk region
+ if (parent)
+ romp = rom_next_file(romp);
+ while (!parent || !romp)
+ {
+ if (!parent)
+ {
+ parent = next_parent();
+ if (!parent)
+ {
+ romp = nullptr;
+ break;
+ }
+ while (ROMENTRY_ISPARAMETER(parent) || ROMENTRY_ISSYSTEM_BIOS(parent) || ROMENTRY_ISDEFAULT_BIOS(parent))
+ ++parent;
+ if (ROMENTRY_ISEND(parent))
+ parent = nullptr;
+ }
+ else
+ {
+ parent = rom_next_region(parent);
+ }
+ while (parent && !ROMREGION_ISDISKDATA(parent))
+ parent = rom_next_region(parent);
+ if (parent)
+ romp = rom_first_file(parent);
+ }
+
+ // try it if it matches the hashes
+ if (romp && (util::hash_collection(romp->hashdata()) == hashes))
+ break;
+ }
+ }
+ }
+ return result;
+}
+
+} // anonymous namespace
+
+
+/***************************************************************************
+ ROM LOADING
+***************************************************************************/
+
+/*-------------------------------------------------
+ rom_first_region - return pointer to first ROM
+ region
+-------------------------------------------------*/
+
+const rom_entry *rom_first_region(const device_t &device)
+{
+ return rom_first_region(&device.rom_region_vector().front());
+}
+
+const rom_entry *rom_first_region(const rom_entry *romp)
+{
+ while (ROMENTRY_ISPARAMETER(romp) || ROMENTRY_ISSYSTEM_BIOS(romp) || ROMENTRY_ISDEFAULT_BIOS(romp))
+ romp++;
+ return !ROMENTRY_ISEND(romp) ? romp : nullptr;
+}
+
+
+/*-------------------------------------------------
+ rom_next_region - return pointer to next ROM
+ region
+-------------------------------------------------*/
+
+const rom_entry *rom_next_region(const rom_entry *romp)
+{
+ romp++;
+ while (!ROMENTRY_ISREGIONEND(romp))
+ romp++;
+ while (ROMENTRY_ISPARAMETER(romp))
+ romp++;
+ return ROMENTRY_ISEND(romp) ? nullptr : romp;
+}
+
+
+/*-------------------------------------------------
+ rom_first_file - return pointer to first ROM
+ file
+-------------------------------------------------*/
+
+const rom_entry *rom_first_file(const rom_entry *romp)
+{
+ romp++;
+ while (!ROMENTRY_ISFILE(romp) && !ROMENTRY_ISREGIONEND(romp))
+ romp++;
+ return ROMENTRY_ISREGIONEND(romp) ? nullptr : romp;
+}
+
+
+/*-------------------------------------------------
+ rom_next_file - return pointer to next ROM
+ file
+-------------------------------------------------*/
+
+const rom_entry *rom_next_file(const rom_entry *romp)
+{
+ romp++;
+ while (!ROMENTRY_ISFILE(romp) && !ROMENTRY_ISREGIONEND(romp))
+ romp++;
+ return ROMENTRY_ISREGIONEND(romp) ? nullptr : romp;
+}
+
+
+/*-------------------------------------------------
+ rom_first_parameter - return pointer to the first
+ per-game parameter
+-------------------------------------------------*/
+
+const rom_entry *rom_first_parameter(const device_t &device)
+{
+ const rom_entry *romp = &device.rom_region_vector().front();
+ while (romp && !ROMENTRY_ISEND(romp) && !ROMENTRY_ISPARAMETER(romp))
+ romp++;
+ return (romp != nullptr && !ROMENTRY_ISEND(romp)) ? romp : nullptr;
+}
+
+
+/*-------------------------------------------------
+ rom_next_parameter - return pointer to the next
+ per-game parameter
+-------------------------------------------------*/
+
+const rom_entry *rom_next_parameter(const rom_entry *romp)
+{
+ romp++;
+ while (!ROMENTRY_ISREGIONEND(romp) && !ROMENTRY_ISPARAMETER(romp))
+ romp++;
+ return ROMENTRY_ISEND(romp) ? nullptr : romp;
+}
+
+
+/*-------------------------------------------------
+ rom_file_size - return the expected size of a
+ file given the ROM description
+-------------------------------------------------*/
+
+u32 rom_file_size(const rom_entry *romp)
+{
+ u32 maxlength = 0;
+
+ /* loop until we run out of reloads */
+ do
+ {
+ /* loop until we run out of continues/ignores */
+ u32 curlength = ROM_GETLENGTH(romp++);
+ while (ROMENTRY_ISCONTINUE(romp) || ROMENTRY_ISIGNORE(romp))
+ curlength += ROM_GETLENGTH(romp++);
+
+ /* track the maximum length */
+ maxlength = std::max(maxlength, curlength);
+ }
+ while (ROMENTRY_ISRELOAD(romp));
+
+ return maxlength;
+}
+
+
+/*-------------------------------------------------
+ debugload - log data to a file
+-------------------------------------------------*/
+
+static void CLIB_DECL ATTR_PRINTF(1,2) debugload(const char *string, ...)
+{
+ static int opened;
+ va_list arg;
+ FILE *f;
+
+ f = fopen("romload.log", opened++ ? "a" : "w");
+ if (f)
+ {
+ va_start(arg, string);
+ vfprintf(f, string, arg);
+ va_end(arg);
+ fclose(f);
+ }
+}
+
+
+/***************************************************************************
+ HARD DISK HANDLING
+***************************************************************************/
+
+/*-------------------------------------------------
+ get_disk_handle - return a pointer to the
+ CHD file associated with the given region
+-------------------------------------------------*/
+
+chd_file *rom_load_manager::get_disk_handle(std::string_view region)
+{
+ for (auto &curdisk : m_chd_list)
+ if (curdisk->region() == region)
+ return &curdisk->chd();
+ return nullptr;
+}
+
+
+/*-------------------------------------------------
+ set_disk_handle - set a pointer to the CHD
+ file associated with the given region
+-------------------------------------------------*/
+
+std::error_condition rom_load_manager::set_disk_handle(std::string_view region, const char *fullpath)
+{
+ auto chd = std::make_unique<open_chd>(region);
+ auto err = chd->orig_chd().open(fullpath);
+ if (!err)
+ m_chd_list.push_back(std::move(chd));
+ return err;
+}
+
+/*-------------------------------------------------
+ determine_bios_rom - determine system_bios
+ from SystemBios structure and OPTION_BIOS
+-------------------------------------------------*/
+
+void rom_load_manager::determine_bios_rom(device_t &device, const char *specbios)
+{
+ // default is applied by the device at config complete time
+ if (specbios && *specbios && core_stricmp(specbios, "default"))
+ {
+ bool found(false);
+ for (const rom_entry &rom : device.rom_region_vector())
+ {
+ if (ROMENTRY_ISSYSTEM_BIOS(&rom))
+ {
+ char const *const biosname = ROM_GETNAME(&rom);
+ int const bios_flags = ROM_GETBIOSFLAGS(&rom);
+ char bios_number[20];
+
+ // Allow '-bios n' to still be used
+ sprintf(bios_number, "%d", bios_flags - 1);
+ if (!core_stricmp(bios_number, specbios) || !core_stricmp(biosname, specbios))
+ {
+ found = true;
+ device.set_system_bios(bios_flags);
+ break;
+ }
+ }
+ }
+
+ // if we got neither an empty string nor 'default' then warn the user
+ if (!found)
+ {
+ m_errorstring.append(util::string_format("%s: invalid BIOS \"%s\", reverting to default\n", device.tag(), specbios));
+ m_warnings++;
+ }
+ }
+
+ // log final result
+ LOG("For \"%s\" using System BIOS: %d\n", device.tag(), device.system_bios());
+}
+
+
+/*-------------------------------------------------
+ count_roms - counts the total number of ROMs
+ that will need to be loaded
+-------------------------------------------------*/
+
+void rom_load_manager::count_roms()
+{
+ const rom_entry *region, *rom;
+
+ /* start with 0 */
+ m_romstotal = 0;
+ m_romstotalsize = 0;
+
+ /* loop over regions, then over files */
+ for (device_t &device : device_enumerator(machine().config().root_device()))
+ for (region = rom_first_region(device); region != nullptr; region = rom_next_region(region))
+ for (rom = rom_first_file(region); rom != nullptr; rom = rom_next_file(rom))
+ if (ROM_GETBIOSFLAGS(rom) == 0 || ROM_GETBIOSFLAGS(rom) == device.system_bios())
+ {
+ m_romstotal++;
+ m_romstotalsize += rom_file_size(rom);
+ }
+}
+
+
+/*-------------------------------------------------
+ fill_random - fills an area of memory with
+ random data
+-------------------------------------------------*/
+
+void rom_load_manager::fill_random(u8 *base, u32 length)
+{
+ while (length--)
+ *base++ = machine().rand();
+}
+
+
+/*-------------------------------------------------
+ handle_missing_file - handles error generation
+ for missing files
+-------------------------------------------------*/
+
+void rom_load_manager::handle_missing_file(const rom_entry *romp, const std::vector<std::string> &tried_file_names, std::error_condition chderr)
+{
+ std::string tried;
+ if (!tried_file_names.empty())
+ {
+ tried = " (tried in";
+ for (const std::string &path : tried_file_names)
+ {
+ tried += ' ';
+ tried += path;
+ }
+ tried += ')';
+ }
+
+ const bool is_chd(chderr);
+ const std::string name(is_chd ? romp->name() + ".chd" : romp->name());
+
+ const bool is_chd_error(is_chd && chderr != std::errc::no_such_file_or_directory);
+ if (is_chd_error)
+ m_errorstring.append(string_format("%s CHD ERROR: %s\n", name, chderr.message()));
+
+ if (ROM_ISOPTIONAL(romp))
+ {
+ // optional files are okay
+ if (!is_chd_error)
+ m_errorstring.append(string_format("OPTIONAL %s NOT FOUND%s\n", name, tried));
+ m_warnings++;
+ }
+ else if (util::hash_collection(romp->hashdata()).flag(util::hash_collection::FLAG_NO_DUMP))
+ {
+ // no good dumps are okay
+ if (!is_chd_error)
+ m_errorstring.append(string_format("%s NOT FOUND (NO GOOD DUMP KNOWN)%s\n", name, tried));
+ m_knownbad++;
+ }
+ else
+ {
+ // anything else is bad
+ if (!is_chd_error)
+ m_errorstring.append(string_format("%s NOT FOUND%s\n", name, tried));
+ m_errors++;
+ }
+}
+
+
+/*-------------------------------------------------
+ dump_wrong_and_correct_checksums - dump an
+ error message containing the wrong and the
+ correct checksums for a given ROM
+-------------------------------------------------*/
+
+void rom_load_manager::dump_wrong_and_correct_checksums(const util::hash_collection &hashes, const util::hash_collection &acthashes)
+{
+ m_errorstring.append(string_format(" EXPECTED: %s\n", hashes.macro_string()));
+ m_errorstring.append(string_format(" FOUND: %s\n", acthashes.macro_string()));
+}
+
+
+/*-------------------------------------------------
+ verify_length_and_hash - verify the length
+ and hash signatures of a file
+-------------------------------------------------*/
+
+void rom_load_manager::verify_length_and_hash(emu_file *file, std::string_view name, u32 explength, const util::hash_collection &hashes)
+{
+ // we've already complained if there is no file
+ if (!file)
+ return;
+
+ // verify length
+ u64 const actlength = file->size();
+ if (explength != actlength)
+ {
+ m_errorstring.append(string_format("%s WRONG LENGTH (expected: %08x found: %08x)\n", name, explength, actlength));
+ m_warnings++;
+ }
+
+ if (hashes.flag(util::hash_collection::FLAG_NO_DUMP))
+ {
+ // If there is no good dump known, write it
+ m_errorstring.append(string_format("%s NO GOOD DUMP KNOWN\n", name));
+ m_knownbad++;
+ }
+ else
+ {
+ // verify checksums
+ util::hash_collection const &acthashes = file->hashes(hashes.hash_types());
+ if (hashes != acthashes)
+ {
+ // otherwise, it's just bad
+ util::hash_collection const &all_acthashes = (acthashes.hash_types() == util::hash_collection::HASH_TYPES_ALL)
+ ? acthashes
+ : file->hashes(util::hash_collection::HASH_TYPES_ALL);
+ m_errorstring.append(string_format("%s WRONG CHECKSUMS:\n", name));
+ dump_wrong_and_correct_checksums(hashes, all_acthashes);
+ m_warnings++;
+ }
+ else if (hashes.flag(util::hash_collection::FLAG_BAD_DUMP))
+ {
+ // If it matches, but it is actually a bad dump, write it
+ m_errorstring.append(string_format("%s ROM NEEDS REDUMP\n", name));
+ m_knownbad++;
+ }
+ }
+}
+
+
+/*-------------------------------------------------
+ display_loading_rom_message - display
+ messages about ROM loading to the user
+-------------------------------------------------*/
+
+void rom_load_manager::display_loading_rom_message(const char *name, bool from_list)
+{
+ std::string buffer;
+ if (name)
+ buffer = util::string_format("%s (%d%%)", from_list ? "Loading Software" : "Loading Machine", u32(100 * m_romsloadedsize / m_romstotalsize));
+ else
+ buffer = "Loading Complete";
+
+ if (!machine().ui().is_menu_active())
+ machine().ui().set_startup_text(buffer.c_str(), false);
+}
+
+
+/*-------------------------------------------------
+ display_rom_load_results - display the final
+ results of ROM loading
+-------------------------------------------------*/
+
+void rom_load_manager::display_rom_load_results(bool from_list)
+{
+ /* final status display */
+ display_loading_rom_message(nullptr, from_list);
+
+ /* if we had errors, they are fatal */
+ if (m_errors != 0)
+ {
+ /* create the error message and exit fatally */
+ osd_printf_error("%s", m_errorstring);
+ throw emu_fatalerror(EMU_ERR_MISSING_FILES, "Required files are missing, the machine cannot be run.");
+ }
+
+ /* if we had warnings, output them, but continue */
+ if ((m_warnings) || (m_knownbad))
+ {
+ m_errorstring.append("WARNING: the machine might not run correctly.");
+ osd_printf_warning("%s\n", m_errorstring);
+ }
+}
+
+
+/*-------------------------------------------------
+ region_post_process - post-process a region,
+ byte swapping and inverting data as necessary
+-------------------------------------------------*/
+
+void rom_load_manager::region_post_process(memory_region *region, bool invert)
+{
+ // do nothing if no region
+ if (region == nullptr)
+ return;
+
+ LOG("+ datawidth=%dbit endian=%s\n", region->bitwidth(),
+ region->endianness() == ENDIANNESS_LITTLE ? "little" : "big");
+
+ /* if the region is inverted, do that now */
+ if (invert)
+ {
+ LOG("+ Inverting region\n");
+ u8 *base = region->base();
+ for (int i = 0; i < region->bytes(); i++)
+ *base++ ^= 0xff;
+ }
+
+ /* swap the endianness if we need to */
+ if (region->bytewidth() > 1 && region->endianness() != ENDIANNESS_NATIVE)
+ {
+ LOG("+ Byte swapping region\n");
+ int datawidth = region->bytewidth();
+ u8 *base = region->base();
+ for (int i = 0; i < region->bytes(); i += datawidth)
+ {
+ u8 temp[8];
+ memcpy(temp, base, datawidth);
+ for (int j = datawidth - 1; j >= 0; j--)
+ *base++ = temp[j];
+ }
+ }
+}
+
+
+/*-------------------------------------------------
+ open_rom_file - open a ROM file, searching
+ up the parent and loading by checksum
+-------------------------------------------------*/
+
+std::unique_ptr<emu_file> rom_load_manager::open_rom_file(std::initializer_list<std::reference_wrapper<const std::vector<std::string> > > searchpath, const rom_entry *romp, std::vector<std::string> &tried_file_names, bool from_list)
+{
+ std::error_condition filerr = std::errc::no_such_file_or_directory;
+ u32 const romsize = rom_file_size(romp);
+ tried_file_names.clear();
+
+ // update status display
+ display_loading_rom_message(ROM_GETNAME(romp), from_list);
+
+ // extract CRC to use for searching
+ u32 crc = 0;
+ bool const has_crc = util::hash_collection(romp->hashdata()).crc(crc);
+
+ // attempt reading up the chain through the parents
+ // it also automatically attempts any kind of load by checksum supported by the archives.
+ std::unique_ptr<emu_file> result;
+ for (const std::vector<std::string> &paths : searchpath)
+ {
+ result = open_rom_file(paths, tried_file_names, has_crc, crc, ROM_GETNAME(romp), filerr);
+ if (result)
+ break;
+ }
+
+ // update counters
+ m_romsloaded++;
+ m_romsloadedsize += romsize;
+
+ // return the result
+ if (filerr)
+ return nullptr;
+ else
+ return result;
+}
+
+
+std::unique_ptr<emu_file> rom_load_manager::open_rom_file(const std::vector<std::string> &paths, std::vector<std::string> &tried, bool has_crc, u32 crc, std::string_view name, std::error_condition &filerr)
+{
+ // record the set names we search
+ tried.insert(tried.end(), paths.begin(), paths.end());
+
+ // attempt to open the file
+ std::unique_ptr<emu_file> result(new emu_file(machine().options().media_path(), paths, OPEN_FLAG_READ));
+ result->set_restrict_to_mediapath(1);
+ if (has_crc)
+ filerr = result->open(name, crc);
+ else
+ filerr = result->open(name);
+
+ // don't return anything if unsuccessful
+ if (filerr)
+ return nullptr;
+ else
+ return result;
+}
+
+
+/*-------------------------------------------------
+ rom_fread - cheesy fread that fills with
+ random data for a nullptr file
+-------------------------------------------------*/
+
+int rom_load_manager::rom_fread(emu_file *file, u8 *buffer, int length, const rom_entry *parent_region)
+{
+ if (file) // files just pass through
+ return file->read(buffer, length);
+
+ if (!ROMREGION_ISERASE(parent_region)) // otherwise, fill with randomness unless it was already specifically erased
+ fill_random(buffer, length);
+
+ return length;
+}
+
+
+/*-------------------------------------------------
+ read_rom_data - read ROM data for a single
+ entry
+-------------------------------------------------*/
+
+int rom_load_manager::read_rom_data(emu_file *file, const rom_entry *parent_region, const rom_entry *romp)
+{
+ int datashift = ROM_GETBITSHIFT(romp);
+ int datamask = ((1 << ROM_GETBITWIDTH(romp)) - 1) << datashift;
+ int numbytes = ROM_GETLENGTH(romp);
+ int groupsize = ROM_GETGROUPSIZE(romp);
+ int skip = ROM_GETSKIPCOUNT(romp);
+ int reversed = ROM_ISREVERSED(romp);
+ int numgroups = (numbytes + groupsize - 1) / groupsize;
+ u8 *base = m_region->base() + ROM_GETOFFSET(romp);
+ u32 tempbufsize;
+ int i;
+
+ LOG("Loading ROM data: offs=%X len=%X mask=%02X group=%d skip=%d reverse=%d\n", ROM_GETOFFSET(romp), numbytes, datamask, groupsize, skip, reversed);
+
+ /* make sure the length was an even multiple of the group size */
+// if (numbytes % groupsize != 0)
+// osd_printf_warning("Warning in RomModule definition: %s length not an even multiple of group size\n", romp->name()); // HBMAME
+
+ /* make sure we only fill within the region space */
+ if (ROM_GETOFFSET(romp) + numgroups * groupsize + (numgroups - 1) * skip > m_region->bytes())
+ throw emu_fatalerror("Error in RomModule definition: %s out of memory region space\n", romp->name());
+
+ /* make sure the length was valid */
+ if (numbytes == 0)
+ throw emu_fatalerror("Error in RomModule definition: %s has an invalid length\n", romp->name());
+
+ /* special case for simple loads */
+ if (datamask == 0xff && (groupsize == 1 || !reversed) && skip == 0)
+ return rom_fread(file, base, numbytes, parent_region);
+
+ /* use a temporary buffer for complex loads */
+ tempbufsize = std::min(TEMPBUFFER_MAX_SIZE, numbytes);
+ std::vector<u8> tempbuf(tempbufsize);
+
+ /* chunky reads for complex loads */
+ skip += groupsize;
+ while (numbytes > 0)
+ {
+ int evengroupcount = (tempbufsize / groupsize) * groupsize;
+ int bytesleft = (numbytes > evengroupcount) ? evengroupcount : numbytes;
+ u8 *bufptr = &tempbuf[0];
+
+ /* read as much as we can */
+ LOG(" Reading %X bytes into buffer\n", bytesleft);
+ if (rom_fread(file, bufptr, bytesleft, parent_region) != bytesleft)
+ return 0;
+ numbytes -= bytesleft;
+
+ LOG(" Copying to %p\n", base);
+
+ /* unmasked cases */
+ if (datamask == 0xff)
+ {
+ /* non-grouped data */
+ if (groupsize == 1)
+ for (i = 0; i < bytesleft; i++, base += skip)
+ *base = *bufptr++;
+
+ /* grouped data -- non-reversed case */
+ else if (!reversed)
+ while (bytesleft)
+ {
+ for (i = 0; i < groupsize && bytesleft; i++, bytesleft--)
+ base[i] = *bufptr++;
+ base += skip;
+ }
+
+ /* grouped data -- reversed case */
+ else
+ while (bytesleft)
+ {
+ for (i = groupsize - 1; i >= 0 && bytesleft; i--, bytesleft--)
+ base[i] = *bufptr++;
+ base += skip;
+ }
+ }
+
+ /* masked cases */
+ else
+ {
+ /* non-grouped data */
+ if (groupsize == 1)
+ for (i = 0; i < bytesleft; i++, base += skip)
+ *base = (*base & ~datamask) | ((*bufptr++ << datashift) & datamask);
+
+ /* grouped data -- non-reversed case */
+ else if (!reversed)
+ while (bytesleft)
+ {
+ for (i = 0; i < groupsize && bytesleft; i++, bytesleft--)
+ base[i] = (base[i] & ~datamask) | ((*bufptr++ << datashift) & datamask);
+ base += skip;
+ }
+
+ /* grouped data -- reversed case */
+ else
+ while (bytesleft)
+ {
+ for (i = groupsize - 1; i >= 0 && bytesleft; i--, bytesleft--)
+ base[i] = (base[i] & ~datamask) | ((*bufptr++ << datashift) & datamask);
+ base += skip;
+ }
+ }
+ }
+
+ LOG(" All done\n");
+ return ROM_GETLENGTH(romp);
+}
+
+
+/*-------------------------------------------------
+ fill_rom_data - fill a region of ROM space
+-------------------------------------------------*/
+
+void rom_load_manager::fill_rom_data(const rom_entry *romp)
+{
+ u32 numbytes = ROM_GETLENGTH(romp);
+ int skip = ROM_GETSKIPCOUNT(romp);
+ u8 *base = m_region->base() + ROM_GETOFFSET(romp);
+
+ // make sure we fill within the region space
+ if (ROM_GETOFFSET(romp) + numbytes > m_region->bytes())
+ throw emu_fatalerror("Error in RomModule definition: FILL out of memory region space\n");
+
+ // make sure the length was valid
+ if (numbytes == 0)
+ throw emu_fatalerror("Error in RomModule definition: FILL has an invalid length\n");
+
+ // for fill bytes, the byte that gets filled is the first byte of the hashdata string
+ u8 fill_byte = u8(strtol(romp->hashdata().c_str(), nullptr, 0));
+
+ // fill the data (filling value is stored in place of the hashdata)
+ if(skip != 0)
+ {
+ for (int i = 0; i < numbytes; i+= skip + 1)
+ base[i] = fill_byte;
+ }
+ else
+ memset(base, fill_byte, numbytes);
+}
+
+
+/*-------------------------------------------------
+ copy_rom_data - copy a region of ROM space
+-------------------------------------------------*/
+
+void rom_load_manager::copy_rom_data(const rom_entry *romp)
+{
+ u8 *base = m_region->base() + ROM_GETOFFSET(romp);
+ const std::string &srcrgntag = romp->name();
+ u32 numbytes = ROM_GETLENGTH(romp);
+ u32 srcoffs = u32(strtol(romp->hashdata().c_str(), nullptr, 0)); /* srcoffset in place of hashdata */
+
+ /* make sure we copy within the region space */
+ if (ROM_GETOFFSET(romp) + numbytes > m_region->bytes())
+ throw emu_fatalerror("Error in RomModule definition: COPY out of target memory region space\n");
+
+ /* make sure the length was valid */
+ if (numbytes == 0)
+ throw emu_fatalerror("Error in RomModule definition: COPY has an invalid length\n");
+
+ /* make sure the source was valid */
+ memory_region *region = machine().root_device().memregion(srcrgntag);
+ if (region == nullptr)
+ throw emu_fatalerror("Error in RomModule definition: COPY from an invalid region\n");
+
+ /* make sure we find within the region space */
+ if (srcoffs + numbytes > region->bytes())
+ throw emu_fatalerror("Error in RomModule definition: COPY out of source memory region space\n");
+
+ /* fill the data */
+ memcpy(base, region->base() + srcoffs, numbytes);
+}
+
+
+/*-------------------------------------------------
+ process_rom_entries - process all ROM entries
+ for a region
+-------------------------------------------------*/
+
+void rom_load_manager::process_rom_entries(std::initializer_list<std::reference_wrapper<const std::vector<std::string> > > searchpath, u8 bios, const rom_entry *parent_region, const rom_entry *romp, bool from_list)
+{
+ u32 lastflags = 0;
+ std::vector<std::string> tried_file_names;
+
+ // loop until we hit the end of this region
+ while (!ROMENTRY_ISREGIONEND(romp))
+ {
+ tried_file_names.clear();
+
+ if (ROMENTRY_ISCONTINUE(romp))
+ throw emu_fatalerror("Error in RomModule definition: ROM_CONTINUE not preceded by ROM_LOAD\n");
+
+ if (ROMENTRY_ISIGNORE(romp))
+ throw emu_fatalerror("Error in RomModule definition: ROM_IGNORE not preceded by ROM_LOAD\n");
+
+ if (ROMENTRY_ISRELOAD(romp))
+ throw emu_fatalerror("Error in RomModule definition: ROM_RELOAD not preceded by ROM_LOAD\n");
+
+ if (ROMENTRY_ISFILL(romp))
+ {
+ if (!ROM_GETBIOSFLAGS(romp) || (ROM_GETBIOSFLAGS(romp) == bios))
+ fill_rom_data(romp);
+
+ romp++;
+ }
+ else if (ROMENTRY_ISCOPY(romp))
+ {
+ copy_rom_data(romp++);
+ }
+ else if (ROMENTRY_ISFILE(romp))
+ {
+ // handle files
+ bool const irrelevantbios = (ROM_GETBIOSFLAGS(romp) != 0) && (ROM_GETBIOSFLAGS(romp) != bios);
+ rom_entry const *baserom = romp;
+ int explength = 0;
+
+ // open the file if it is a non-BIOS or matches the current BIOS
+ LOG("Opening ROM file: %s\n", ROM_GETNAME(romp));
+ std::unique_ptr<emu_file> file;
+ if (!irrelevantbios)
+ {
+ file = open_rom_file(searchpath, romp, tried_file_names, from_list);
+ if (!file)
+ handle_missing_file(romp, tried_file_names, std::error_condition());
+ }
+
+ // loop until we run out of reloads
+ do
+ {
+ // loop until we run out of continues/ignores
+ do
+ {
+ rom_entry modified_romp = *romp++;
+ //int readresult;
+
+ // handle flag inheritance
+ if (!ROM_INHERITSFLAGS(&modified_romp))
+ lastflags = modified_romp.get_flags();
+ else
+ modified_romp.set_flags((modified_romp.get_flags() & ~ROM_INHERITEDFLAGS) | lastflags);
+
+ explength += ROM_GETLENGTH(&modified_romp);
+
+ // attempt to read using the modified entry
+ if (!ROMENTRY_ISIGNORE(&modified_romp) && !irrelevantbios)
+ /*readresult = */read_rom_data(file.get(), parent_region, &modified_romp);
+ }
+ while (ROMENTRY_ISCONTINUE(romp) || ROMENTRY_ISIGNORE(romp));
+
+ // if this was the first use of this file, verify the length and CRC
+ if (baserom)
+ {
+ LOG("Verifying length (%X) and checksums\n", explength);
+ verify_length_and_hash(file.get(), baserom->name(), explength, util::hash_collection(baserom->hashdata()));
+ LOG("Verify finished\n");
+ }
+
+ // re-seek to the start and clear the baserom so we don't reverify
+ if (file)
+ file->seek(0, SEEK_SET);
+ baserom = nullptr;
+ explength = 0;
+ }
+ while (ROMENTRY_ISRELOAD(romp));
+
+ // close the file
+ if (file)
+ {
+ LOG("Closing ROM file\n");
+ file.reset();
+ }
+ }
+ else
+ {
+ romp++; // something else - skip
+ }
+ }
+}
+
+
+/*-------------------------------------------------
+ open_disk_diff - open a DISK diff file
+-------------------------------------------------*/
+
+std::error_condition rom_load_manager::open_disk_diff(emu_options &options, const rom_entry *romp, chd_file &source, chd_file &diff_chd)
+{
+ // TODO: use system name and/or software list name in the path - the current setup doesn't scale
+ std::string fname = romp->name() + ".dif";
+
+ // try to open the diff
+ LOG("Opening differencing image file: %s\n", fname.c_str());
+ emu_file diff_file(options.diff_directory(), OPEN_FLAG_READ | OPEN_FLAG_WRITE);
+ std::error_condition filerr = diff_file.open(fname);
+ if (!filerr)
+ {
+ std::string fullpath(diff_file.fullpath());
+ diff_file.close();
+
+ LOG("Opening differencing image file: %s\n", fullpath.c_str());
+ return diff_chd.open(fullpath, true, &source);
+ }
+
+ // didn't work; try creating it instead
+ LOG("Creating differencing image: %s\n", fname.c_str());
+ diff_file.set_openflags(OPEN_FLAG_READ | OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS);
+ filerr = diff_file.open(fname);
+ if (!filerr)
+ {
+ std::string fullpath(diff_file.fullpath());
+ diff_file.close();
+
+ // create the CHD
+ LOG("Creating differencing image file: %s\n", fullpath.c_str());
+ chd_codec_type compression[4] = { CHD_CODEC_NONE };
+ std::error_condition err = diff_chd.create(fullpath, source.logical_bytes(), source.hunk_bytes(), compression, source);
+ if (err)
+ return err;
+
+ return diff_chd.clone_all_metadata(source);
+ }
+
+ return std::errc::no_such_file_or_directory;
+}
+
+
+/*-------------------------------------------------
+ process_disk_entries - process all disk entries
+ for a region
+-------------------------------------------------*/
+
+void rom_load_manager::process_disk_entries(std::initializer_list<std::reference_wrapper<const std::vector<std::string> > > searchpath, std::string_view regiontag, const rom_entry *romp, std::function<const rom_entry * ()> next_parent)
+{
+ /* remove existing disk entries for this region */
+ m_chd_list.erase(std::remove_if(m_chd_list.begin(), m_chd_list.end(),
+ [regiontag] (std::unique_ptr<open_chd> &chd) { return chd->region() == regiontag; }), m_chd_list.end());
+
+ /* loop until we hit the end of this region */
+ for ( ; !ROMENTRY_ISREGIONEND(romp); romp++)
+ {
+ /* handle files */
+ if (ROMENTRY_ISFILE(romp))
+ {
+ auto chd = std::make_unique<open_chd>(regiontag);
+ std::error_condition err;
+
+ /* make the filename of the source */
+ const std::string filename = romp->name() + ".chd";
+
+ /* first open the source drive */
+ // FIXME: we've lost the ability to search parents here
+ LOG("Opening disk image: %s\n", filename.c_str());
+ err = do_open_disk(machine().options(), searchpath, romp, chd->orig_chd(), next_parent);
+ if (err)
+ {
+ handle_missing_file(romp, std::vector<std::string>(), err);
+ chd = nullptr;
+ continue;
+ }
+
+ /* get the header and extract the SHA1 */
+ util::hash_collection acthashes;
+ acthashes.add_sha1(chd->orig_chd().sha1());
+
+ /* verify the hash */
+ const util::hash_collection hashes(romp->hashdata());
+ if (hashes != acthashes)
+ {
+ m_errorstring.append(string_format("%s WRONG CHECKSUMS:\n", filename));
+ dump_wrong_and_correct_checksums(hashes, acthashes);
+ m_warnings++;
+ }
+ else if (hashes.flag(util::hash_collection::FLAG_BAD_DUMP))
+ {
+ m_errorstring.append(string_format("%s CHD NEEDS REDUMP\n", filename));
+ m_knownbad++;
+ }
+
+ /* if not read-only, make the diff file */
+ if (!DISK_ISREADONLY(romp))
+ {
+ /* try to open or create the diff */
+ err = open_disk_diff(machine().options(), romp, chd->orig_chd(), chd->diff_chd());
+ if (err)
+ {
+ m_errorstring.append(string_format("%s DIFF CHD ERROR: %s\n", filename, err.message()));
+ m_errors++;
+ chd = nullptr;
+ continue;
+ }
+ }
+
+ /* we're okay, add to the list of disks */
+ LOG("Assigning to handle %d\n", DISK_GETINDEX(romp));
+ m_chd_list.push_back(std::move(chd));
+ }
+ }
+}
+
+
+/*-------------------------------------------------
+ get_software_searchpath - get search path
+ for a software list item
+-------------------------------------------------*/
+
+std::vector<std::string> rom_load_manager::get_software_searchpath(software_list_device &swlist, const software_info &swinfo)
+{
+ std::vector<software_info const *> parents;
+ return make_software_searchpath(swlist, swinfo, parents);
+}
+
+
+/*-------------------------------------------------
+ open_disk_image - open a disk image for a
+ device
+-------------------------------------------------*/
+
+std::error_condition rom_load_manager::open_disk_image(const emu_options &options, const device_t &device, const rom_entry *romp, chd_file &image_chd)
+{
+ const std::vector<std::string> searchpath(device.searchpath());
+
+ driver_device const *const driver(dynamic_cast<driver_device const *>(&device));
+ std::function<const rom_entry * ()> next_parent;
+ if (driver)
+ next_parent = next_parent_system(driver->system());
+ else
+ next_parent = [] () { return nullptr; };
+ return do_open_disk(options, { searchpath }, romp, image_chd, std::move(next_parent));
+}
+
+
+/*-------------------------------------------------
+ open_disk_image - open a disk image for a
+ software item
+-------------------------------------------------*/
+
+std::error_condition rom_load_manager::open_disk_image(const emu_options &options, software_list_device &swlist, const software_info &swinfo, const rom_entry *romp, chd_file &image_chd)
+{
+ std::vector<software_info const *> parents;
+ std::vector<std::string> searchpath = make_software_searchpath(swlist, swinfo, parents);
+ searchpath.emplace_back(swlist.list_name()); // look for loose disk images in software list directory
+ return do_open_disk(options, { searchpath }, romp, image_chd, next_parent_software(parents));
+}
+
+
+/*-------------------------------------------------
+ normalize_flags_for_device - modify the region
+ flags for the given device
+-------------------------------------------------*/
+
+void rom_load_manager::normalize_flags_for_device(std::string_view rgntag, u8 &width, endianness_t &endian)
+{
+ device_t *device = machine().root_device().subdevice(rgntag);
+ device_memory_interface *memory;
+ if (device != nullptr && device->interface(memory))
+ {
+ const address_space_config *spaceconfig = memory->space_config();
+ if (spaceconfig != nullptr)
+ {
+ int buswidth;
+
+ /* set the endianness */
+ if (spaceconfig->endianness() == ENDIANNESS_LITTLE)
+ endian = ENDIANNESS_LITTLE;
+ else
+ endian = ENDIANNESS_BIG;
+
+ /* set the width */
+ buswidth = spaceconfig->data_width();
+ if (buswidth <= 8)
+ width = 1;
+ else if (buswidth <= 16)
+ width = 2;
+ else if (buswidth <= 32)
+ width = 4;
+ else
+ width = 8;
+ }
+ }
+}
+
+
+/*-------------------------------------------------
+ load_software_part_region - load a software part
+
+ This is used by MESS when loading a piece of
+ software. The code should be merged with
+ process_region_list or updated to use a slight
+ more general process_region_list.
+-------------------------------------------------*/
+
+void rom_load_manager::load_software_part_region(device_t &device, software_list_device &swlist, std::string_view swname, const rom_entry *start_region)
+{
+ m_errorstring.clear();
+ m_softwarningstring.clear();
+
+ m_romstotal = 0;
+ m_romstotalsize = 0;
+ m_romsloadedsize = 0;
+
+ std::vector<const software_info *> parents;
+ std::vector<std::string> swsearch, disksearch, devsearch;
+ const software_info *const swinfo = swlist.find(std::string(swname));
+ if (swinfo)
+ {
+ // dispay a warning for unsupported software
+ // TODO: list supported clones like we do for machines?
+ if (swinfo->supported() == software_support::PARTIALLY_SUPPORTED)
+ {
+ m_errorstring.append(string_format("WARNING: support for software %s (in list %s) is only partial\n", swname, swlist.list_name()));
+ m_softwarningstring.append(string_format("Support for software %s (in list %s) is only partial\n", swname, swlist.list_name()));
+ }
+ else if (swinfo->supported() == software_support::UNSUPPORTED)
+ {
+ m_errorstring.append(string_format("WARNING: support for software %s (in list %s) is only preliminary\n", swname, swlist.list_name()));
+ m_softwarningstring.append(string_format("Support for software %s (in list %s) is only preliminary\n", swname, swlist.list_name()));
+ }
+
+ // walk the chain of parents and add them to the search path
+ swsearch = make_software_searchpath(swlist, *swinfo, parents);
+ }
+ else
+ {
+ swsearch.emplace_back(util::string_format("%s" PATH_SEPARATOR "%s", swlist.list_name(), swname));
+ swsearch.emplace_back(swname);
+ }
+
+ // this is convenient for CD-only lists so you don't need an extra level of directories containing one file each
+ disksearch.emplace_back(swlist.list_name());
+
+ // for historical reasons, add the search path for the software list device's owner
+ const device_t *const listowner = swlist.owner();
+ if (listowner)
+ devsearch = listowner->searchpath();
+
+ // loop until we hit the end
+ std::function<const rom_entry * ()> next_parent;
+ for (const rom_entry *region = start_region; region != nullptr; region = rom_next_region(region))
+ {
+ u32 regionlength = ROMREGION_GETLENGTH(region);
+
+ std::string regiontag = device.subtag(region->name());
+ LOG("Processing region \"%s\" (length=%X)\n", regiontag.c_str(), regionlength);
+
+ // the first entry must be a region
+ assert(ROMENTRY_ISREGION(region));
+
+ // if this is a device region, override with the device width and endianness
+ endianness_t endianness = ROMREGION_ISBIGENDIAN(region) ? ENDIANNESS_BIG : ENDIANNESS_LITTLE;
+ u8 width = ROMREGION_GETWIDTH(region) / 8;
+ memory_region *memregion = machine().root_device().memregion(regiontag);
+ if (memregion != nullptr)
+ {
+ normalize_flags_for_device(regiontag, width, endianness);
+
+ // clear old region (TODO: should be moved to an image unload function)
+ machine().memory().region_free(memregion->name());
+ }
+
+ // remember the base and length
+ m_region = machine().memory().region_alloc(regiontag, regionlength, width, endianness);
+ LOG("Allocated %X bytes @ %p\n", m_region->bytes(), m_region->base());
+
+ if (ROMREGION_ISERASE(region)) // clear the region if it's requested
+ memset(m_region->base(), ROMREGION_GETERASEVAL(region), m_region->bytes());
+ else if (m_region->bytes() <= 0x400000) // or if it's sufficiently small (<= 4MB)
+ memset(m_region->base(), 0, m_region->bytes());
+#ifdef MAME_DEBUG
+ else // if we're debugging, fill region with random data to catch errors
+ fill_random(m_region->base(), m_region->bytes());
+#endif
+
+ // update total number of roms
+ for (const rom_entry *rom = rom_first_file(region); rom != nullptr; rom = rom_next_file(rom))
+ {
+ m_romstotal++;
+ m_romstotalsize += rom_file_size(rom);
+ }
+
+ // now process the entries in the region
+ if (ROMREGION_ISROMDATA(region))
+ {
+ if (devsearch.empty())
+ process_rom_entries({ swsearch }, 0U, region, region + 1, true);
+ else
+ process_rom_entries({ swsearch, devsearch }, 0U, region, region + 1, true);
+ }
+ else if (ROMREGION_ISDISKDATA(region))
+ {
+ if (!next_parent)
+ {
+ if (!parents.empty())
+ next_parent = next_parent_software(parents);
+ else
+ next_parent = [] () { return nullptr; };
+ }
+ if (devsearch.empty())
+ process_disk_entries({ swsearch, disksearch }, regiontag, region + 1, next_parent);
+ else
+ process_disk_entries({ swsearch, disksearch, devsearch }, regiontag, region + 1, next_parent);
+ }
+ }
+
+ // now go back and post-process all the regions
+ for (const rom_entry *region = start_region; region != nullptr; region = rom_next_region(region))
+ region_post_process(device.memregion(region->name()), ROMREGION_ISINVERTED(region));
+
+ // display the results and exit
+ display_rom_load_results(true);
+}
+
+
+/*-------------------------------------------------
+ process_region_list - process a region list
+-------------------------------------------------*/
+
+void rom_load_manager::process_region_list()
+{
+ // loop until we hit the end
+ device_enumerator deviter(machine().root_device());
+ std::vector<std::string> searchpath;
+ for (device_t &device : deviter)
+ {
+ searchpath.clear();
+ std::function<const rom_entry * ()> next_parent;
+ for (const rom_entry *region = rom_first_region(device); region != nullptr; region = rom_next_region(region))
+ {
+ u32 regionlength = ROMREGION_GETLENGTH(region);
+
+ std::string regiontag = device.subtag(region->name());
+ LOG("Processing region \"%s\" (length=%X)\n", regiontag.c_str(), regionlength);
+
+ // the first entry must be a region
+ assert(ROMENTRY_ISREGION(region));
+
+ if (ROMREGION_ISROMDATA(region))
+ {
+ // if this is a device region, override with the device width and endianness
+ u8 width = ROMREGION_GETWIDTH(region) / 8;
+ endianness_t endianness = ROMREGION_ISBIGENDIAN(region) ? ENDIANNESS_BIG : ENDIANNESS_LITTLE;
+ normalize_flags_for_device(regiontag, width, endianness);
+
+ // remember the base and length
+ m_region = machine().memory().region_alloc(regiontag, regionlength, width, endianness);
+ LOG("Allocated %X bytes @ %p\n", m_region->bytes(), m_region->base());
+
+ if (ROMREGION_ISERASE(region)) // clear the region if it's requested
+ memset(m_region->base(), ROMREGION_GETERASEVAL(region), m_region->bytes());
+ else if (m_region->bytes() <= 0x400000) // or if it's sufficiently small (<= 4MB)
+ memset(m_region->base(), 0, m_region->bytes());
+#ifdef MAME_DEBUG
+ else // if we're debugging, fill region with random data to catch errors
+ fill_random(m_region->base(), m_region->bytes());
+#endif
+
+ // now process the entries in the region
+ if (searchpath.empty())
+ searchpath = device.searchpath();
+ assert(!searchpath.empty());
+ process_rom_entries({ searchpath }, device.system_bios(), region, region + 1, false);
+ }
+ else if (ROMREGION_ISDISKDATA(region))
+ {
+ if (searchpath.empty())
+ searchpath = device.searchpath();
+ assert(!searchpath.empty());
+ if (!next_parent)
+ {
+ driver_device const *const driver(dynamic_cast<driver_device const *>(&device));
+ if (driver)
+ next_parent = next_parent_system(driver->system());
+ else
+ next_parent = [] () { return nullptr; };
+ }
+ process_disk_entries({ searchpath }, regiontag, region + 1, next_parent);
+ }
+ }
+ }
+
+ // now go back and post-process all the regions
+ for (device_t &device : deviter)
+ for (const rom_entry *region = rom_first_region(device); region != nullptr; region = rom_next_region(region))
+ region_post_process(device.memregion(region->name()), ROMREGION_ISINVERTED(region));
+
+ // and finally register all per-game parameters
+ for (device_t &device : deviter)
+ {
+ for (const rom_entry *param = rom_first_parameter(device); param != nullptr; param = rom_next_parameter(param))
+ {
+ std::string regiontag = device.subtag(param->name());
+ machine().parameters().add(regiontag, param->hashdata());
+ }
+ }
+}
+
+
+/*-------------------------------------------------
+ rom_init - load the ROMs and open the disk
+ images associated with the given machine
+-------------------------------------------------*/
+
+rom_load_manager::rom_load_manager(running_machine &machine)
+ : m_machine(machine)
+ , m_warnings(0)
+ , m_knownbad(0)
+ , m_errors(0)
+ , m_romsloaded(0)
+ , m_romstotal(0)
+ , m_romsloadedsize(0)
+ , m_romstotalsize(0)
+ , m_chd_list()
+ , m_region(nullptr)
+ , m_errorstring()
+ , m_softwarningstring()
+{
+ // figure out which BIOS we are using
+ std::map<std::string_view, std::string> card_bios;
+ for (device_t &device : device_enumerator(machine.config().root_device()))
+ {
+ device_slot_interface const *const slot(dynamic_cast<device_slot_interface *>(&device));
+ if (slot)
+ {
+ device_t const *const card(slot->get_card_device());
+ slot_option const &slot_opt(machine.options().slot_option(slot->slot_name()));
+ if (card && !slot_opt.bios().empty())
+ card_bios.emplace(card->tag(), slot_opt.bios());
+ }
+
+ if (device.rom_region())
+ {
+ std::string specbios;
+ if (!device.owner())
+ {
+ specbios = machine.options().bios();
+ }
+ else
+ {
+ auto const found(card_bios.find(device.tag()));
+ if (card_bios.end() != found)
+ {
+ specbios = std::move(found->second);
+ card_bios.erase(found);
+ }
+ }
+ determine_bios_rom(device, specbios.c_str());
+ }
+ }
+
+ // count the total number of ROMs
+ count_roms();
+
+ // reset the disk list
+ m_chd_list.clear();
+
+ // process the ROM entries we were passed
+ process_region_list();
+
+ // display the results and exit
+ display_rom_load_results(false);
+}
+
+
+// -------------------------------------------------
+// rom_build_entries - builds a rom_entry vector
+// from a tiny_rom_entry array
+// -------------------------------------------------
+
+std::vector<rom_entry> rom_build_entries(const tiny_rom_entry *tinyentries)
+{
+ std::vector<rom_entry> result;
+ if (tinyentries)
+ {
+ int i = 0;
+ do
+ {
+ result.emplace_back(tinyentries[i]);
+ }
+ while (!ROMENTRY_ISEND(tinyentries[i++]));
+ }
+ else
+ {
+ tiny_rom_entry const end_entry = { nullptr, nullptr, 0, 0, ROMENTRYTYPE_END };
+ result.emplace_back(end_entry);
+ }
+ return result;
+}
diff --git a/docs/release/src/emu/validity.cpp b/docs/release/src/emu/validity.cpp
new file mode 100644
index 00000000000..590f4faccac
--- /dev/null
+++ b/docs/release/src/emu/validity.cpp
@@ -0,0 +1,2848 @@
+// license:BSD-3-Clause
+// copyright-holders:Aaron Giles, Paul Priest
+/***************************************************************************
+
+ validity.cpp
+
+ Validity checks on internal data structures.
+
+***************************************************************************/
+
+#include "emu.h"
+#include "validity.h"
+
+#include "corestr.h"
+#include "emuopts.h"
+#include "romload.h"
+#include "video/rgbutil.h"
+#include "unicode.h"
+
+#include <cctype>
+#include <type_traits>
+#include <typeinfo>
+
+
+namespace {
+
+//-------------------------------------------------
+// diamond_inheritance - forward declaration of a
+// class to force MSVC to use unknown inheritance
+// form of pointers to member functions
+//-------------------------------------------------
+
+class diamond_inheritance;
+
+
+//-------------------------------------------------
+// test_delegate - a delegate that can return a
+// result in a register
+//-------------------------------------------------
+
+using test_delegate = delegate<char (void const *&)>;
+
+
+//-------------------------------------------------
+// make_diamond_class_delegate - make a delegate
+// bound to an instance of an incomplete class
+// type
+//-------------------------------------------------
+
+#if !defined(_LIBCPP_VERSION) || (_LIBCPP_VERSION >= 7000)
+test_delegate make_diamond_class_delegate(char (diamond_inheritance::*func)(void const *&), diamond_inheritance *obj)
+{
+ return test_delegate(func, obj);
+}
+#endif // !defined(_LIBCPP_VERSION) || (_LIBCPP_VERSION >= 7000)
+
+
+//-------------------------------------------------
+// virtual_base - simple class that will be used
+// as the top vertex of the diamond
+//-------------------------------------------------
+
+struct virtual_base
+{
+ char get_base(void const *&p) { p = this; return 'x'; }
+ int x;
+};
+
+
+//-------------------------------------------------
+// virtual_derived_a - first class derived from
+// virtual base
+//-------------------------------------------------
+
+struct virtual_derived_a : virtual virtual_base
+{
+ char get_derived_a(void const *&p) { p = this; return 'a'; }
+ int a;
+};
+
+
+//-------------------------------------------------
+// virtual_derived_b - second class derived from
+// virtual base
+//-------------------------------------------------
+
+struct virtual_derived_b : virtual virtual_base
+{
+ char get_derived_b(void const *&p) { p = this; return 'b'; }
+ int b;
+};
+
+
+//-------------------------------------------------
+// diamond_inheritance - actual definition of
+// class with diamond inheritance
+//-------------------------------------------------
+
+class diamond_inheritance : public virtual_derived_a, public virtual_derived_b
+{
+};
+
+
+//-------------------------------------------------
+// pure_virtual_base - abstract class with a
+// vtable
+//-------------------------------------------------
+
+struct pure_virtual_base
+{
+ virtual ~pure_virtual_base() = default;
+ virtual char operator()(void const *&p) const = 0;
+};
+
+
+//-------------------------------------------------
+// ioport_string_from_index - return an indexed
+// string from the I/O port system
+//-------------------------------------------------
+
+inline char const *ioport_string_from_index(u32 index)
+{
+ return ioport_configurer::string_from_token(reinterpret_cast<char const *>(uintptr_t(index)));
+}
+
+
+//-------------------------------------------------
+// random_u64
+// random_s64
+// random_u32
+// random_s32
+//-------------------------------------------------
+#undef rand
+inline u32 random_u32() { return rand() ^ (rand() << 15); }
+inline s32 random_i32() { return s32(random_u32()); }
+inline u64 random_u64() { return u64(random_u32()) ^ (u64(random_u32()) << 30); }
+inline s64 random_i64() { return s64(random_u64()); }
+
+
+//-------------------------------------------------
+// validate_integer_semantics - validate that
+// integers behave as expected, particularly
+// with regards to overflow and shifting
+//-------------------------------------------------
+
+void validate_integer_semantics()
+{
+ // basic system checks
+ if (~0 != -1) osd_printf_error("Machine must be two's complement\n");
+
+ u8 a = 0xff;
+ u8 b = a + 1;
+ if (b > a) osd_printf_error("u8 must be 8 bits\n");
+
+ // check size of core integer types
+ if (sizeof(s8) != 1) osd_printf_error("s8 must be 8 bits\n");
+ if (sizeof(u8) != 1) osd_printf_error("u8 must be 8 bits\n");
+ if (sizeof(s16) != 2) osd_printf_error("s16 must be 16 bits\n");
+ if (sizeof(u16) != 2) osd_printf_error("u16 must be 16 bits\n");
+ if (sizeof(s32) != 4) osd_printf_error("s32 must be 32 bits\n");
+ if (sizeof(u32) != 4) osd_printf_error("u32 must be 32 bits\n");
+ if (sizeof(s64) != 8) osd_printf_error("s64 must be 64 bits\n");
+ if (sizeof(u64) != 8) osd_printf_error("u64 must be 64 bits\n");
+
+ // check signed right shift
+ s8 a8 = -3;
+ s16 a16 = -3;
+ s32 a32 = -3;
+ s64 a64 = -3;
+ if (a8 >> 1 != -2) osd_printf_error("s8 right shift must be arithmetic\n");
+ if (a16 >> 1 != -2) osd_printf_error("s16 right shift must be arithmetic\n");
+ if (a32 >> 1 != -2) osd_printf_error("s32 right shift must be arithmetic\n");
+ if (a64 >> 1 != -2) osd_printf_error("s64 right shift must be arithmetic\n");
+
+ // check pointer size
+#ifdef PTR64
+ static_assert(sizeof(void *) == 8, "PTR64 flag enabled, but was compiled for 32-bit target\n");
+#else
+ static_assert(sizeof(void *) == 4, "PTR64 flag not enabled, but was compiled for 64-bit target\n");
+#endif
+
+ // TODO: check if this is actually working
+ // check endianness definition
+ u16 lsbtest = 0;
+ *(u8 *)&lsbtest = 0xff;
+#ifdef LSB_FIRST
+ if (lsbtest == 0xff00) osd_printf_error("LSB_FIRST specified, but running on a big-endian machine\n");
+#else
+ if (lsbtest == 0x00ff) osd_printf_error("LSB_FIRST not specified, but running on a little-endian machine\n");
+#endif
+}
+
+
+//-------------------------------------------------
+// validate_inlines - validate inline function
+// behaviors
+//-------------------------------------------------
+
+void validate_inlines()
+{
+ volatile u64 testu64a = random_u64();
+ volatile s64 testi64a = random_i64();
+ volatile u32 testu32a = random_u32();
+ volatile u32 testu32b = random_u32();
+ volatile s32 testi32a = random_i32();
+ volatile s32 testi32b = random_i32();
+ s32 resulti32, expectedi32;
+ u32 resultu32, expectedu32;
+ s64 resulti64, expectedi64;
+ u64 resultu64, expectedu64;
+ s32 remainder, expremainder;
+ u32 uremainder, expuremainder, bigu32 = 0xffffffff;
+
+ // use only non-zero, positive numbers
+ if (testu64a == 0) testu64a++;
+ if (testi64a == 0) testi64a++;
+ else if (testi64a < 0) testi64a = -testi64a;
+ if (testu32a == 0) testu32a++;
+ if (testu32b == 0) testu32b++;
+ if (testi32a == 0) testi32a++;
+ else if (testi32a < 0) testi32a = -testi32a;
+ if (testi32b == 0) testi32b++;
+ else if (testi32b < 0) testi32b = -testi32b;
+
+ resulti64 = mul_32x32(testi32a, testi32b);
+ expectedi64 = s64(testi32a) * s64(testi32b);
+ if (resulti64 != expectedi64)
+ osd_printf_error("Error testing mul_32x32 (%08X x %08X) = %16X (expected %16X)\n", s32(testi32a), s32(testi32b), resulti64, expectedi64);
+
+ resultu64 = mulu_32x32(testu32a, testu32b);
+ expectedu64 = u64(testu32a) * u64(testu32b);
+ if (resultu64 != expectedu64)
+ osd_printf_error("Error testing mulu_32x32 (%08X x %08X) = %16X (expected %16X)\n", u32(testu32a), u32(testu32b), resultu64, expectedu64);
+
+ resulti32 = mul_32x32_hi(testi32a, testi32b);
+ expectedi32 = (s64(testi32a) * s64(testi32b)) >> 32;
+ if (resulti32 != expectedi32)
+ osd_printf_error("Error testing mul_32x32_hi (%08X x %08X) = %08X (expected %08X)\n", s32(testi32a), s32(testi32b), resulti32, expectedi32);
+
+ resultu32 = mulu_32x32_hi(testu32a, testu32b);
+ expectedu32 = (s64(testu32a) * s64(testu32b)) >> 32;
+ if (resultu32 != expectedu32)
+ osd_printf_error("Error testing mulu_32x32_hi (%08X x %08X) = %08X (expected %08X)\n", u32(testu32a), u32(testu32b), resultu32, expectedu32);
+
+ resulti32 = mul_32x32_shift(testi32a, testi32b, 7);
+ expectedi32 = (s64(testi32a) * s64(testi32b)) >> 7;
+ if (resulti32 != expectedi32)
+ osd_printf_error("Error testing mul_32x32_shift (%08X x %08X) >> 7 = %08X (expected %08X)\n", s32(testi32a), s32(testi32b), resulti32, expectedi32);
+
+ resultu32 = mulu_32x32_shift(testu32a, testu32b, 7);
+ expectedu32 = (s64(testu32a) * s64(testu32b)) >> 7;
+ if (resultu32 != expectedu32)
+ osd_printf_error("Error testing mulu_32x32_shift (%08X x %08X) >> 7 = %08X (expected %08X)\n", u32(testu32a), u32(testu32b), resultu32, expectedu32);
+
+ while (s64(testi32a) * s64(0x7fffffff) < testi64a)
+ testi64a /= 2;
+ while (u64(testu32a) * u64(bigu32) < testu64a)
+ testu64a /= 2;
+
+ resulti32 = div_64x32(testi64a, testi32a);
+ expectedi32 = testi64a / s64(testi32a);
+ if (resulti32 != expectedi32)
+ osd_printf_error("Error testing div_64x32 (%16X / %08X) = %08X (expected %08X)\n", s64(testi64a), s32(testi32a), resulti32, expectedi32);
+
+ resultu32 = divu_64x32(testu64a, testu32a);
+ expectedu32 = testu64a / u64(testu32a);
+ if (resultu32 != expectedu32)
+ osd_printf_error("Error testing divu_64x32 (%16X / %08X) = %08X (expected %08X)\n", u64(testu64a), u32(testu32a), resultu32, expectedu32);
+
+ resulti32 = div_64x32_rem(testi64a, testi32a, remainder);
+ expectedi32 = testi64a / s64(testi32a);
+ expremainder = testi64a % s64(testi32a);
+ if (resulti32 != expectedi32 || remainder != expremainder)
+ osd_printf_error("Error testing div_64x32_rem (%16X / %08X) = %08X,%08X (expected %08X,%08X)\n", s64(testi64a), s32(testi32a), resulti32, remainder, expectedi32, expremainder);
+
+ resultu32 = divu_64x32_rem(testu64a, testu32a, uremainder);
+ expectedu32 = testu64a / u64(testu32a);
+ expuremainder = testu64a % u64(testu32a);
+ if (resultu32 != expectedu32 || uremainder != expuremainder)
+ osd_printf_error("Error testing divu_64x32_rem (%16X / %08X) = %08X,%08X (expected %08X,%08X)\n", u64(testu64a), u32(testu32a), resultu32, uremainder, expectedu32, expuremainder);
+
+ resulti32 = mod_64x32(testi64a, testi32a);
+ expectedi32 = testi64a % s64(testi32a);
+ if (resulti32 != expectedi32)
+ osd_printf_error("Error testing mod_64x32 (%16X / %08X) = %08X (expected %08X)\n", s64(testi64a), s32(testi32a), resulti32, expectedi32);
+
+ resultu32 = modu_64x32(testu64a, testu32a);
+ expectedu32 = testu64a % u64(testu32a);
+ if (resultu32 != expectedu32)
+ osd_printf_error("Error testing modu_64x32 (%16X / %08X) = %08X (expected %08X)\n", u64(testu64a), u32(testu32a), resultu32, expectedu32);
+
+ while (s64(testi32a) * s64(0x7fffffff) < (s32(testi64a) << 3))
+ testi64a /= 2;
+ while (u64(testu32a) * u64(0xffffffff) < (u32(testu64a) << 3))
+ testu64a /= 2;
+
+ resulti32 = div_32x32_shift(s32(testi64a), testi32a, 3);
+ expectedi32 = (s64(s32(testi64a)) << 3) / s64(testi32a);
+ if (resulti32 != expectedi32)
+ osd_printf_error("Error testing div_32x32_shift (%08X << 3) / %08X = %08X (expected %08X)\n", s32(testi64a), s32(testi32a), resulti32, expectedi32);
+
+ resultu32 = divu_32x32_shift(u32(testu64a), testu32a, 3);
+ expectedu32 = (u64(u32(testu64a)) << 3) / u64(testu32a);
+ if (resultu32 != expectedu32)
+ osd_printf_error("Error testing divu_32x32_shift (%08X << 3) / %08X = %08X (expected %08X)\n", u32(testu64a), u32(testu32a), resultu32, expectedu32);
+
+ if (fabsf(recip_approx(100.0f) - 0.01f) > 0.0001f)
+ osd_printf_error("Error testing recip_approx\n");
+
+ for (int i = 0; i <= 32; i++)
+ {
+ u32 t = i < 32 ? (1 << (31 - i) | testu32a >> i) : 0;
+ u8 resultu8 = count_leading_zeros_32(t);
+ if (resultu8 != i)
+ osd_printf_error("Error testing count_leading_zeros_32 %08x=%02x (expected %02x)\n", t, resultu8, i);
+
+ t ^= 0xffffffff;
+ resultu8 = count_leading_ones_32(t);
+ if (resultu8 != i)
+ osd_printf_error("Error testing count_leading_ones_32 %08x=%02x (expected %02x)\n", t, resultu8, i);
+ }
+}
+
+
+//-------------------------------------------------
+// validate_rgb - validate optimised RGB utility
+// class
+//-------------------------------------------------
+
+void validate_rgb()
+{
+ /*
+ This performs cursory tests of most of the vector-optimised RGB
+ utilities, concentrating on the low-level maths. It uses random
+ values most of the time for a quick go/no-go indication rather
+ than trying to exercise edge cases. It doesn't matter too much
+ if the compiler optimises out some of the operations since it's
+ really intended to check for logic bugs in the vector code. If
+ the compiler can work out that the code produces the expected
+ result, that's good enough.
+
+ The tests for bitwise logical operations are ordered to minimise
+ the chance of all-zero or all-one patterns producing a
+ misleading good result.
+
+ The following functions are not tested yet:
+ rgbaint_t()
+ clamp_and_clear(const u32)
+ sign_extend(const u32, const u32)
+ min(const s32)
+ max(const s32)
+ blend(const rgbaint_t&, u8)
+ scale_and_clamp(const rgbaint_t&)
+ scale_imm_and_clamp(const s32)
+ scale2_add_and_clamp(const rgbaint_t&, const rgbaint_t&, const rgbaint_t&)
+ scale_add_and_clamp(const rgbaint_t&, const rgbaint_t&);
+ scale_imm_add_and_clamp(const s32, const rgbaint_t&);
+ */
+
+ auto random_i32_nolimit =
+ [] ()
+ {
+ s32 result;
+ do { result = random_i32(); } while ((result == std::numeric_limits<s32>::min()) || (result == std::numeric_limits<s32>::max()));
+ return result;
+ };
+
+ volatile s32 expected_a, expected_r, expected_g, expected_b;
+ volatile s32 actual_a, actual_r, actual_g, actual_b;
+ volatile s32 imm;
+ rgbaint_t rgb, other;
+ rgb_t packed;
+ auto check_expected =
+ [&] (const char *desc)
+ {
+ const volatile s32 a = rgb.get_a32();
+ const volatile s32 r = rgb.get_r32();
+ const volatile s32 g = rgb.get_g32();
+ const volatile s32 b = rgb.get_b32();
+ if (a != expected_a) osd_printf_error("Error testing %s get_a32() = %d (expected %d)\n", desc, s32(a), s32(expected_a));
+ if (r != expected_r) osd_printf_error("Error testing %s get_r32() = %d (expected %d)\n", desc, s32(r), s32(expected_r));
+ if (g != expected_g) osd_printf_error("Error testing %s get_g32() = %d (expected %d)\n", desc, s32(g), s32(expected_g));
+ if (b != expected_b) osd_printf_error("Error testing %s get_b32() = %d (expected %d)\n", desc, s32(b), s32(expected_b));
+ };
+
+ // check set/get
+ expected_a = random_i32();
+ expected_r = random_i32();
+ expected_g = random_i32();
+ expected_b = random_i32();
+ rgb.set(expected_a, expected_r, expected_g, expected_b);
+ check_expected("rgbaint_t::set(a, r, g, b)");
+
+ // check construct/set
+ expected_a = random_i32();
+ expected_r = random_i32();
+ expected_g = random_i32();
+ expected_b = random_i32();
+ rgb.set(rgbaint_t(expected_a, expected_r, expected_g, expected_b));
+ check_expected("rgbaint_t::set(rgbaint_t)");
+
+ packed = random_i32();
+ expected_a = packed.a();
+ expected_r = packed.r();
+ expected_g = packed.g();
+ expected_b = packed.b();
+ rgb.set(packed);
+ check_expected("rgbaint_t::set(const rgb_t& rgb)");
+
+ // check construct/assign
+ expected_a = random_i32();
+ expected_r = random_i32();
+ expected_g = random_i32();
+ expected_b = random_i32();
+ rgb = rgbaint_t(expected_a, expected_r, expected_g, expected_b);
+ check_expected("rgbaint_t assignment");
+
+ // check piecewise set
+ rgb.set_a(expected_a = random_i32());
+ check_expected("rgbaint_t::set_a");
+ rgb.set_r(expected_r = random_i32());
+ check_expected("rgbaint_t::set_r");
+ rgb.set_g(expected_g = random_i32());
+ check_expected("rgbaint_t::set_g");
+ rgb.set_b(expected_b = random_i32());
+ check_expected("rgbaint_t::set_b");
+
+ // test merge_alpha
+ expected_a = rand();
+ rgb.merge_alpha(rgbaint_t(expected_a, rand(), rand(), rand()));
+ check_expected("rgbaint_t::merge_alpha");
+
+ // test RGB addition (method)
+ expected_a += actual_a = random_i32();
+ expected_r += actual_r = random_i32();
+ expected_g += actual_g = random_i32();
+ expected_b += actual_b = random_i32();
+ rgb.add(rgbaint_t(actual_a, actual_r, actual_g, actual_b));
+ check_expected("rgbaint_t::add");
+
+ // test RGB addition (operator)
+ expected_a += actual_a = random_i32();
+ expected_r += actual_r = random_i32();
+ expected_g += actual_g = random_i32();
+ expected_b += actual_b = random_i32();
+ rgb += rgbaint_t(actual_a, actual_r, actual_g, actual_b);
+ check_expected("rgbaint_t::operator+=");
+
+ // test offset addition (method)
+ imm = random_i32();
+ expected_a += imm;
+ expected_r += imm;
+ expected_g += imm;
+ expected_b += imm;
+ rgb.add_imm(imm);
+ check_expected("rgbaint_t::add_imm");
+
+ // test offset addition (operator)
+ imm = random_i32();
+ expected_a += imm;
+ expected_r += imm;
+ expected_g += imm;
+ expected_b += imm;
+ rgb += imm;
+ check_expected("rgbaint_t::operator+=");
+
+ // test immediate RGB addition
+ expected_a += actual_a = random_i32();
+ expected_r += actual_r = random_i32();
+ expected_g += actual_g = random_i32();
+ expected_b += actual_b = random_i32();
+ rgb.add_imm_rgba(actual_a, actual_r, actual_g, actual_b);
+ check_expected("rgbaint_t::add_imm_rgba");
+
+ // test RGB subtraction (method)
+ expected_a -= actual_a = random_i32();
+ expected_r -= actual_r = random_i32();
+ expected_g -= actual_g = random_i32();
+ expected_b -= actual_b = random_i32();
+ rgb.sub(rgbaint_t(actual_a, actual_r, actual_g, actual_b));
+ check_expected("rgbaint_t::sub");
+
+ // test RGB subtraction (operator)
+ expected_a -= actual_a = random_i32();
+ expected_r -= actual_r = random_i32();
+ expected_g -= actual_g = random_i32();
+ expected_b -= actual_b = random_i32();
+ rgb -= rgbaint_t(actual_a, actual_r, actual_g, actual_b);
+ check_expected("rgbaint_t::operator-=");
+
+ // test offset subtraction
+ imm = random_i32();
+ expected_a -= imm;
+ expected_r -= imm;
+ expected_g -= imm;
+ expected_b -= imm;
+ rgb.sub_imm(imm);
+ check_expected("rgbaint_t::sub_imm");
+
+ // test immediate RGB subtraction
+ expected_a -= actual_a = random_i32();
+ expected_r -= actual_r = random_i32();
+ expected_g -= actual_g = random_i32();
+ expected_b -= actual_b = random_i32();
+ rgb.sub_imm_rgba(actual_a, actual_r, actual_g, actual_b);
+ check_expected("rgbaint_t::sub_imm_rgba");
+
+ // test reversed RGB subtraction
+ expected_a = (actual_a = random_i32()) - expected_a;
+ expected_r = (actual_r = random_i32()) - expected_r;
+ expected_g = (actual_g = random_i32()) - expected_g;
+ expected_b = (actual_b = random_i32()) - expected_b;
+ rgb.subr(rgbaint_t(actual_a, actual_r, actual_g, actual_b));
+ check_expected("rgbaint_t::subr");
+
+ // test reversed offset subtraction
+ imm = random_i32();
+ expected_a = imm - expected_a;
+ expected_r = imm - expected_r;
+ expected_g = imm - expected_g;
+ expected_b = imm - expected_b;
+ rgb.subr_imm(imm);
+ check_expected("rgbaint_t::subr_imm");
+
+ // test reversed immediate RGB subtraction
+ expected_a = (actual_a = random_i32()) - expected_a;
+ expected_r = (actual_r = random_i32()) - expected_r;
+ expected_g = (actual_g = random_i32()) - expected_g;
+ expected_b = (actual_b = random_i32()) - expected_b;
+ rgb.subr_imm_rgba(actual_a, actual_r, actual_g, actual_b);
+ check_expected("rgbaint_t::subr_imm_rgba");
+
+ // test RGB multiplication (method)
+ expected_a *= actual_a = random_i32();
+ expected_r *= actual_r = random_i32();
+ expected_g *= actual_g = random_i32();
+ expected_b *= actual_b = random_i32();
+ rgb.mul(rgbaint_t(actual_a, actual_r, actual_g, actual_b));
+ check_expected("rgbaint_t::mul");
+
+ // test RGB multiplication (operator)
+ expected_a *= actual_a = random_i32();
+ expected_r *= actual_r = random_i32();
+ expected_g *= actual_g = random_i32();
+ expected_b *= actual_b = random_i32();
+ rgb *= rgbaint_t(actual_a, actual_r, actual_g, actual_b);
+ check_expected("rgbaint_t::operator*=");
+
+ // test factor multiplication (method)
+ imm = random_i32();
+ expected_a *= imm;
+ expected_r *= imm;
+ expected_g *= imm;
+ expected_b *= imm;
+ rgb.mul_imm(imm);
+ check_expected("rgbaint_t::mul_imm");
+
+ // test factor multiplication (operator)
+ imm = random_i32();
+ expected_a *= imm;
+ expected_r *= imm;
+ expected_g *= imm;
+ expected_b *= imm;
+ rgb *= imm;
+ check_expected("rgbaint_t::operator*=");
+
+ // test immediate RGB multiplication
+ expected_a *= actual_a = random_i32();
+ expected_r *= actual_r = random_i32();
+ expected_g *= actual_g = random_i32();
+ expected_b *= actual_b = random_i32();
+ rgb.mul_imm_rgba(actual_a, actual_r, actual_g, actual_b);
+ check_expected("rgbaint_t::mul_imm_rgba");
+
+ // test select alpha element multiplication
+ expected_a *= actual_a = random_i32();
+ expected_r *= actual_a;
+ expected_g *= actual_a;
+ expected_b *= actual_a;
+ rgb.mul(rgbaint_t(actual_a, actual_r, actual_g, actual_b).select_alpha32());
+ check_expected("rgbaint_t::mul(select_alpha32)");
+
+ // test select red element multiplication
+ expected_a *= actual_r = random_i32();
+ expected_r *= actual_r;
+ expected_g *= actual_r;
+ expected_b *= actual_r;
+ rgb.mul(rgbaint_t(actual_a, actual_r, actual_g, actual_b).select_red32());
+ check_expected("rgbaint_t::mul(select_red32)");
+
+ // test select green element multiplication
+ expected_a *= actual_g = random_i32();
+ expected_r *= actual_g;
+ expected_g *= actual_g;
+ expected_b *= actual_g;
+ rgb.mul(rgbaint_t(actual_a, actual_r, actual_g, actual_b).select_green32());
+ check_expected("rgbaint_t::mul(select_green32)");
+
+ // test select blue element multiplication
+ expected_a *= actual_b = random_i32();
+ expected_r *= actual_b;
+ expected_g *= actual_b;
+ expected_b *= actual_b;
+ rgb.mul(rgbaint_t(actual_a, actual_r, actual_g, actual_b).select_blue32());
+ check_expected("rgbaint_t::mul(select_blue32)");
+
+ // test RGB and not
+ expected_a &= ~(actual_a = random_i32());
+ expected_r &= ~(actual_r = random_i32());
+ expected_g &= ~(actual_g = random_i32());
+ expected_b &= ~(actual_b = random_i32());
+ rgb.andnot_reg(rgbaint_t(actual_a, actual_r, actual_g, actual_b));
+ check_expected("rgbaint_t::andnot_reg");
+
+ // test RGB or
+ expected_a |= actual_a = random_i32();
+ expected_r |= actual_r = random_i32();
+ expected_g |= actual_g = random_i32();
+ expected_b |= actual_b = random_i32();
+ rgb.or_reg(rgbaint_t(actual_a, actual_r, actual_g, actual_b));
+ check_expected("rgbaint_t::or_reg");
+
+ // test RGB and
+ expected_a &= actual_a = random_i32();
+ expected_r &= actual_r = random_i32();
+ expected_g &= actual_g = random_i32();
+ expected_b &= actual_b = random_i32();
+ rgb.and_reg(rgbaint_t(actual_a, actual_r, actual_g, actual_b));
+ check_expected("rgbaint_t::and_reg");
+
+ // test RGB xor
+ expected_a ^= actual_a = random_i32();
+ expected_r ^= actual_r = random_i32();
+ expected_g ^= actual_g = random_i32();
+ expected_b ^= actual_b = random_i32();
+ rgb.xor_reg(rgbaint_t(actual_a, actual_r, actual_g, actual_b));
+ check_expected("rgbaint_t::xor_reg");
+
+ // test uniform or
+ imm = random_i32();
+ expected_a |= imm;
+ expected_r |= imm;
+ expected_g |= imm;
+ expected_b |= imm;
+ rgb.or_imm(imm);
+ check_expected("rgbaint_t::or_imm");
+
+ // test uniform and
+ imm = random_i32();
+ expected_a &= imm;
+ expected_r &= imm;
+ expected_g &= imm;
+ expected_b &= imm;
+ rgb.and_imm(imm);
+ check_expected("rgbaint_t::and_imm");
+
+ // test uniform xor
+ imm = random_i32();
+ expected_a ^= imm;
+ expected_r ^= imm;
+ expected_g ^= imm;
+ expected_b ^= imm;
+ rgb.xor_imm(imm);
+ check_expected("rgbaint_t::xor_imm");
+
+ // test immediate RGB or
+ expected_a |= actual_a = random_i32();
+ expected_r |= actual_r = random_i32();
+ expected_g |= actual_g = random_i32();
+ expected_b |= actual_b = random_i32();
+ rgb.or_imm_rgba(actual_a, actual_r, actual_g, actual_b);
+ check_expected("rgbaint_t::or_imm_rgba");
+
+ // test immediate RGB and
+ expected_a &= actual_a = random_i32();
+ expected_r &= actual_r = random_i32();
+ expected_g &= actual_g = random_i32();
+ expected_b &= actual_b = random_i32();
+ rgb.and_imm_rgba(actual_a, actual_r, actual_g, actual_b);
+ check_expected("rgbaint_t::and_imm_rgba");
+
+ // test immediate RGB xor
+ expected_a ^= actual_a = random_i32();
+ expected_r ^= actual_r = random_i32();
+ expected_g ^= actual_g = random_i32();
+ expected_b ^= actual_b = random_i32();
+ rgb.xor_imm_rgba(actual_a, actual_r, actual_g, actual_b);
+ check_expected("rgbaint_t::xor_imm_rgba");
+
+ // test 8-bit get
+ expected_a = s32(u32(expected_a) & 0x00ff);
+ expected_r = s32(u32(expected_r) & 0x00ff);
+ expected_g = s32(u32(expected_g) & 0x00ff);
+ expected_b = s32(u32(expected_b) & 0x00ff);
+ actual_a = s32(u32(rgb.get_a()));
+ actual_r = s32(u32(rgb.get_r()));
+ actual_g = s32(u32(rgb.get_g()));
+ actual_b = s32(u32(rgb.get_b()));
+ if (actual_a != expected_a) osd_printf_error("Error testing rgbaint_t::get_a() = %d (expected %d)\n", s32(actual_a), s32(expected_a));
+ if (actual_r != expected_r) osd_printf_error("Error testing rgbaint_t::get_r() = %d (expected %d)\n", s32(actual_r), s32(expected_r));
+ if (actual_g != expected_g) osd_printf_error("Error testing rgbaint_t::get_g() = %d (expected %d)\n", s32(actual_g), s32(expected_g));
+ if (actual_b != expected_b) osd_printf_error("Error testing rgbaint_t::get_b() = %d (expected %d)\n", s32(actual_b), s32(expected_b));
+
+ // test set from packed RGBA
+ imm = random_i32();
+ expected_a = s32((u32(imm) >> 24) & 0x00ff);
+ expected_r = s32((u32(imm) >> 16) & 0x00ff);
+ expected_g = s32((u32(imm) >> 8) & 0x00ff);
+ expected_b = s32((u32(imm) >> 0) & 0x00ff);
+ rgb.set(u32(imm));
+ check_expected("rgbaint_t::set(u32)");
+
+ // while we have a value loaded that we know doesn't exceed 8-bit range, check the non-clamping convert-to-rgba
+ packed = rgb.to_rgba();
+ if (u32(imm) != u32(packed))
+ osd_printf_error("Error testing rgbaint_t::to_rgba() = %08x (expected %08x)\n", u32(packed), u32(imm));
+
+ // test construct from packed RGBA and assign
+ imm = random_i32();
+ expected_a = s32((u32(imm) >> 24) & 0x00ff);
+ expected_r = s32((u32(imm) >> 16) & 0x00ff);
+ expected_g = s32((u32(imm) >> 8) & 0x00ff);
+ expected_b = s32((u32(imm) >> 0) & 0x00ff);
+ rgb = rgbaint_t(u32(imm));
+ check_expected("rgbaint_t(u32)");
+
+ // while we have a value loaded that we know doesn't exceed 8-bit range, check the non-clamping convert-to-rgba
+ packed = rgb.to_rgba();
+ if (u32(imm) != u32(packed))
+ osd_printf_error("Error testing rgbaint_t::to_rgba() = %08x (expected %08x)\n", u32(packed), u32(imm));
+
+ // test set with rgb_t
+ packed = random_u32();
+ expected_a = s32(u32(packed.a()));
+ expected_r = s32(u32(packed.r()));
+ expected_g = s32(u32(packed.g()));
+ expected_b = s32(u32(packed.b()));
+ rgb.set(packed);
+ check_expected("rgbaint_t::set(rgba_t)");
+
+ // test construct with rgb_t
+ packed = random_u32();
+ expected_a = s32(u32(packed.a()));
+ expected_r = s32(u32(packed.r()));
+ expected_g = s32(u32(packed.g()));
+ expected_b = s32(u32(packed.b()));
+ rgb = rgbaint_t(packed);
+ check_expected("rgbaint_t::set(rgba_t)");
+
+ // test clamping convert-to-rgba with hand-crafted values to catch edge cases
+ rgb.set(std::numeric_limits<s32>::min(), -1, 0, 1);
+ packed = rgb.to_rgba_clamp();
+ if (u32(0x00000001) != u32(packed))
+ osd_printf_error("Error testing rgbaint_t::to_rgba_clamp() = %08x (expected 0x00000001)\n", u32(packed));
+ rgb.set(254, 255, 256, std::numeric_limits<s32>::max());
+ packed = rgb.to_rgba_clamp();
+ if (u32(0xfeffffff) != u32(packed))
+ osd_printf_error("Error testing rgbaint_t::to_rgba_clamp() = %08x (expected 0xfeffffff)\n", u32(packed));
+ rgb.set(std::numeric_limits<s32>::max(), std::numeric_limits<s32>::min(), 256, -1);
+ packed = rgb.to_rgba_clamp();
+ if (u32(0xff00ff00) != u32(packed))
+ osd_printf_error("Error testing rgbaint_t::to_rgba_clamp() = %08x (expected 0xff00ff00)\n", u32(packed));
+ rgb.set(0, 255, 1, 254);
+ packed = rgb.to_rgba_clamp();
+ if (u32(0x00ff01fe) != u32(packed))
+ osd_printf_error("Error testing rgbaint_t::to_rgba_clamp() = %08x (expected 0x00ff01fe)\n", u32(packed));
+
+ // test in-place clamping with hand-crafted values to catch edge cases
+ expected_a = 0;
+ expected_r = 0;
+ expected_g = 0;
+ expected_b = 1;
+ rgb.set(std::numeric_limits<s32>::min(), -1, 0, 1);
+ rgb.clamp_to_uint8();
+ check_expected("rgbaint_t::clamp_to_uint8");
+ expected_a = 254;
+ expected_r = 255;
+ expected_g = 255;
+ expected_b = 255;
+ rgb.set(254, 255, 256, std::numeric_limits<s32>::max());
+ rgb.clamp_to_uint8();
+ check_expected("rgbaint_t::clamp_to_uint8");
+ expected_a = 255;
+ expected_r = 0;
+ expected_g = 255;
+ expected_b = 0;
+ rgb.set(std::numeric_limits<s32>::max(), std::numeric_limits<s32>::min(), 256, -1);
+ rgb.clamp_to_uint8();
+ check_expected("rgbaint_t::clamp_to_uint8");
+ expected_a = 0;
+ expected_r = 255;
+ expected_g = 1;
+ expected_b = 254;
+ rgb.set(0, 255, 1, 254);
+ rgb.clamp_to_uint8();
+ check_expected("rgbaint_t::clamp_to_uint8");
+
+ // test shift left
+ expected_a = (actual_a = random_i32()) << 19;
+ expected_r = (actual_r = random_i32()) << 3;
+ expected_g = (actual_g = random_i32()) << 21;
+ expected_b = (actual_b = random_i32()) << 6;
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.shl(rgbaint_t(19, 3, 21, 6));
+ check_expected("rgbaint_t::shl");
+
+ // test shift left immediate
+ expected_a = (actual_a = random_i32()) << 7;
+ expected_r = (actual_r = random_i32()) << 7;
+ expected_g = (actual_g = random_i32()) << 7;
+ expected_b = (actual_b = random_i32()) << 7;
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.shl_imm(7);
+ check_expected("rgbaint_t::shl_imm");
+
+ // test logical shift right
+ expected_a = s32(u32(actual_a = random_i32()) >> 8);
+ expected_r = s32(u32(actual_r = random_i32()) >> 18);
+ expected_g = s32(u32(actual_g = random_i32()) >> 26);
+ expected_b = s32(u32(actual_b = random_i32()) >> 4);
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.shr(rgbaint_t(8, 18, 26, 4));
+ check_expected("rgbaint_t::shr");
+
+ // test logical shift right with opposite signs
+ expected_a = s32(u32(actual_a = -actual_a) >> 21);
+ expected_r = s32(u32(actual_r = -actual_r) >> 13);
+ expected_g = s32(u32(actual_g = -actual_g) >> 11);
+ expected_b = s32(u32(actual_b = -actual_b) >> 17);
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.shr(rgbaint_t(21, 13, 11, 17));
+ check_expected("rgbaint_t::shr");
+
+ // test logical shift right immediate
+ expected_a = s32(u32(actual_a = random_i32()) >> 5);
+ expected_r = s32(u32(actual_r = random_i32()) >> 5);
+ expected_g = s32(u32(actual_g = random_i32()) >> 5);
+ expected_b = s32(u32(actual_b = random_i32()) >> 5);
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.shr_imm(5);
+ check_expected("rgbaint_t::shr_imm");
+
+ // test logical shift right immediate with opposite signs
+ expected_a = s32(u32(actual_a = -actual_a) >> 15);
+ expected_r = s32(u32(actual_r = -actual_r) >> 15);
+ expected_g = s32(u32(actual_g = -actual_g) >> 15);
+ expected_b = s32(u32(actual_b = -actual_b) >> 15);
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.shr_imm(15);
+ check_expected("rgbaint_t::shr_imm");
+
+ // test arithmetic shift right
+ expected_a = (actual_a = random_i32()) >> 16;
+ expected_r = (actual_r = random_i32()) >> 20;
+ expected_g = (actual_g = random_i32()) >> 14;
+ expected_b = (actual_b = random_i32()) >> 2;
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.sra(rgbaint_t(16, 20, 14, 2));
+ check_expected("rgbaint_t::sra");
+
+ // test arithmetic shift right with opposite signs
+ expected_a = (actual_a = -actual_a) >> 1;
+ expected_r = (actual_r = -actual_r) >> 29;
+ expected_g = (actual_g = -actual_g) >> 10;
+ expected_b = (actual_b = -actual_b) >> 22;
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.sra(rgbaint_t(1, 29, 10, 22));
+ check_expected("rgbaint_t::sra");
+
+ // test arithmetic shift right immediate (method)
+ expected_a = (actual_a = random_i32()) >> 12;
+ expected_r = (actual_r = random_i32()) >> 12;
+ expected_g = (actual_g = random_i32()) >> 12;
+ expected_b = (actual_b = random_i32()) >> 12;
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.sra_imm(12);
+ check_expected("rgbaint_t::sra_imm");
+
+ // test arithmetic shift right immediate with opposite signs (method)
+ expected_a = (actual_a = -actual_a) >> 9;
+ expected_r = (actual_r = -actual_r) >> 9;
+ expected_g = (actual_g = -actual_g) >> 9;
+ expected_b = (actual_b = -actual_b) >> 9;
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.sra_imm(9);
+ check_expected("rgbaint_t::sra_imm");
+
+ // test arithmetic shift right immediate (operator)
+ expected_a = (actual_a = random_i32()) >> 7;
+ expected_r = (actual_r = random_i32()) >> 7;
+ expected_g = (actual_g = random_i32()) >> 7;
+ expected_b = (actual_b = random_i32()) >> 7;
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb >>= 7;
+ check_expected("rgbaint_t::operator>>=");
+
+ // test arithmetic shift right immediate with opposite signs (operator)
+ expected_a = (actual_a = -actual_a) >> 11;
+ expected_r = (actual_r = -actual_r) >> 11;
+ expected_g = (actual_g = -actual_g) >> 11;
+ expected_b = (actual_b = -actual_b) >> 11;
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb >>= 11;
+ check_expected("rgbaint_t::operator>>=");
+
+ // test RGB equality comparison
+ actual_a = random_i32_nolimit();
+ actual_r = random_i32_nolimit();
+ actual_g = random_i32_nolimit();
+ actual_b = random_i32_nolimit();
+ expected_a = ~s32(0);
+ expected_r = 0;
+ expected_g = 0;
+ expected_b = 0;
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.cmpeq(rgbaint_t(actual_a, actual_r - 1, actual_g + 1, std::numeric_limits<s32>::min()));
+ check_expected("rgbaint_t::cmpeq");
+ expected_a = 0;
+ expected_r = ~s32(0);
+ expected_g = 0;
+ expected_b = 0;
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.cmpeq(rgbaint_t(std::numeric_limits<s32>::max(), actual_r, actual_g - 1, actual_b + 1));
+ check_expected("rgbaint_t::cmpeq");
+
+ // test immediate equality comparison
+ actual_a = random_i32_nolimit();
+ actual_r = random_i32_nolimit();
+ actual_g = random_i32_nolimit();
+ actual_b = random_i32_nolimit();
+ expected_a = ~s32(0);
+ expected_r = (actual_r == actual_a) ? ~s32(0) : 0;
+ expected_g = (actual_g == actual_a) ? ~s32(0) : 0;
+ expected_b = (actual_b == actual_a) ? ~s32(0) : 0;
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.cmpeq_imm(actual_a);
+ check_expected("rgbaint_t::cmpeq_imm");
+ expected_a = (actual_a == actual_r) ? ~s32(0) : 0;
+ expected_r = ~s32(0);
+ expected_g = (actual_g == actual_r) ? ~s32(0) : 0;
+ expected_b = (actual_b == actual_r) ? ~s32(0) : 0;
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.cmpeq_imm(actual_r);
+ check_expected("rgbaint_t::cmpeq_imm");
+ expected_a = (actual_a == actual_g) ? ~s32(0) : 0;
+ expected_r = (actual_r == actual_g) ? ~s32(0) : 0;
+ expected_g = ~s32(0);
+ expected_b = (actual_b == actual_g) ? ~s32(0) : 0;
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.cmpeq_imm(actual_g);
+ check_expected("rgbaint_t::cmpeq_imm");
+ expected_a = (actual_a == actual_b) ? ~s32(0) : 0;
+ expected_r = (actual_r == actual_b) ? ~s32(0) : 0;
+ expected_g = (actual_g == actual_b) ? ~s32(0) : 0;
+ expected_b = ~s32(0);
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.cmpeq_imm(actual_b);
+ check_expected("rgbaint_t::cmpeq_imm");
+ expected_a = 0;
+ expected_r = 0;
+ expected_g = 0;
+ expected_b = 0;
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.cmpeq_imm(std::numeric_limits<s32>::min());
+ check_expected("rgbaint_t::cmpeq_imm");
+ expected_a = !actual_a ? ~s32(0) : 0;
+ expected_r = !actual_r ? ~s32(0) : 0;
+ expected_g = !actual_g ? ~s32(0) : 0;
+ expected_b = !actual_b ? ~s32(0) : 0;
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.cmpeq_imm(0);
+ check_expected("rgbaint_t::cmpeq_imm");
+ expected_a = 0;
+ expected_r = 0;
+ expected_g = 0;
+ expected_b = 0;
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.cmpeq_imm(std::numeric_limits<s32>::max());
+ check_expected("rgbaint_t::cmpeq_imm");
+
+ // test immediate RGB equality comparison
+ actual_a = random_i32_nolimit();
+ actual_r = random_i32_nolimit();
+ actual_g = random_i32_nolimit();
+ actual_b = random_i32_nolimit();
+ expected_a = 0;
+ expected_r = 0;
+ expected_g = ~s32(0);
+ expected_b = 0;
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.cmpeq_imm_rgba(std::numeric_limits<s32>::min(), std::numeric_limits<s32>::max(), actual_g, actual_b - 1);
+ check_expected("rgbaint_t::cmpeq_imm_rgba");
+ expected_a = 0;
+ expected_r = 0;
+ expected_g = 0;
+ expected_b = ~s32(0);
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.cmpeq_imm_rgba(actual_a + 1, std::numeric_limits<s32>::min(), std::numeric_limits<s32>::max(), actual_b);
+ check_expected("rgbaint_t::cmpeq_imm_rgba");
+
+ // test RGB greater than comparison
+ actual_a = random_i32_nolimit();
+ actual_r = random_i32_nolimit();
+ actual_g = random_i32_nolimit();
+ actual_b = random_i32_nolimit();
+ expected_a = 0;
+ expected_r = ~s32(0);
+ expected_g = 0;
+ expected_b = ~s32(0);
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.cmpgt(rgbaint_t(actual_a, actual_r - 1, actual_g + 1, std::numeric_limits<s32>::min()));
+ check_expected("rgbaint_t::cmpgt");
+ expected_a = 0;
+ expected_r = 0;
+ expected_g = ~s32(0);
+ expected_b = 0;
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.cmpgt(rgbaint_t(std::numeric_limits<s32>::max(), actual_r, actual_g - 1, actual_b + 1));
+ check_expected("rgbaint_t::cmpgt");
+
+ // test immediate greater than comparison
+ actual_a = random_i32_nolimit();
+ actual_r = random_i32_nolimit();
+ actual_g = random_i32_nolimit();
+ actual_b = random_i32_nolimit();
+ expected_a = 0;
+ expected_r = (actual_r > actual_a) ? ~s32(0) : 0;
+ expected_g = (actual_g > actual_a) ? ~s32(0) : 0;
+ expected_b = (actual_b > actual_a) ? ~s32(0) : 0;
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.cmpgt_imm(actual_a);
+ check_expected("rgbaint_t::cmpgt_imm");
+ expected_a = (actual_a > actual_r) ? ~s32(0) : 0;
+ expected_r = 0;
+ expected_g = (actual_g > actual_r) ? ~s32(0) : 0;
+ expected_b = (actual_b > actual_r) ? ~s32(0) : 0;
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.cmpgt_imm(actual_r);
+ check_expected("rgbaint_t::cmpgt_imm");
+ expected_a = (actual_a > actual_g) ? ~s32(0) : 0;
+ expected_r = (actual_r > actual_g) ? ~s32(0) : 0;
+ expected_g =0;
+ expected_b = (actual_b > actual_g) ? ~s32(0) : 0;
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.cmpgt_imm(actual_g);
+ check_expected("rgbaint_t::cmpgt_imm");
+ expected_a = (actual_a > actual_b) ? ~s32(0) : 0;
+ expected_r = (actual_r > actual_b) ? ~s32(0) : 0;
+ expected_g = (actual_g > actual_b) ? ~s32(0) : 0;
+ expected_b = 0;
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.cmpgt_imm(actual_b);
+ check_expected("rgbaint_t::cmpgt_imm");
+ expected_a = ~s32(0);
+ expected_r = ~s32(0);
+ expected_g = ~s32(0);
+ expected_b = ~s32(0);
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.cmpgt_imm(std::numeric_limits<s32>::min());
+ check_expected("rgbaint_t::cmpgt_imm");
+ expected_a = (actual_a > 0) ? ~s32(0) : 0;
+ expected_r = (actual_r > 0) ? ~s32(0) : 0;
+ expected_g = (actual_g > 0) ? ~s32(0) : 0;
+ expected_b = (actual_b > 0) ? ~s32(0) : 0;
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.cmpgt_imm(0);
+ check_expected("rgbaint_t::cmpgt_imm");
+ expected_a = 0;
+ expected_r = 0;
+ expected_g = 0;
+ expected_b = 0;
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.cmpgt_imm(std::numeric_limits<s32>::max());
+ check_expected("rgbaint_t::cmpgt_imm");
+
+ // test immediate RGB greater than comparison
+ actual_a = random_i32_nolimit();
+ actual_r = random_i32_nolimit();
+ actual_g = random_i32_nolimit();
+ actual_b = random_i32_nolimit();
+ expected_a = ~s32(0);
+ expected_r = 0;
+ expected_g = 0;
+ expected_b = ~s32(0);
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.cmpgt_imm_rgba(std::numeric_limits<s32>::min(), std::numeric_limits<s32>::max(), actual_g, actual_b - 1);
+ check_expected("rgbaint_t::cmpgt_imm_rgba");
+ expected_a = 0;
+ expected_r = ~s32(0);
+ expected_g = 0;
+ expected_b = 0;
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.cmpgt_imm_rgba(actual_a + 1, std::numeric_limits<s32>::min(), std::numeric_limits<s32>::max(), actual_b);
+ check_expected("rgbaint_t::cmpgt_imm_rgba");
+
+ // test RGB less than comparison
+ actual_a = random_i32_nolimit();
+ actual_r = random_i32_nolimit();
+ actual_g = random_i32_nolimit();
+ actual_b = random_i32_nolimit();
+ expected_a = 0;
+ expected_r = 0;
+ expected_g = ~s32(0);
+ expected_b = 0;
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.cmplt(rgbaint_t(actual_a, actual_r - 1, actual_g + 1, std::numeric_limits<s32>::min()));
+ check_expected("rgbaint_t::cmplt");
+ expected_a = ~s32(0);
+ expected_r = 0;
+ expected_g = 0;
+ expected_b = ~s32(0);
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.cmplt(rgbaint_t(std::numeric_limits<s32>::max(), actual_r, actual_g - 1, actual_b + 1));
+ check_expected("rgbaint_t::cmplt");
+
+ // test immediate less than comparison
+ actual_a = random_i32_nolimit();
+ actual_r = random_i32_nolimit();
+ actual_g = random_i32_nolimit();
+ actual_b = random_i32_nolimit();
+ expected_a = 0;
+ expected_r = (actual_r < actual_a) ? ~s32(0) : 0;
+ expected_g = (actual_g < actual_a) ? ~s32(0) : 0;
+ expected_b = (actual_b < actual_a) ? ~s32(0) : 0;
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.cmplt_imm(actual_a);
+ check_expected("rgbaint_t::cmplt_imm");
+ expected_a = (actual_a < actual_r) ? ~s32(0) : 0;
+ expected_r = 0;
+ expected_g = (actual_g < actual_r) ? ~s32(0) : 0;
+ expected_b = (actual_b < actual_r) ? ~s32(0) : 0;
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.cmplt_imm(actual_r);
+ check_expected("rgbaint_t::cmplt_imm");
+ expected_a = (actual_a < actual_g) ? ~s32(0) : 0;
+ expected_r = (actual_r < actual_g) ? ~s32(0) : 0;
+ expected_g =0;
+ expected_b = (actual_b < actual_g) ? ~s32(0) : 0;
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.cmplt_imm(actual_g);
+ check_expected("rgbaint_t::cmplt_imm");
+ expected_a = (actual_a < actual_b) ? ~s32(0) : 0;
+ expected_r = (actual_r < actual_b) ? ~s32(0) : 0;
+ expected_g = (actual_g < actual_b) ? ~s32(0) : 0;
+ expected_b = 0;
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.cmplt_imm(actual_b);
+ check_expected("rgbaint_t::cmplt_imm");
+ expected_a = 0;
+ expected_r = 0;
+ expected_g = 0;
+ expected_b = 0;
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.cmplt_imm(std::numeric_limits<s32>::min());
+ check_expected("rgbaint_t::cmplt_imm");
+ expected_a = (actual_a < 0) ? ~s32(0) : 0;
+ expected_r = (actual_r < 0) ? ~s32(0) : 0;
+ expected_g = (actual_g < 0) ? ~s32(0) : 0;
+ expected_b = (actual_b < 0) ? ~s32(0) : 0;
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.cmplt_imm(0);
+ check_expected("rgbaint_t::cmplt_imm");
+ expected_a = ~s32(0);
+ expected_r = ~s32(0);
+ expected_g = ~s32(0);
+ expected_b = ~s32(0);
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.cmplt_imm(std::numeric_limits<s32>::max());
+ check_expected("rgbaint_t::cmplt_imm");
+
+ // test immediate RGB less than comparison
+ actual_a = random_i32_nolimit();
+ actual_r = random_i32_nolimit();
+ actual_g = random_i32_nolimit();
+ actual_b = random_i32_nolimit();
+ expected_a = 0;
+ expected_r = ~s32(0);
+ expected_g = 0;
+ expected_b = 0;
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.cmplt_imm_rgba(std::numeric_limits<s32>::min(), std::numeric_limits<s32>::max(), actual_g, actual_b - 1);
+ check_expected("rgbaint_t::cmplt_imm_rgba");
+ expected_a = ~s32(0);
+ expected_r = 0;
+ expected_g = ~s32(0);
+ expected_b = 0;
+ rgb.set(actual_a, actual_r, actual_g, actual_b);
+ rgb.cmplt_imm_rgba(actual_a + 1, std::numeric_limits<s32>::min(), std::numeric_limits<s32>::max(), actual_b);
+ check_expected("rgbaint_t::cmplt_imm_rgba");
+
+ // test bilinear_filter and bilinear_filter_rgbaint
+ // SSE implementation carries more internal precision between the bilinear stages
+#if defined(MAME_RGB_HIGH_PRECISION)
+ const int first_shift = 1;
+#else
+ const int first_shift = 8;
+#endif
+ for (int index = 0; index < 1000; index++)
+ {
+ u8 u, v;
+ rgbaint_t rgb_point[4];
+ u32 top_row, bottom_row;
+
+ for (int i = 0; i < 4; i++)
+ {
+ rgb_point[i].set(random_u32());
+ }
+
+ switch (index)
+ {
+ case 0: u = 0; v = 0; break;
+ case 1: u = 255; v = 255; break;
+ case 2: u = 0; v = 255; break;
+ case 3: u = 255; v = 0; break;
+ case 4: u = 128; v = 128; break;
+ case 5: u = 63; v = 32; break;
+ default:
+ u = random_u32() & 0xff;
+ v = random_u32() & 0xff;
+ break;
+ }
+
+ top_row = (rgb_point[0].get_a() * (256 - u) + rgb_point[1].get_a() * u) >> first_shift;
+ bottom_row = (rgb_point[2].get_a() * (256 - u) + rgb_point[3].get_a() * u) >> first_shift;
+ expected_a = (top_row * (256 - v) + bottom_row * v) >> (16 - first_shift);
+
+ top_row = (rgb_point[0].get_r() * (256 - u) + rgb_point[1].get_r() * u) >> first_shift;
+ bottom_row = (rgb_point[2].get_r() * (256 - u) + rgb_point[3].get_r() * u) >> first_shift;
+ expected_r = (top_row * (256 - v) + bottom_row * v) >> (16 - first_shift);
+
+ top_row = (rgb_point[0].get_g() * (256 - u) + rgb_point[1].get_g() * u) >> first_shift;
+ bottom_row = (rgb_point[2].get_g() * (256 - u) + rgb_point[3].get_g() * u) >> first_shift;
+ expected_g = (top_row * (256 - v) + bottom_row * v) >> (16 - first_shift);
+
+ top_row = (rgb_point[0].get_b() * (256 - u) + rgb_point[1].get_b() * u) >> first_shift;
+ bottom_row = (rgb_point[2].get_b() * (256 - u) + rgb_point[3].get_b() * u) >> first_shift;
+ expected_b = (top_row * (256 - v) + bottom_row * v) >> (16 - first_shift);
+
+ imm = rgbaint_t::bilinear_filter(rgb_point[0].to_rgba(), rgb_point[1].to_rgba(), rgb_point[2].to_rgba(), rgb_point[3].to_rgba(), u, v);
+ rgb.set(imm);
+ check_expected("rgbaint_t::bilinear_filter");
+
+ rgb.bilinear_filter_rgbaint(rgb_point[0].to_rgba(), rgb_point[1].to_rgba(), rgb_point[2].to_rgba(), rgb_point[3].to_rgba(), u, v);
+ check_expected("rgbaint_t::bilinear_filter_rgbaint");
+ }
+}
+
+
+//-------------------------------------------------
+// validate_delegates_mfp - test delegate member
+// function functionality
+//-------------------------------------------------
+
+void validate_delegates_mfp()
+{
+ struct base_a
+ {
+ virtual ~base_a() = default;
+ char get_a(void const *&p) { p = this; return 'a'; }
+ virtual char get_a_v(void const *&p) { p = this; return 'A'; }
+ int a;
+ };
+
+ struct base_b
+ {
+ virtual ~base_b() = default;
+ char get_b(void const *&p) { p = this; return 'b'; }
+ virtual char get_b_v(void const *&p) { p = this; return 'B'; }
+ int b;
+ };
+
+ struct multiple_inheritance : base_a, base_b
+ {
+ };
+
+ struct overridden : base_a, base_b
+ {
+ virtual char get_a_v(void const *&p) override { p = this; return 'x'; }
+ virtual char get_b_v(void const *&p) override { p = this; return 'y'; }
+ };
+
+ multiple_inheritance mi;
+ overridden o;
+ diamond_inheritance d;
+ char ch;
+ void const *addr;
+
+ // test non-virtual member functions and "this" pointer adjustment
+ test_delegate cb1(&multiple_inheritance::get_a, &mi);
+ test_delegate cb2(&multiple_inheritance::get_b, &mi);
+
+ addr = nullptr;
+ ch = cb1(addr);
+ if ('a' != ch)
+ osd_printf_error("Error testing delegate non-virtual member function dispatch\n");
+ if (static_cast<base_a *>(&mi) != addr)
+ osd_printf_error("Error testing delegate this pointer adjustment %p -> %p (expected %p)\n", static_cast<void const *>(&mi), addr, static_cast<void const *>(static_cast<base_a *>(&mi)));
+
+ addr = nullptr;
+ ch = cb2(addr);
+ if ('b' != ch)
+ osd_printf_error("Error testing delegate non-virtual member function dispatch\n");
+ if (static_cast<base_b *>(&mi) != addr)
+ osd_printf_error("Error testing delegate this pointer adjustment %p -> %p (expected %p)\n", static_cast<void const *>(&mi), addr, static_cast<void const *>(static_cast<base_b *>(&mi)));
+
+ // test that "this" pointer adjustment survives copy construction
+ test_delegate cb3(cb1);
+ test_delegate cb4(cb2);
+
+ addr = nullptr;
+ ch = cb3(addr);
+ if ('a' != ch)
+ osd_printf_error("Error testing copy constructed delegate non-virtual member function dispatch\n");
+ if (static_cast<base_a *>(&mi) != addr)
+ osd_printf_error("Error testing copy constructed delegate this pointer adjustment %p -> %p (expected %p)\n", static_cast<void const *>(&mi), addr, static_cast<void const *>(static_cast<base_a *>(&mi)));
+
+ addr = nullptr;
+ ch = cb4(addr);
+ if ('b' != ch)
+ osd_printf_error("Error testing copy constructed delegate non-virtual member function dispatch\n");
+ if (static_cast<base_b *>(&mi) != addr)
+ osd_printf_error("Error testing copy constructed delegate this pointer adjustment %p -> %p (expected %p)\n", static_cast<void const *>(&mi), addr, static_cast<void const *>(static_cast<base_b *>(&mi)));
+
+ // test that "this" pointer adjustment survives assignment and doesn't suffer generational loss
+ cb1 = cb4;
+ cb2 = cb3;
+
+ addr = nullptr;
+ ch = cb1(addr);
+ if ('b' != ch)
+ osd_printf_error("Error testing assigned delegate non-virtual member function dispatch\n");
+ if (static_cast<base_b *>(&mi) != addr)
+ osd_printf_error("Error testing assigned delegate this pointer adjustment %p -> %p (expected %p)\n", static_cast<void const *>(&mi), addr, static_cast<void const *>(static_cast<base_b *>(&mi)));
+
+ addr = nullptr;
+ ch = cb2(addr);
+ if ('a' != ch)
+ osd_printf_error("Error testing assigned delegate non-virtual member function dispatch\n");
+ if (static_cast<base_a *>(&mi) != addr)
+ osd_printf_error("Error testing assigned delegate this pointer adjustment %p -> %p (expected %p)\n", static_cast<void const *>(&mi), addr, static_cast<void const *>(static_cast<base_a *>(&mi)));
+
+ // test virtual member functions and "this" pointer adjustment
+ cb1 = test_delegate(&multiple_inheritance::get_a_v, &mi);
+ cb2 = test_delegate(&multiple_inheritance::get_b_v, &mi);
+
+ addr = nullptr;
+ ch = cb1(addr);
+ if ('A' != ch)
+ osd_printf_error("Error testing delegate virtual member function dispatch\n");
+ if (static_cast<base_a *>(&mi) != addr)
+ osd_printf_error("Error testing delegate this pointer adjustment for virtual member function %p -> %p (expected %p)\n", static_cast<void const *>(&mi), addr, static_cast<void const *>(static_cast<base_a *>(&mi)));
+
+ addr = nullptr;
+ ch = cb2(addr);
+ if ('B' != ch)
+ osd_printf_error("Error testing delegate virtual member function dispatch\n");
+ if (static_cast<base_b *>(&mi) != addr)
+ osd_printf_error("Error testing delegate this pointer adjustment for virtual member function %p -> %p (expected %p)\n", static_cast<void const *>(&mi), addr, static_cast<void const *>(static_cast<base_b *>(&mi)));
+
+ // test that virtual member functions survive copy construction
+ test_delegate cb5(cb1);
+ test_delegate cb6(cb2);
+
+ addr = nullptr;
+ ch = cb5(addr);
+ if ('A' != ch)
+ osd_printf_error("Error testing copy constructed delegate virtual member function dispatch\n");
+ if (static_cast<base_a *>(&mi) != addr)
+ osd_printf_error("Error testing copy constructed delegate this pointer adjustment for virtual member function %p -> %p (expected %p)\n", static_cast<void const *>(&mi), addr, static_cast<void const *>(static_cast<base_a *>(&mi)));
+
+ addr = nullptr;
+ ch = cb6(addr);
+ if ('B' != ch)
+ osd_printf_error("Error testing copy constructed delegate virtual member function dispatch\n");
+ if (static_cast<base_b *>(&mi) != addr)
+ osd_printf_error("Error testing copy constructed delegate this pointer adjustment for virtual member function %p -> %p (expected %p)\n", static_cast<void const *>(&mi), addr, static_cast<void const *>(static_cast<base_b *>(&mi)));
+
+ // test virtual member function dispatch through base pointer
+ cb1 = test_delegate(&base_a::get_a_v, static_cast<base_a *>(&o));
+ cb2 = test_delegate(&base_b::get_b_v, static_cast<base_b *>(&o));
+
+ addr = nullptr;
+ ch = cb1(addr);
+ if ('x' != ch)
+ osd_printf_error("Error testing delegate virtual member function dispatch through base class pointer\n");
+ if (&o != addr)
+ osd_printf_error("Error testing delegate this pointer adjustment for virtual member function through base class pointer %p -> %p (expected %p)\n", static_cast<void const *>(static_cast<base_a *>(&o)), addr, static_cast<void const *>(&o));
+
+ addr = nullptr;
+ ch = cb2(addr);
+ if ('y' != ch)
+ osd_printf_error("Error testing delegate virtual member function dispatch through base class pointer\n");
+ if (&o != addr)
+ osd_printf_error("Error testing delegate this pointer adjustment for virtual member function through base class pointer %p -> %p (expected %p)\n", static_cast<void const *>(static_cast<base_b *>(&o)), addr, static_cast<void const *>(&o));
+
+#if !defined(_LIBCPP_VERSION) || (_LIBCPP_VERSION >= 7000)
+ // test creating delegates for a forward-declared class
+ cb1 = make_diamond_class_delegate(&diamond_inheritance::get_derived_a, &d);
+ cb2 = make_diamond_class_delegate(&diamond_inheritance::get_derived_b, &d);
+
+ addr = nullptr;
+ ch = cb1(addr);
+ if ('a' != ch)
+ osd_printf_error("Error testing delegate non-virtual member function dispatch for incomplete class\n");
+ if (static_cast<virtual_derived_a *>(&d) != addr)
+ osd_printf_error("Error testing delegate this pointer adjustment for incomplete class %p -> %p (expected %p)\n", static_cast<void const *>(&d), addr, static_cast<void const *>(static_cast<virtual_derived_b *>(&d)));
+
+ addr = nullptr;
+ ch = cb2(addr);
+ if ('b' != ch)
+ osd_printf_error("Error testing delegate non-virtual member function dispatch for incomplete class\n");
+ if (static_cast<virtual_derived_b *>(&d) != addr)
+ osd_printf_error("Error testing delegate this pointer adjustment for incomplete class %p -> %p (expected %p)\n", static_cast<void const *>(&d), addr, static_cast<void const *>(static_cast<virtual_derived_b *>(&d)));
+
+#if defined(_MSC_VER) && !defined(__clang__)
+ // test MSVC extension allowing casting member pointer types across virtual inheritance relationships
+ cb1 = make_diamond_class_delegate(&diamond_inheritance::get_base, &d);
+
+ addr = nullptr;
+ ch = cb1(addr);
+ if ('x' != ch)
+ osd_printf_error("Error testing delegate non-virtual member function dispatch for incomplete class\n");
+ if (static_cast<virtual_base *>(&d) != addr)
+ osd_printf_error("Error testing delegate this pointer adjustment for incomplete class %p -> %p (expected %p)\n", static_cast<void const *>(&d), addr, static_cast<void const *>(static_cast<virtual_base *>(&d)));
+#endif // defined(_MSC_VER) && !defined(__clang__)
+#endif // !defined(_LIBCPP_VERSION) || (_LIBCPP_VERSION >= 7000)
+}
+
+
+//-------------------------------------------------
+// validate_delegates_latebind - test binding a
+// delegate to an object after the function is
+// set
+//-------------------------------------------------
+
+void validate_delegates_latebind()
+{
+ struct derived_a : pure_virtual_base, delegate_late_bind
+ {
+ virtual char operator()(void const *&p) const override { p = this; return 'a'; }
+ };
+
+ struct derived_b : pure_virtual_base, delegate_late_bind
+ {
+ virtual char operator()(void const *&p) const override { p = this; return 'b'; }
+ };
+
+ struct unrelated : delegate_late_bind
+ {
+ };
+
+ char ch;
+ void const *addr;
+ derived_a a;
+ derived_b b;
+ unrelated u;
+
+ // delegate with no target object
+ test_delegate cb1(&pure_virtual_base::operator(), static_cast<pure_virtual_base *>(nullptr));
+
+ // test late bind on construction
+ test_delegate cb2(cb1, a);
+ addr = nullptr;
+ ch = cb2(addr);
+ if (('a' != ch) || (&a != addr))
+ osd_printf_error("Error testing delegate late bind on construction\n");
+
+ // test explicit late bind
+ cb1.late_bind(b);
+ ch = cb1(addr);
+ if (('b' != ch) || (&b != addr))
+ osd_printf_error("Error testing delegate explicit late bind\n");
+
+ // test late bind when object is set
+ cb1.late_bind(a);
+ ch = cb1(addr);
+ if (('a' != ch) || (&a != addr))
+ osd_printf_error("Error testing delegate explicit late bind when object is set\n");
+
+ // test late bind on copy of delegate with target set
+ test_delegate cb3(cb1, b);
+ addr = nullptr;
+ ch = cb3(addr);
+ if (('b' != ch) || (&b != addr))
+ osd_printf_error("Error testing delegate late bind on construction using template with object set\n");
+
+ // test late bind exception
+ ch = '-';
+ try
+ {
+ cb1.late_bind(u);
+ }
+ catch (binding_type_exception const &e)
+ {
+ if ((e.target_type() != typeid(pure_virtual_base)) || (e.actual_type() != typeid(unrelated)))
+ {
+ osd_printf_error(
+ "Error testing delegate late bind type error %s -> %s (expected %s -> %s)\n",
+ e.actual_type().name(),
+ e.target_type().name(),
+ typeid(unrelated).name(),
+ typeid(pure_virtual_base).name());
+ }
+ ch = '+';
+ }
+ if ('+' != ch)
+ osd_printf_error("Error testing delegate late bind type error\n");
+
+ // test syntax for creating delegate with alternate late bind base
+ delegate<char (void const *&), pure_virtual_base> cb4(
+ [] (auto &o, void const *&p) { p = &o; return 'l'; },
+ static_cast<unrelated *>(nullptr));
+ try { cb1.late_bind(a); }
+ catch (binding_type_exception const &) { }
+}
+
+
+//-------------------------------------------------
+// validate_delegates_functoid - test delegate
+// functoid functionality
+//-------------------------------------------------
+
+void validate_delegates_functoid()
+{
+ using void_delegate = delegate<void (void const *&)>;
+ struct const_only
+ {
+ char operator()(void const *&p) const { return 'C'; }
+ };
+
+ struct const_or_not
+ {
+ char operator()(void const *&p) { return 'n'; }
+ char operator()(void const *&p) const { return 'c'; }
+ };
+
+ struct noncopyable
+ {
+ noncopyable() = default;
+ noncopyable(noncopyable const &) = delete;
+ noncopyable &operator=(noncopyable const &) = delete;
+
+ char operator()(void const *&p) { p = this; return '*'; }
+ };
+
+ noncopyable n;
+ char ch;
+ void const *addr = nullptr;
+
+ // test that const call operators are supported
+ test_delegate cb1{ const_only() };
+ if ('C' != cb1(addr))
+ osd_printf_error("Error testing delegate functoid dispatch\n");
+
+ // test that non-const call operators are preferred
+ cb1 = test_delegate{ const_or_not() };
+ if ('n' != cb1(addr))
+ osd_printf_error("Error testing delegate functoid dispatch\n");
+
+ // test that functoids are implicitly mutable
+ cb1 = test_delegate{ [a = &addr, c = '0'] (void const *&p) mutable { p = a; return c++; } };
+
+ addr = nullptr;
+ ch = cb1(addr);
+ if (('0' != ch) || (&addr != addr))
+ osd_printf_error("Error testing delegate functoid %c (expected 0)\n", ch);
+
+ addr = nullptr;
+ ch = cb1(addr);
+ if (('1' != ch) || (&addr != addr))
+ osd_printf_error("Error testing delegate functoid %c (expected 1)\n", ch);
+
+ // test that functoids survive copy construction
+ test_delegate cb2(cb1);
+
+ addr = nullptr;
+ ch = cb2(addr);
+ if (('2' != ch) || (&addr != addr))
+ osd_printf_error("Error testing delegate functoid %c (expected 2)\n", ch);
+
+ addr = nullptr;
+ ch = cb2(addr);
+ if (('3' != ch) || (&addr != addr))
+ osd_printf_error("Error testing delegate functoid %c (expected 3)\n", ch);
+
+ addr = nullptr;
+ ch = cb1(addr);
+ if (('2' != ch) || (&addr != addr))
+ osd_printf_error("Error testing delegate functoid %c (expected 2)\n", ch);
+
+ // test that functoids survive assignment
+ cb1 = cb2;
+
+ addr = nullptr;
+ ch = cb1(addr);
+ if (('4' != ch) || (&addr != addr))
+ osd_printf_error("Error testing delegate functoid %c (expected 4)\n", ch);
+
+ addr = nullptr;
+ ch = cb1(addr);
+ if (('5' != ch) || (&addr != addr))
+ osd_printf_error("Error testing delegate functoid %c (expected 5)\n", ch);
+
+ addr = nullptr;
+ ch = cb2(addr);
+ if (('4' != ch) || (&addr != addr))
+ osd_printf_error("Error testing delegate functoid %c (expected 4)\n", ch);
+
+ // test that std::ref can be used with non-copyable functoids
+ test_delegate cb3(std::ref(n));
+
+ addr = nullptr;
+ ch = cb3(addr);
+ if (('*' != ch) || (&n != addr))
+ osd_printf_error("Error testing delegate with functoid reference wrapper %p (expected %p)\n", addr, static_cast<void const *>(&n));
+
+ // test that std::ref survives copy construction and assignment
+ cb2 = cb3;
+ test_delegate cb4(cb3);
+
+ addr = nullptr;
+ ch = cb2(addr);
+ if (('*' != ch) || (&n != addr))
+ osd_printf_error("Error testing delegate with functoid reference wrapper %p (expected %p)\n", addr, static_cast<void const *>(&n));
+
+ addr = nullptr;
+ ch = cb4(addr);
+ if (('*' != ch) || (&n != addr))
+ osd_printf_error("Error testing delegate with functoid reference wrapper %p (expected %p)\n", addr, static_cast<void const *>(&n));
+
+ // test discarding return value for delegates returning void
+ void_delegate void_cb1{ [&cb1] (void const *&p) { p = &cb1; return 123; } };
+ void_delegate void_cb2{ std::ref(n) };
+
+ addr = nullptr;
+ void_cb1(addr);
+ if (&cb1 != addr)
+ osd_printf_error("Error testing delegate with functoid requiring adapter %p (expected %p)\n", addr, static_cast<void const *>(&cb1));
+
+ addr = nullptr;
+ void_cb2(addr);
+ if (&n != addr)
+ osd_printf_error("Error testing delegate with functoid requiring adapter %p (expected %p)\n", addr, static_cast<void const *>(&n));
+
+ // test that adaptor is generated after assignment
+ void_cb2 = void_cb1;
+
+ addr = nullptr;
+ void_cb2(addr);
+ if (&cb1 != addr)
+ osd_printf_error("Error testing delegate with functoid requiring adapter %p (expected %p)\n", addr, static_cast<void const *>(&cb1));
+}
+
+} // anonymous namespace
+
+
+
+//-------------------------------------------------
+// get_defstr_index - return the index of the
+// string assuming it is one of the default
+// strings
+//-------------------------------------------------
+
+inline int validity_checker::get_defstr_index(const char *string, bool suppress_error)
+{
+ // check for strings that should be DEF_STR
+ auto strindex = m_defstr_map.find(string);
+ if (!suppress_error && strindex != m_defstr_map.end() && string != ioport_string_from_index(strindex->second))
+ osd_printf_error("Must use DEF_STR( %s )\n", string);
+ return (strindex != m_defstr_map.end()) ? strindex->second : 0;
+}
+
+
+
+//-------------------------------------------------
+// validate_tag - ensure that the given tag
+// meets the general requirements
+//-------------------------------------------------
+
+void validity_checker::validate_tag(const char *tag)
+{
+ // some common names that are now deprecated
+ if (strcmp(tag, "main") == 0 || strcmp(tag, "audio") == 0 || strcmp(tag, "sound") == 0 || strcmp(tag, "left") == 0 || strcmp(tag, "right") == 0)
+ osd_printf_error("Invalid generic tag '%s' used\n", tag);
+
+ // scan for invalid characters
+ static char const *const validchars = "abcdefghijklmnopqrstuvwxyz0123456789_.:^$";
+ for (char const *p = tag; *p; ++p)
+ {
+ // only lower-case permitted
+ if (*p != tolower(u8(*p)))
+ {
+ osd_printf_error("Tag '%s' contains upper-case characters\n", tag);
+ break;
+ }
+ if (*p == ' ')
+ {
+ osd_printf_error("Tag '%s' contains spaces\n", tag);
+ break;
+ }
+ if (!strchr(validchars, *p))
+ {
+ osd_printf_error("Tag '%s' contains invalid character '%c'\n", tag, *p);
+ break;
+ }
+ }
+
+ // find the start of the final tag
+ const char *begin = strrchr(tag, ':');
+ if (begin == nullptr)
+ begin = tag;
+ else
+ begin += 1;
+
+ // 0-length = bad
+ if (*begin == 0)
+ osd_printf_error("Found 0-length tag\n");
+
+ // too short/too long = bad
+ if (strlen(begin) < MIN_TAG_LENGTH)
+ osd_printf_error("Tag '%s' is too short (must be at least %d characters)\n", tag, MIN_TAG_LENGTH);
+}
+
+
+
+//**************************************************************************
+// VALIDATION FUNCTIONS
+//**************************************************************************
+
+//-------------------------------------------------
+// validity_checker - constructor
+//-------------------------------------------------
+
+validity_checker::validity_checker(emu_options &options, bool quick)
+ : m_drivlist(options)
+ , m_errors(0)
+ , m_warnings(0)
+ , m_print_verbose(options.verbose())
+ , m_current_driver(nullptr)
+ , m_current_device(nullptr)
+ , m_current_ioport(nullptr)
+ , m_checking_card(false)
+ , m_quick(quick)
+{
+ // pre-populate the defstr map with all the default strings
+ for (int strnum = 1; strnum < INPUT_STRING_COUNT; strnum++)
+ {
+ const char *string = ioport_string_from_index(strnum);
+ if (string != nullptr)
+ m_defstr_map.insert(std::make_pair(string, strnum));
+ }
+}
+
+//-------------------------------------------------
+// validity_checker - destructor
+//-------------------------------------------------
+
+validity_checker::~validity_checker()
+{
+ validate_end();
+}
+
+//-------------------------------------------------
+// check_driver - check a single driver
+//-------------------------------------------------
+
+void validity_checker::check_driver(const game_driver &driver)
+{
+ // simply validate the one driver
+ validate_begin();
+ validate_one(driver);
+ validate_end();
+}
+
+
+//-------------------------------------------------
+// check_shared_source - check all drivers that
+// share the same source file as the given driver
+//-------------------------------------------------
+
+void validity_checker::check_shared_source(const game_driver &driver)
+{
+ // initialize
+ validate_begin();
+
+ // then iterate over all drivers and check the ones that share the same source file
+ m_drivlist.reset();
+ while (m_drivlist.next())
+ if (strcmp(driver.type.source(), m_drivlist.driver().type.source()) == 0)
+ validate_one(m_drivlist.driver());
+
+ // cleanup
+ validate_end();
+}
+
+
+//-------------------------------------------------
+// check_all_matching - check all drivers whose
+// names match the given string
+//-------------------------------------------------
+
+bool validity_checker::check_all_matching(const char *string)
+{
+ // start by checking core stuff
+ validate_begin();
+ validate_integer_semantics();
+ validate_inlines();
+ validate_rgb();
+ validate_delegates_mfp();
+ validate_delegates_latebind();
+ validate_delegates_functoid();
+
+ // if we had warnings or errors, output
+ if (m_errors > 0 || m_warnings > 0 || !m_verbose_text.empty())
+ {
+ output_via_delegate(OSD_OUTPUT_CHANNEL_ERROR, "Core: %d errors, %d warnings\n", m_errors, m_warnings);
+ if (m_errors > 0)
+ output_indented_errors(m_error_text, "Errors");
+ if (m_warnings > 0)
+ output_indented_errors(m_warning_text, "Warnings");
+ if (!m_verbose_text.empty())
+ output_indented_errors(m_verbose_text, "Messages");
+ output_via_delegate(OSD_OUTPUT_CHANNEL_ERROR, "\n");
+ }
+
+ // then iterate over all drivers and check them
+ m_drivlist.reset();
+ bool validated_any = false;
+ while (m_drivlist.next())
+ {
+ if (driver_list::matches(string, m_drivlist.driver().name))
+ {
+ validate_one(m_drivlist.driver());
+ validated_any = true;
+ }
+ }
+
+ // validate devices
+ if (!string)
+ validate_device_types();
+
+ // cleanup
+ validate_end();
+
+ // if we failed to match anything, it
+ if (string && !validated_any)
+ throw emu_fatalerror(EMU_ERR_NO_SUCH_SYSTEM, "No matching systems found for '%s'", string);
+
+ return !(m_errors > 0 || m_warnings > 0);
+}
+
+
+//-------------------------------------------------
+// validate_begin - prepare for validation by
+// taking over the output callbacks and resetting
+// our internal state
+//-------------------------------------------------
+
+void validity_checker::validate_begin()
+{
+ // take over error and warning outputs
+ osd_output::push(this);
+
+ // reset all our maps
+ m_names_map.clear();
+ m_descriptions_map.clear();
+ m_roms_map.clear();
+ m_defstr_map.clear();
+ m_region_map.clear();
+ m_ioport_set.clear();
+
+ // reset internal state
+ m_errors = 0;
+ m_warnings = 0;
+ m_already_checked.clear();
+}
+
+
+//-------------------------------------------------
+// validate_end - restore output callbacks and
+// clean up
+//-------------------------------------------------
+
+void validity_checker::validate_end()
+{
+ // restore the original output callbacks
+ osd_output::pop(this);
+}
+
+
+//-------------------------------------------------
+// validate_drivers - master validity checker
+//-------------------------------------------------
+
+void validity_checker::validate_one(const game_driver &driver)
+{
+ // help verbose validation detect configuration-related crashes
+ if (m_print_verbose)
+ output_via_delegate(OSD_OUTPUT_CHANNEL_ERROR, "Validating driver %s (%s)...\n", driver.name, core_filename_extract_base(driver.type.source()));
+
+ // set the current driver
+ m_current_driver = &driver;
+ m_current_device = nullptr;
+ m_current_ioport = nullptr;
+ m_region_map.clear();
+ m_ioport_set.clear();
+ m_checking_card = false;
+
+ // reset error/warning state
+ int start_errors = m_errors;
+ int start_warnings = m_warnings;
+ m_error_text.clear();
+ m_warning_text.clear();
+ m_verbose_text.clear();
+
+ // wrap in try/catch to catch fatalerrors
+ try
+ {
+ machine_config config(driver, m_blank_options);
+ validate_driver(config.root_device());
+ validate_roms(config.root_device());
+ validate_inputs(config.root_device());
+ validate_devices(config);
+ }
+ catch (emu_fatalerror const &err)
+ {
+ osd_printf_error("Fatal error %s", err.what());
+ }
+
+ // if we had warnings or errors, output
+ if (m_errors > start_errors || m_warnings > start_warnings || !m_verbose_text.empty())
+ {
+ if (!m_print_verbose)
+ output_via_delegate(OSD_OUTPUT_CHANNEL_ERROR, "Driver %s (file %s): ", driver.name, core_filename_extract_base(driver.type.source()));
+ output_via_delegate(OSD_OUTPUT_CHANNEL_ERROR, "%d errors, %d warnings\n", m_errors - start_errors, m_warnings - start_warnings);
+ if (m_errors > start_errors)
+ output_indented_errors(m_error_text, "Errors");
+ if (m_warnings > start_warnings)
+ output_indented_errors(m_warning_text, "Warnings");
+ if (!m_verbose_text.empty())
+ output_indented_errors(m_verbose_text, "Messages");
+ output_via_delegate(OSD_OUTPUT_CHANNEL_ERROR, "\n");
+ }
+
+ // reset the driver/device
+ m_current_driver = nullptr;
+ m_current_device = nullptr;
+ m_current_ioport = nullptr;
+ m_region_map.clear();
+ m_ioport_set.clear();
+ m_checking_card = false;
+}
+
+
+//-------------------------------------------------
+// validate_driver - validate basic driver
+// information
+//-------------------------------------------------
+
+void validity_checker::validate_driver(device_t &root)
+{
+ // check for duplicate names
+ if (!m_names_map.insert(std::make_pair(m_current_driver->name, m_current_driver)).second)
+ {
+ const game_driver *match = m_names_map.find(m_current_driver->name)->second;
+ osd_printf_error("Driver name is a duplicate of %s(%s)\n", core_filename_extract_base(match->type.source()), match->name);
+ }
+
+ // check for duplicate descriptions
+ if (!m_descriptions_map.insert(std::make_pair(m_current_driver->type.fullname(), m_current_driver)).second)
+ {
+ const game_driver *match = m_descriptions_map.find(m_current_driver->type.fullname())->second;
+ osd_printf_error("Driver description is a duplicate of %s(%s)\n", core_filename_extract_base(match->type.source()), match->name);
+ }
+
+ // determine if we are a clone
+ bool is_clone = (strcmp(m_current_driver->parent, "0") != 0);
+ int clone_of = driver_list::clone(*m_current_driver);
+ if (clone_of != -1 && (driver_list::driver(clone_of).flags & machine_flags::IS_BIOS_ROOT))
+ is_clone = false;
+
+ // if we have at least 100 drivers, validate the clone
+ // (100 is arbitrary, but tries to avoid tiny.mak dependencies)
+ if (driver_list::total() > 100 && clone_of == -1 && is_clone)
+ osd_printf_error("Driver is a clone of nonexistent driver %s\n", m_current_driver->parent);
+
+ // look for recursive cloning
+ if (clone_of != -1 && &driver_list::driver(clone_of) == m_current_driver)
+ osd_printf_error("Driver is a clone of itself\n");
+
+ // look for clones that are too deep
+ if (clone_of != -1 && (clone_of = driver_list::non_bios_clone(clone_of)) != -1)
+ osd_printf_error("Driver is a clone of a clone\n");
+
+ // look for drivers specifying a parent ROM device type
+ if (root.type().parent_rom_device_type())
+ osd_printf_error("Driver has parent ROM device type '%s'\n", root.type().parent_rom_device_type()->shortname());
+
+ // make sure the driver name is not too long
+ if (!is_clone && strlen(m_current_driver->name) > 16)
+ osd_printf_error("Parent driver name must be 16 characters or less\n");
+ if (is_clone && strlen(m_current_driver->name) > 16)
+ osd_printf_error("Clone driver name must be 16 characters or less\n");
+
+ // make sure the driver name doesn't contain invalid characters
+ for (const char *s = m_current_driver->name; *s != 0; s++)
+ if (((*s < '0') || (*s > '9')) && ((*s < 'a') || (*s > 'z')) && (*s != '_'))
+ {
+ osd_printf_error("Driver name contains invalid characters\n");
+ break;
+ }
+
+ // make sure the year is only digits, '?' or '+'
+ for (const char *s = m_current_driver->year; *s != 0; s++)
+ if (!isdigit(u8(*s)) && *s != '?' && *s != '+')
+ {
+ osd_printf_error("Driver has an invalid year '%s'\n", m_current_driver->year);
+ break;
+ }
+
+ // normalize driver->compatible_with
+ const char *compatible_with = m_current_driver->compatible_with;
+ if (compatible_with != nullptr && strcmp(compatible_with, "0") == 0)
+ compatible_with = nullptr;
+
+ // check for this driver being compatible with a nonexistent driver
+ if (compatible_with != nullptr && driver_list::find(m_current_driver->compatible_with) == -1)
+ osd_printf_error("Driver is listed as compatible with nonexistent driver %s\n", m_current_driver->compatible_with);
+
+ // check for clone_of and compatible_with being specified at the same time
+ if (driver_list::clone(*m_current_driver) != -1 && compatible_with != nullptr)
+ osd_printf_error("Driver cannot be both a clone and listed as compatible with another system\n");
+
+ // find any recursive dependencies on the current driver
+ for (int other_drv = driver_list::compatible_with(*m_current_driver); other_drv != -1; other_drv = driver_list::compatible_with(other_drv))
+ if (m_current_driver == &driver_list::driver(other_drv))
+ {
+ osd_printf_error("Driver is recursively compatible with itself\n");
+ break;
+ }
+
+ // make sure sound-less drivers are flagged
+ device_t::feature_type const unemulated(m_current_driver->type.unemulated_features());
+ device_t::feature_type const imperfect(m_current_driver->type.imperfect_features());
+ if (!(m_current_driver->flags & (machine_flags::IS_BIOS_ROOT | machine_flags::NO_SOUND_HW)) && !(unemulated & device_t::feature::SOUND))
+ {
+ sound_interface_enumerator iter(root);
+ if (!iter.first())
+ osd_printf_error("Driver is missing MACHINE_NO_SOUND or MACHINE_NO_SOUND_HW flag\n");
+ }
+
+ // catch invalid flag combinations
+ if (unemulated & ~device_t::feature::ALL)
+ osd_printf_error("Driver has invalid unemulated feature flags (0x%08X)\n", util::underlying_value(unemulated & ~device_t::feature::ALL));
+ if (imperfect & ~device_t::feature::ALL)
+ osd_printf_error("Driver has invalid imperfect feature flags (0x%08X)\n", util::underlying_value(imperfect & ~device_t::feature::ALL));
+ if (unemulated & imperfect)
+ osd_printf_error("Driver cannot have features that are both unemulated and imperfect (0x%08X)\n", util::underlying_value(unemulated & imperfect));
+ if ((m_current_driver->flags & machine_flags::NO_SOUND_HW) && ((unemulated | imperfect) & device_t::feature::SOUND))
+ osd_printf_error("Machine without sound hardware cannot have unemulated/imperfect sound\n");
+}
+
+
+//-------------------------------------------------
+// validate_roms - validate ROM definitions
+//-------------------------------------------------
+
+void validity_checker::validate_roms(device_t &root)
+{
+ // iterate, starting with the driver's ROMs and continuing with device ROMs
+ for (device_t &device : device_enumerator(root))
+ {
+ // track the current device
+ m_current_device = &device;
+
+ // scan the ROM entries for this device
+ char const *last_region_name = "???";
+ char const *last_name = "???";
+ u32 current_length = 0;
+ int items_since_region = 1;
+ int last_bios = 0, max_bios = 0;
+ int total_files = 0;
+ std::unordered_map<std::string, int> bios_names;
+ std::unordered_map<std::string, std::string> bios_descs;
+ char const *defbios = nullptr;
+ for (tiny_rom_entry const *romp = device.rom_region(); romp && !ROMENTRY_ISEND(romp); ++romp)
+ {
+ if (ROMENTRY_ISREGION(romp)) // if this is a region, make sure it's valid, and record the length
+ {
+ // if we haven't seen any items since the last region, print a warning
+ if (items_since_region == 0)
+ osd_printf_warning("Empty ROM region '%s' (warning)\n", last_region_name);
+
+ // reset our region tracking states
+ char const *const basetag = romp->name;
+ items_since_region = (ROMREGION_ISERASE(romp) || ROMREGION_ISDISKDATA(romp)) ? 1 : 0;
+ last_region_name = basetag;
+
+ // check for a valid tag
+ if (!basetag)
+ {
+ osd_printf_error("ROM_REGION tag with nullptr name\n");
+ continue;
+ }
+
+ // validate the base tag
+ validate_tag(basetag);
+
+ // generate the full tag
+ std::string const fulltag = device.subtag(romp->name);
+
+ // attempt to add it to the map, reporting duplicates as errors
+ current_length = ROMREGION_GETLENGTH(romp);
+ if (!m_region_map.emplace(fulltag, current_length).second)
+ osd_printf_error("Multiple ROM_REGIONs with the same tag '%s' defined\n", fulltag);
+ }
+ else if (ROMENTRY_ISSYSTEM_BIOS(romp)) // If this is a system bios, make sure it is using the next available bios number
+ {
+ int const bios_flags = ROM_GETBIOSFLAGS(romp);
+ char const *const biosname = romp->name;
+ if (bios_flags != last_bios + 1)
+ osd_printf_error("Non-sequential BIOS %s (specified as %d, expected to be %d)\n", biosname, bios_flags - 1, last_bios);
+ last_bios = bios_flags;
+
+ // validate the name
+ if (strlen(biosname) > 16)
+ osd_printf_error("BIOS name %s exceeds maximum 16 characters\n", biosname);
+ for (char const *s = biosname; *s; ++s)
+ {
+ if (((*s < '0') || (*s > '9')) && ((*s < 'a') || (*s > 'z')) && (*s != '.') && (*s != '_') && (*s != '-'))
+ {
+ osd_printf_error("BIOS name %s contains invalid characters\n", biosname);
+ break;
+ }
+ }
+
+ // check for duplicate names/descriptions
+ auto const nameins = bios_names.emplace(biosname, bios_flags);
+ if (!nameins.second)
+ osd_printf_error("Duplicate BIOS name %s specified (%d and %d)\n", biosname, nameins.first->second, bios_flags - 1);
+ auto const descins = bios_descs.emplace(romp->hashdata, biosname);
+ if (!descins.second)
+ osd_printf_error("BIOS %s has duplicate description '%s' (was %s)\n", biosname, romp->hashdata, descins.first->second);
+ }
+ else if (ROMENTRY_ISDEFAULT_BIOS(romp)) // if this is a default BIOS setting, remember it so it to check at the end
+ {
+ defbios = romp->name;
+ }
+ else if (ROMENTRY_ISFILE(romp)) // if this is a file, make sure it is properly formatted
+ {
+ // track the last filename we found
+ last_name = romp->name;
+ total_files++;
+ max_bios = std::max<int>(max_bios, ROM_GETBIOSFLAGS(romp));
+
+ // validate the name
+ if (strlen(last_name) > 127)
+ osd_printf_error("ROM label %s exceeds maximum 127 characters\n", last_name);
+ for (char const *s = last_name; *s; ++s)
+ {
+ if (((*s < '0') || (*s > '9')) && ((*s < 'a') || (*s > 'z')) && (*s != ' ') && (*s != '@') && (*s != '.') && (*s != ',') && (*s != '_') && (*s != '-') && (*s != '+') && (*s != '='))
+ {
+ osd_printf_error("ROM label %s contains invalid characters\n", last_name);
+ break;
+ }
+ }
+
+ // make sure the hash is valid
+ util::hash_collection hashes;
+ if (!hashes.from_internal_string(romp->hashdata))
+ osd_printf_error("ROM '%s' has an invalid hash string '%s'\n", last_name, romp->hashdata);
+ }
+
+ // for any non-region ending entries, make sure they don't extend past the end
+ if (!ROMENTRY_ISREGIONEND(romp) && current_length > 0 && !ROMENTRY_ISIGNORE(romp)) // HBMAME
+ {
+ items_since_region++;
+ if (!ROMENTRY_ISIGNORE(romp) && (ROM_GETOFFSET(romp) + ROM_GETLENGTH(romp) > current_length))
+ osd_printf_error("ROM '%s' extends past the defined memory region\n", last_name);
+ }
+ }
+
+ // if we haven't seen any items since the last region, print a warning
+ if (items_since_region == 0)
+ osd_printf_warning("Empty ROM region '%s' (warning)\n", last_region_name);
+
+ // check that default BIOS exists
+ if (defbios && (bios_names.find(defbios) == bios_names.end()))
+ osd_printf_error("Default BIOS '%s' not found\n", defbios);
+ if (!device.get_default_bios_tag().empty() && (bios_names.find(device.get_default_bios_tag()) == bios_names.end()))
+ osd_printf_error("Configured BIOS '%s' not found\n", device.get_default_bios_tag());
+
+ // check that there aren't ROMs for a non-existent BIOS option
+ if (max_bios > last_bios)
+ osd_printf_error("BIOS %d set on file is higher than maximum system BIOS number %d\n", max_bios - 1, last_bios - 1);
+
+ // final check for empty regions
+ if (items_since_region == 0)
+ osd_printf_warning("Empty ROM region '%s' (warning)\n", last_region_name);
+
+ // reset the current device
+ m_current_device = nullptr;
+ }
+}
+
+
+//-------------------------------------------------
+// validate_analog_input_field - validate an
+// analog input field
+//-------------------------------------------------
+
+void validity_checker::validate_analog_input_field(const ioport_field &field)
+{
+ // analog ports must have a valid sensitivity
+ if (field.sensitivity() == 0)
+ osd_printf_error("Analog port with zero sensitivity\n");
+
+ // check that the default falls in the bitmask range
+ if (field.defvalue() & ~field.mask())
+ osd_printf_error("Analog port with a default value (%X) out of the bitmask range (%X)\n", field.defvalue(), field.mask());
+
+ // tests for positional devices
+ if (field.type() == IPT_POSITIONAL || field.type() == IPT_POSITIONAL_V)
+ {
+ int shift;
+ for (shift = 0; shift <= 31 && (~field.mask() & (1 << shift)) != 0; shift++) { }
+
+ // convert the positional max value to be in the bitmask for testing
+ //s32 analog_max = field.maxval();
+ //analog_max = (analog_max - 1) << shift;
+
+ // positional port size must fit in bits used
+ if ((field.mask() >> shift) + 1 < field.maxval())
+ osd_printf_error("Analog port with a positional port size bigger then the mask size\n");
+ }
+
+ // tests for absolute devices
+ else if (field.type() > IPT_ANALOG_ABSOLUTE_FIRST && field.type() < IPT_ANALOG_ABSOLUTE_LAST)
+ {
+ // adjust for signed values
+ s32 default_value = field.defvalue();
+ s32 analog_min = field.minval();
+ s32 analog_max = field.maxval();
+ if (analog_min > analog_max)
+ {
+ analog_min = -analog_min;
+ if (default_value > analog_max)
+ default_value = -default_value;
+ }
+
+ // check that the default falls in the MINMAX range
+ if (default_value < analog_min || default_value > analog_max)
+ osd_printf_error("Analog port with a default value (%X) out of PORT_MINMAX range (%X-%X)\n", field.defvalue(), field.minval(), field.maxval());
+
+ // check that the MINMAX falls in the bitmask range
+ // we use the unadjusted min for testing
+ if (field.minval() & ~field.mask() || analog_max & ~field.mask())
+ osd_printf_error("Analog port with a PORT_MINMAX (%X-%X) value out of the bitmask range (%X)\n", field.minval(), field.maxval(), field.mask());
+
+ // absolute analog ports do not use PORT_RESET
+ if (field.analog_reset())
+ osd_printf_error("Absolute analog port using PORT_RESET\n");
+
+ // absolute analog ports do not use PORT_WRAPS
+ if (field.analog_wraps())
+ osd_printf_error("Absolute analog port using PORT_WRAPS\n");
+ }
+
+ // tests for non IPT_POSITIONAL relative devices
+ else
+ {
+ // relative devices do not use PORT_MINMAX
+ if (field.minval() != 0 || field.maxval() != field.mask())
+ osd_printf_error("Relative port using PORT_MINMAX\n");
+
+ // relative devices do not use a default value
+ // the counter is at 0 on power up
+ if (field.defvalue() != 0)
+ osd_printf_error("Relative port using non-0 default value\n");
+
+ // relative analog ports do not use PORT_WRAPS
+ if (field.analog_wraps())
+ osd_printf_error("Absolute analog port using PORT_WRAPS\n");
+ }
+}
+
+
+//-------------------------------------------------
+// validate_dip_settings - validate a DIP switch
+// setting
+//-------------------------------------------------
+
+void validity_checker::validate_dip_settings(const ioport_field &field)
+{
+ const char *demo_sounds = ioport_string_from_index(INPUT_STRING_Demo_Sounds);
+ const char *flipscreen = ioport_string_from_index(INPUT_STRING_Flip_Screen);
+ u8 coin_list[__input_string_coinage_end + 1 - __input_string_coinage_start] = { 0 };
+ bool coin_error = false;
+
+ // iterate through the settings
+ for (auto setting = field.settings().begin(); field.settings().end() != setting; ++setting)
+ {
+ // note any coinage strings
+ int strindex = get_defstr_index(setting->name());
+ if (strindex >= __input_string_coinage_start && strindex <= __input_string_coinage_end)
+ coin_list[strindex - __input_string_coinage_start] = 1;
+
+ // make sure demo sounds default to on
+ if (field.name() == demo_sounds && strindex == INPUT_STRING_On && field.defvalue() != setting->value())
+ osd_printf_error("Demo Sounds must default to On\n");
+
+ // check for bad demo sounds options
+ if (field.name() == demo_sounds && (strindex == INPUT_STRING_Yes || strindex == INPUT_STRING_No))
+ osd_printf_error("Demo Sounds option must be Off/On, not %s\n", setting->name());
+
+ // check for bad flip screen options
+ if (field.name() == flipscreen && (strindex == INPUT_STRING_Yes || strindex == INPUT_STRING_No))
+ osd_printf_error("Flip Screen option must be Off/On, not %s\n", setting->name());
+
+ // if we have a neighbor, compare ourselves to him
+ auto const nextsetting = std::next(setting);
+ if (field.settings().end() != nextsetting)
+ {
+ // check for inverted off/on DIP switch order
+ int next_strindex = get_defstr_index(nextsetting->name(), true);
+ if (strindex == INPUT_STRING_On && next_strindex == INPUT_STRING_Off)
+ osd_printf_error("%s option must have Off/On options in the order: Off, On\n", field.name());
+
+ // check for inverted yes/no DIP switch order
+ else if (strindex == INPUT_STRING_Yes && next_strindex == INPUT_STRING_No)
+ osd_printf_error("%s option must have Yes/No options in the order: No, Yes\n", field.name());
+
+ // check for inverted upright/cocktail DIP switch order
+ else if (strindex == INPUT_STRING_Cocktail && next_strindex == INPUT_STRING_Upright)
+ osd_printf_error("%s option must have Upright/Cocktail options in the order: Upright, Cocktail\n", field.name());
+
+ // check for proper coin ordering
+ else if (strindex >= __input_string_coinage_start && strindex <= __input_string_coinage_end && next_strindex >= __input_string_coinage_start && next_strindex <= __input_string_coinage_end &&
+ strindex >= next_strindex && setting->condition() == nextsetting->condition())
+ {
+ osd_printf_error("%s option has unsorted coinage %s > %s\n", field.name(), setting->name(), nextsetting->name());
+ coin_error = true;
+ }
+ }
+ }
+
+ // if we have a coin error, demonstrate the correct way
+ if (coin_error)
+ {
+ output_via_delegate(OSD_OUTPUT_CHANNEL_ERROR, " Note proper coin sort order should be:\n");
+ for (int entry = 0; entry < std::size(coin_list); entry++)
+ if (coin_list[entry])
+ output_via_delegate(OSD_OUTPUT_CHANNEL_ERROR, " %s\n", ioport_string_from_index(__input_string_coinage_start + entry));
+ }
+}
+
+
+//-------------------------------------------------
+// validate_condition - validate a condition
+// stored within an ioport field or setting
+//-------------------------------------------------
+
+void validity_checker::validate_condition(const ioport_condition &condition, device_t &device)
+{
+ // resolve the tag, then find a matching port
+ if (m_ioport_set.find(device.subtag(condition.tag())) == m_ioport_set.end())
+ osd_printf_error("Condition referencing non-existent ioport tag '%s'\n", condition.tag());
+}
+
+
+//-------------------------------------------------
+// validate_inputs - validate input configuration
+//-------------------------------------------------
+
+void validity_checker::validate_inputs(device_t &root)
+{
+ // iterate over devices
+ for (device_t &device : device_enumerator(root))
+ {
+ // see if this device has ports; if not continue
+ if (device.input_ports() == nullptr)
+ continue;
+
+ // track the current device
+ m_current_device = &device;
+
+ // allocate the input ports
+ ioport_list portlist;
+ std::string errorbuf;
+ portlist.append(device, errorbuf);
+
+ // report any errors during construction
+ if (!errorbuf.empty())
+ osd_printf_error("I/O port error during construction:\n%s\n", errorbuf);
+
+ // do a first pass over ports to add their names and find duplicates
+ for (auto &port : portlist)
+ if (!m_ioport_set.insert(port.second->tag()).second)
+ osd_printf_error("Multiple I/O ports with the same tag '%s' defined\n", port.second->tag());
+
+ // iterate over ports
+ for (auto &port : portlist)
+ {
+ m_current_ioport = port.second->tag();
+
+ // scan for invalid characters
+ static char const *const validchars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.:^$";
+ for (char const *p = m_current_ioport; *p; ++p)
+ {
+ if (*p == ' ')
+ {
+ osd_printf_error("Tag '%s' contains spaces\n", m_current_ioport);
+ break;
+ }
+ if (!strchr(validchars, *p))
+ {
+ osd_printf_error("Tag '%s' contains invalid character '%c'\n", m_current_ioport, *p);
+ break;
+ }
+ }
+
+ // iterate through the fields on this port
+ for (ioport_field const &field : port.second->fields())
+ {
+ // verify analog inputs
+ if (field.is_analog())
+ validate_analog_input_field(field);
+
+ // look for invalid (0) types which should be mapped to IPT_OTHER
+ if (field.type() == IPT_INVALID)
+ osd_printf_error("Field has an invalid type (0); use IPT_OTHER instead\n");
+
+ if (field.type() == IPT_SPECIAL)
+ osd_printf_error("Field has an invalid type IPT_SPECIAL\n");
+
+ // verify dip switches
+ if (field.type() == IPT_DIPSWITCH)
+ {
+ // dip switch fields must have a specific name
+ if (field.specific_name() == nullptr)
+ osd_printf_error("DIP switch has no specific name\n");
+
+ // verify the settings list
+ validate_dip_settings(field);
+ }
+
+ // verify config settings
+ if (field.type() == IPT_CONFIG)
+ {
+ // config fields must have a specific name
+ if (field.specific_name() == nullptr)
+ osd_printf_error("Config switch has no specific name\n");
+ }
+
+ // verify names
+ const char *name = field.specific_name();
+ if (name != nullptr)
+ {
+ // check for empty string
+ if (name[0] == 0)
+ osd_printf_error("Field name is an empty string\n");
+
+ // check for trailing spaces
+ if (name[0] != 0 && name[strlen(name) - 1] == ' ')
+ osd_printf_error("Field '%s' has trailing spaces\n", name);
+
+ // check for invalid UTF-8
+ if (!utf8_is_valid_string(name))
+ osd_printf_error("Field '%s' has invalid characters\n", name);
+
+ // look up the string and print an error if default strings are not used
+ /*strindex =get_defstr_index(defstr_map, name, driver, &error);*/
+ }
+
+ // verify conditions on the field
+ if (!field.condition().none())
+ validate_condition(field.condition(), device);
+
+ // verify conditions on the settings
+ for (ioport_setting const &setting : field.settings())
+ if (!setting.condition().none())
+ validate_condition(setting.condition(), device);
+
+ // verify natural keyboard codes
+ for (int which = 0; which < 1 << (UCHAR_SHIFT_END - UCHAR_SHIFT_BEGIN + 1); which++)
+ {
+ std::vector<char32_t> codes = field.keyboard_codes(which);
+ for (char32_t code : codes)
+ {
+ if (!uchar_isvalid(code))
+ {
+ osd_printf_error("Field '%s' has non-character U+%04X in PORT_CHAR(%d)\n",
+ name,
+ (unsigned)code,
+ (int)code);
+ }
+ }
+ }
+ }
+
+ // done with this port
+ m_current_ioport = nullptr;
+ }
+
+ // done with this device
+ m_current_device = nullptr;
+ }
+}
+
+
+//-------------------------------------------------
+// validate_devices - run per-device validity
+// checks
+//-------------------------------------------------
+
+void validity_checker::validate_devices(machine_config &config)
+{
+ std::unordered_set<std::string> device_map;
+
+ for (device_t &device : device_enumerator(config.root_device()))
+ {
+ // track the current device
+ m_current_device = &device;
+
+ // validate auto-finders
+ device.findit(this);
+
+ // validate the device tag
+ validate_tag(device.basetag());
+
+ // look for duplicates
+ bool duplicate = !device_map.insert(device.tag()).second;
+ if (duplicate)
+ osd_printf_error("Multiple devices with the same tag defined\n");
+
+ // check for device-specific validity check
+ device.validity_check(*this);
+
+ // done with this device
+ m_current_device = nullptr;
+
+ // if it's a slot, iterate over possible cards (don't recurse, or you'll stack infinite tee connectors)
+ device_slot_interface *const slot = dynamic_cast<device_slot_interface *>(&device);
+ if (slot && !slot->fixed() && !duplicate)
+ {
+ for (auto &option : slot->option_list())
+ {
+ // the default option is already instantiated here, so don't try adding it again
+ if (slot->default_option() != nullptr && option.first == slot->default_option())
+ continue;
+
+ m_checking_card = true;
+ device_t *card;
+ {
+ machine_config::token const tok(config.begin_configuration(slot->device()));
+ card = config.device_add(option.second->name(), option.second->devtype(), option.second->clock());
+
+ const char *const def_bios = option.second->default_bios();
+ if (def_bios)
+ card->set_default_bios_tag(def_bios);
+ auto additions = option.second->machine_config();
+ if (additions)
+ additions(card);
+ }
+
+ for (device_slot_interface &subslot : slot_interface_enumerator(*card))
+ {
+ if (subslot.fixed())
+ {
+ // TODO: make this self-contained so it can apply itself
+ device_slot_interface::slot_option const *suboption = subslot.option(subslot.default_option());
+ if (suboption)
+ {
+ machine_config::token const tok(config.begin_configuration(subslot.device()));
+ device_t *const sub_card = config.device_add(suboption->name(), suboption->devtype(), suboption->clock());
+ const char *const sub_bios = suboption->default_bios();
+ if (sub_bios)
+ sub_card->set_default_bios_tag(sub_bios);
+ auto sub_additions = suboption->machine_config();
+ if (sub_additions)
+ sub_additions(sub_card);
+ }
+ }
+ }
+
+ for (device_t &card_dev : device_enumerator(*card))
+ card_dev.config_complete();
+ validate_roms(*card);
+
+ for (device_t &card_dev : device_enumerator(*card))
+ {
+ m_current_device = &card_dev;
+ card_dev.findit(this);
+ validate_tag(card_dev.basetag());
+ card_dev.validity_check(*this);
+ m_current_device = nullptr;
+ }
+
+ machine_config::token const tok(config.begin_configuration(slot->device()));
+ config.device_remove(option.second->name());
+ m_checking_card = false;
+ }
+ }
+ }
+}
+
+
+//-------------------------------------------------
+// validate_devices_types - check validity of
+// registered device types
+//-------------------------------------------------
+
+void validity_checker::validate_device_types()
+{
+ // reset error/warning state
+ int start_errors = m_errors;
+ int start_warnings = m_warnings;
+ m_error_text.clear();
+ m_warning_text.clear();
+ m_verbose_text.clear();
+
+ std::unordered_map<std::string, std::add_pointer_t<device_type> > device_name_map, device_shortname_map;
+ machine_config config(GAME_NAME(___empty), m_drivlist.options());
+ machine_config::token const tok(config.begin_configuration(config.root_device()));
+ for (device_type type : registered_device_types)
+ {
+ device_t *const dev = config.device_add(type.shortname(), type, 0);
+
+ char const *name((dev->shortname() && *dev->shortname()) ? dev->shortname() : type.type().name());
+ std::string const description((dev->source() && *dev->source()) ? util::string_format("%s(%s)", core_filename_extract_base(dev->source()), name) : name);
+
+ if (m_print_verbose)
+ output_via_delegate(OSD_OUTPUT_CHANNEL_ERROR, "Validating device %s...\n", description);
+
+ // ensure shortname exists
+ if (!dev->shortname() || !*dev->shortname())
+ {
+ osd_printf_error("Device %s does not have short name defined\n", description);
+ }
+ else
+ {
+ // make sure the device name is not too long
+ if (strlen(dev->shortname()) > 32)
+ osd_printf_error("Device short name must be 32 characters or less\n");
+
+ // check for invalid characters in shortname
+ for (char const *s = dev->shortname(); *s; ++s)
+ {
+ if (((*s < '0') || (*s > '9')) && ((*s < 'a') || (*s > 'z')) && (*s != '_'))
+ {
+ osd_printf_error("Device %s short name contains invalid characters\n", description);
+ break;
+ }
+ }
+
+ // check for name conflicts
+ std::string tmpname(dev->shortname());
+ game_driver_map::const_iterator const drvname(m_names_map.find(tmpname));
+ auto const devname(device_shortname_map.emplace(std::move(tmpname), &type));
+ if (m_names_map.end() != drvname)
+ {
+ game_driver const &dup(*drvname->second);
+ osd_printf_error("Device %s short name is a duplicate of %s(%s)\n", description, core_filename_extract_base(dup.type.source()), dup.name);
+ }
+ else if (!devname.second)
+ {
+ device_t *const dup = config.device_add("_dup", *devname.first->second, 0);
+ osd_printf_error("Device %s short name is a duplicate of %s(%s)\n", description, core_filename_extract_base(dup->source()), dup->shortname());
+ config.device_remove("_dup");
+ }
+ }
+
+ // ensure name exists
+ if (!dev->name() || !*dev->name())
+ {
+ osd_printf_error("Device %s does not have name defined\n", description);
+ }
+ else
+ {
+ // check for description conflicts
+ std::string tmpdesc(dev->name());
+ game_driver_map::const_iterator const drvdesc(m_descriptions_map.find(tmpdesc));
+ auto const devdesc(device_name_map.emplace(std::move(tmpdesc), &type));
+ if (m_descriptions_map.end() != drvdesc)
+ {
+ game_driver const &dup(*drvdesc->second);
+ osd_printf_error("Device %s name '%s' is a duplicate of %s(%s)\n", description, dev->name(), core_filename_extract_base(dup.type.source()), dup.name);
+ }
+ else if (!devdesc.second)
+ {
+ device_t *const dup = config.device_add("_dup", *devdesc.first->second, 0);
+ osd_printf_error("Device %s name '%s' is a duplicate of %s(%s)\n", description, dev->name(), core_filename_extract_base(dup->source()), dup->shortname());
+ config.device_remove("_dup");
+ }
+ }
+
+ // ensure source exists
+ if (!dev->source() || !*dev->source())
+ osd_printf_error("Device %s does not have source defined\n", description);
+
+ // check that reported type matches supplied type
+ if (dev->type().type() != type.type())
+ osd_printf_error("Device %s reports type '%s' (created with '%s')\n", description, dev->type().type().name(), type.type().name());
+
+ // catch invalid flag combinations
+ device_t::feature_type const unemulated(dev->type().unemulated_features());
+ device_t::feature_type const imperfect(dev->type().imperfect_features());
+ if (unemulated & ~device_t::feature::ALL)
+ osd_printf_error("Device has invalid unemulated feature flags (0x%08X)\n", util::underlying_value(unemulated & ~device_t::feature::ALL));
+ if (imperfect & ~device_t::feature::ALL)
+ osd_printf_error("Device has invalid imperfect feature flags (0x%08X)\n", util::underlying_value(imperfect & ~device_t::feature::ALL));
+ if (unemulated & imperfect)
+ osd_printf_error("Device cannot have features that are both unemulated and imperfect (0x%08X)\n", util::underlying_value(unemulated & imperfect));
+
+ // check that parents are only ever one generation deep
+ auto const parent(dev->type().parent_rom_device_type());
+ if (parent)
+ {
+ auto const grandparent(parent->parent_rom_device_type());
+ if ((dev->type() == *parent) || !strcmp(parent->shortname(), name))
+ osd_printf_error("Device has parent ROM set that identical to its type\n");
+ if (grandparent)
+ osd_printf_error("Device has parent ROM set '%s' which has parent ROM set '%s'\n", parent->shortname(), grandparent->shortname());
+ }
+
+ // give devices some of the same scrutiny that drivers get - necessary for cards not default for any slots
+ validate_roms(*dev);
+ validate_inputs(*dev);
+
+ // reset the device
+ m_current_device = nullptr;
+ m_current_ioport = nullptr;
+ m_region_map.clear();
+ m_ioport_set.clear();
+
+ // remove the device in preparation for re-using the machine configuration
+ config.device_remove(type.shortname());
+ }
+
+ // if we had warnings or errors, output
+ if (m_errors > start_errors || m_warnings > start_warnings || !m_verbose_text.empty())
+ {
+ output_via_delegate(OSD_OUTPUT_CHANNEL_ERROR, "%d errors, %d warnings\n", m_errors - start_errors, m_warnings - start_warnings);
+ if (m_errors > start_errors)
+ output_indented_errors(m_error_text, "Errors");
+ if (m_warnings > start_warnings)
+ output_indented_errors(m_warning_text, "Warnings");
+ if (!m_verbose_text.empty())
+ output_indented_errors(m_verbose_text, "Messages");
+ output_via_delegate(OSD_OUTPUT_CHANNEL_ERROR, "\n");
+ }
+}
+
+
+//-------------------------------------------------
+// build_output_prefix - create a prefix
+// indicating the current source file, driver,
+// and device
+//-------------------------------------------------
+
+void validity_checker::build_output_prefix(std::ostream &str) const
+{
+ // if we have a current (non-root) device, indicate that
+ if (m_current_device && m_current_device->owner())
+ util::stream_format(str, "%s device '%s': ", m_current_device->name(), m_current_device->tag() + 1);
+
+ // if we have a current port, indicate that as well
+ if (m_current_ioport)
+ util::stream_format(str, "ioport '%s': ", m_current_ioport);
+}
+
+
+//-------------------------------------------------
+// error_output - error message output override
+//-------------------------------------------------
+
+void validity_checker::output_callback(osd_output_channel channel, const util::format_argument_pack<std::ostream> &args)
+{
+ std::ostringstream output;
+ switch (channel)
+ {
+ case OSD_OUTPUT_CHANNEL_ERROR:
+ // count the error
+ m_errors++;
+
+ // output the source(driver) device 'tag'
+ build_output_prefix(output);
+
+ // generate the string
+ util::stream_format(output, args);
+ m_error_text.append(output.str());
+ break;
+
+ case OSD_OUTPUT_CHANNEL_WARNING:
+ // count the error
+ m_warnings++;
+
+ // output the source(driver) device 'tag'
+ build_output_prefix(output);
+
+ // generate the string and output to the original target
+ util::stream_format(output, args);
+ m_warning_text.append(output.str());
+ break;
+
+ case OSD_OUTPUT_CHANNEL_VERBOSE:
+ // if we're not verbose, skip it
+ if (!m_print_verbose) break;
+
+ // output the source(driver) device 'tag'
+ build_output_prefix(output);
+
+ // generate the string and output to the original target
+ util::stream_format(output, args);
+ m_verbose_text.append(output.str());
+ break;
+
+ default:
+ chain_output(channel, args);
+ break;
+ }
+}
+
+//-------------------------------------------------
+// output_via_delegate - helper to output a
+// message via a varargs string, so the argptr
+// can be forwarded onto the given delegate
+//-------------------------------------------------
+
+template <typename Format, typename... Params>
+void validity_checker::output_via_delegate(osd_output_channel channel, Format &&fmt, Params &&...args)
+{
+ // call through to the delegate with the proper parameters
+ chain_output(channel, util::make_format_argument_pack(std::forward<Format>(fmt), std::forward<Params>(args)...));
+}
+
+//-------------------------------------------------
+// output_indented_errors - helper to output error
+// and warning messages with header and indents
+//-------------------------------------------------
+void validity_checker::output_indented_errors(std::string &text, const char *header)
+{
+ // remove trailing newline
+ if (text[text.size()-1] == '\n')
+ text.erase(text.size()-1, 1);
+ strreplace(text, "\n", "\n ");
+ output_via_delegate(OSD_OUTPUT_CHANNEL_ERROR, "%s:\n %s\n", header, text);
+}
diff --git a/docs/release/src/emu/video.cpp b/docs/release/src/emu/video.cpp
new file mode 100644
index 00000000000..9ab18b03f4d
--- /dev/null
+++ b/docs/release/src/emu/video.cpp
@@ -0,0 +1,1275 @@
+// license:BSD-3-Clause
+// copyright-holders:Aaron Giles
+/***************************************************************************
+
+ video.cpp
+
+ Core MAME video routines.
+
+***************************************************************************/
+
+#include "emu.h"
+#include "emuopts.h"
+#include "debugger.h"
+#include "ui/uimain.h"
+#include "crsshair.h"
+#include "rendersw.hxx"
+#include "output.h"
+
+#include "corestr.h"
+#include "png.h"
+#include "xmlfile.h"
+
+#include "osdepend.h"
+
+
+//**************************************************************************
+// DEBUGGING
+//**************************************************************************
+
+#define LOG_THROTTLE (0)
+
+
+
+//**************************************************************************
+// GLOBAL VARIABLES
+//**************************************************************************
+
+// frameskipping tables
+const bool video_manager::s_skiptable[FRAMESKIP_LEVELS][FRAMESKIP_LEVELS] =
+{
+ { false, false, false, false, false, false, false, false, false, false, false, false },
+ { false, false, false, false, false, false, false, false, false, false, false, true },
+ { false, false, false, false, false, true , false, false, false, false, false, true },
+ { false, false, false, true , false, false, false, true , false, false, false, true },
+ { false, false, true , false, false, true , false, false, true , false, false, true },
+ { false, true , false, false, true , false, true , false, false, true , false, true },
+ { false, true , false, true , false, true , false, true , false, true , false, true },
+ { false, true , false, true , true , false, true , false, true , true , false, true },
+ { false, true , true , false, true , true , false, true , true , false, true , true },
+ { false, true , true , true , false, true , true , true , false, true , true , true },
+ { false, true , true , true , true , true , false, true , true , true , true , true },
+ { false, true , true , true , true , true , true , true , true , true , true , true }
+};
+
+
+
+//**************************************************************************
+// VIDEO MANAGER
+//**************************************************************************
+
+static void video_notifier_callback(const char *outname, s32 value, void *param)
+{
+ video_manager *vm = (video_manager *)param;
+
+ vm->set_output_changed();
+}
+
+
+//-------------------------------------------------
+// video_manager - constructor
+//-------------------------------------------------
+
+video_manager::video_manager(running_machine &machine)
+ : m_machine(machine)
+ , m_screenless_frame_timer(nullptr)
+ , m_output_changed(false)
+ , m_throttle_last_ticks(0)
+ , m_throttle_realtime(attotime::zero)
+ , m_throttle_emutime(attotime::zero)
+ , m_throttle_history(0)
+ , m_speed_last_realtime(0)
+ , m_speed_last_emutime(attotime::zero)
+ , m_speed_percent(1.0)
+ , m_overall_real_seconds(0)
+ , m_overall_real_ticks(0)
+ , m_overall_emutime(attotime::zero)
+ , m_overall_valid_counter(0)
+ , m_throttled(true)
+ , m_throttle_rate(1.0f)
+ , m_fastforward(false)
+ , m_seconds_to_run(machine.options().seconds_to_run())
+ , m_auto_frameskip(machine.options().auto_frameskip())
+ , m_speed(original_speed_setting())
+ , m_low_latency(machine.options().low_latency())
+ , m_empty_skip_count(0)
+ , m_frameskip_max(m_auto_frameskip ? machine.options().frameskip() : 0)
+ , m_frameskip_level(m_auto_frameskip ? 0 : machine.options().frameskip())
+ , m_frameskip_counter(0)
+ , m_frameskip_adjust(0)
+ , m_skipping_this_frame(false)
+ , m_average_oversleep(0)
+ , m_snap_target(nullptr)
+ , m_snap_native(true)
+ , m_snap_width(0)
+ , m_snap_height(0)
+ , m_timecode_enabled(false)
+ , m_timecode_write(false)
+ , m_timecode_text("")
+ , m_timecode_start(attotime::zero)
+ , m_timecode_total(attotime::zero)
+{
+ // request a callback upon exiting
+ machine.add_notifier(MACHINE_NOTIFY_EXIT, machine_notify_delegate(&video_manager::exit, this));
+ machine.save().register_postload(save_prepost_delegate(FUNC(video_manager::postload), this));
+
+ // extract initial execution state from global configuration settings
+ update_refresh_speed();
+
+ const unsigned screen_count(screen_device_enumerator(machine.root_device()).count());
+ const bool no_screens(!screen_count);
+
+ // create a render target for snapshots
+ const char *viewname = machine.options().snap_view();
+ m_snap_native = !no_screens && !strcmp(viewname, "native");
+
+ if (m_snap_native)
+ {
+ // the native target is hard-coded to our internal layout and has all options disabled
+ util::xml::file::ptr const root(util::xml::file::create());
+ if (!root)
+ throw emu_fatalerror("Couldn't create XML document??");
+ util::xml::data_node *const layoutnode(root->add_child("mamelayout", nullptr));
+ if (!layoutnode)
+ throw emu_fatalerror("Couldn't create XML node??");
+ layoutnode->set_attribute_int("version", 2);
+
+ for (unsigned i = 0; screen_count > i; ++i)
+ {
+ util::xml::data_node *const viewnode(layoutnode->add_child("view", nullptr));
+ if (!viewnode)
+ throw emu_fatalerror("Couldn't create XML node??");
+ viewnode->set_attribute("name", util::string_format("s%1$u", i).c_str());
+ util::xml::data_node *const screennode(viewnode->add_child("screen", nullptr));
+ if (!screennode)
+ throw emu_fatalerror("Couldn't create XML node??");
+ screennode->set_attribute_int("index", i);
+ util::xml::data_node *const boundsnode(screennode->add_child("bounds", nullptr));
+ if (!boundsnode)
+ throw emu_fatalerror("Couldn't create XML node??");
+ boundsnode->set_attribute_int("left", 0);
+ boundsnode->set_attribute_int("top", 0);
+ boundsnode->set_attribute_int("right", 1);
+ boundsnode->set_attribute_int("bottom", 1);
+ }
+
+ m_snap_target = machine.render().target_alloc(*root, RENDER_CREATE_SINGLE_FILE | RENDER_CREATE_HIDDEN);
+ m_snap_target->set_screen_overlay_enabled(false);
+ m_snap_target->set_zoom_to_screen(false);
+ }
+ else
+ {
+ // otherwise, non-default targets select the specified view and turn off effects
+ m_snap_target = machine.render().target_alloc(nullptr, RENDER_CREATE_HIDDEN);
+ m_snap_target->set_view(m_snap_target->configured_view(viewname, 0, 1));
+ m_snap_target->set_screen_overlay_enabled(false);
+ }
+
+ // extract snap resolution if present
+ if (sscanf(machine.options().snap_size(), "%dx%d", &m_snap_width, &m_snap_height) != 2)
+ m_snap_width = m_snap_height = 0;
+
+ // if no screens, create a periodic timer to drive updates
+ if (no_screens)
+ {
+ m_screenless_frame_timer = machine.scheduler().timer_alloc(timer_expired_delegate(FUNC(video_manager::screenless_update_callback), this));
+ m_screenless_frame_timer->adjust(screen_device::DEFAULT_FRAME_PERIOD, 0, screen_device::DEFAULT_FRAME_PERIOD);
+ machine.output().set_global_notifier(video_notifier_callback, this);
+ }
+}
+
+
+//-------------------------------------------------
+// set_frameskip - set the current actual
+// frameskip (-1 means autoframeskip)
+//-------------------------------------------------
+
+void video_manager::set_frameskip(int frameskip)
+{
+ if (0 > frameskip)
+ {
+ // -1 means autoframeskip
+ if (!m_auto_frameskip)
+ m_frameskip_level = 0;
+ m_auto_frameskip = true;
+ }
+ else
+ {
+ // any other level is a direct control
+ m_auto_frameskip = false;
+ m_frameskip_level = std::min<int>(frameskip, MAX_FRAMESKIP);
+ }
+}
+
+
+//-------------------------------------------------
+// frame_update - handle frameskipping and UI,
+// plus updating the screen during normal
+// operations
+//-------------------------------------------------
+
+void video_manager::frame_update(bool from_debugger)
+{
+ // only render sound and video if we're in the running phase
+ machine_phase const phase = machine().phase();
+ bool skipped_it = m_skipping_this_frame;
+ if (phase == machine_phase::RUNNING && (!machine().paused() || machine().options().update_in_pause()))
+ {
+ bool anything_changed = finish_screen_updates();
+
+ // if none of the screens changed and we haven't skipped too many frames in a row,
+ // mark this frame as skipped to prevent throttling; this helps for games that
+ // don't update their screen at the monitor refresh rate
+ if (!anything_changed && !m_auto_frameskip && m_frameskip_level == 0 && m_empty_skip_count++ < 3)
+ skipped_it = true;
+ else
+ m_empty_skip_count = 0;
+ }
+
+ // draw the user interface
+ emulator_info::draw_user_interface(machine());
+
+ // if we're throttling, synchronize before rendering
+ attotime current_time = machine().time();
+ if (!from_debugger && !skipped_it && phase > machine_phase::INIT && !m_low_latency && effective_throttle())
+ update_throttle(current_time);
+
+ // ask the OSD to update
+ g_profiler.start(PROFILER_BLIT);
+ machine().osd().update(!from_debugger && skipped_it);
+ g_profiler.stop();
+
+ // we synchronize after rendering instead of before, if low latency mode is enabled
+ if (!from_debugger && !skipped_it && phase > machine_phase::INIT && m_low_latency && effective_throttle())
+ update_throttle(current_time);
+
+ // get most recent input now
+ machine().osd().input_update();
+
+ emulator_info::periodic_check();
+
+ if (!from_debugger)
+ {
+ // perform tasks for this frame
+ machine().call_notifiers(MACHINE_NOTIFY_FRAME);
+
+ // update frameskipping
+ if (phase > machine_phase::INIT)
+ update_frameskip();
+
+ // update speed computations
+ if (!skipped_it && phase > machine_phase::INIT)
+ recompute_speed(current_time);
+ }
+
+ // call the end-of-frame callback
+ if (phase == machine_phase::RUNNING)
+ {
+ // reset partial updates if we're paused or if the debugger is active
+ screen_device *const screen = screen_device_enumerator(machine().root_device()).first();
+ bool const debugger_enabled = machine().debug_flags & DEBUG_FLAG_ENABLED;
+ bool const within_instruction_hook = debugger_enabled && machine().debugger().within_instruction_hook();
+ if (screen && ((machine().paused() && machine().options().update_in_pause()) || from_debugger || within_instruction_hook))
+ screen->reset_partial_updates();
+ }
+}
+
+
+//-------------------------------------------------
+// speed_text - print the text to be displayed
+// into a string buffer
+//-------------------------------------------------
+
+std::string video_manager::speed_text()
+{
+ std::ostringstream str;
+
+ // if we're paused, just display Paused
+ bool paused = machine().paused();
+ if (paused)
+ str << "paused";
+
+ // if we're fast forwarding, just display Fast-forward
+ else if (m_fastforward)
+ str << "fast ";
+
+ // if we're auto frameskipping, display that plus the level
+ else if (effective_autoframeskip())
+ util::stream_format(str, "auto%2d/%d", effective_frameskip(), m_frameskip_max ? m_frameskip_max : MAX_FRAMESKIP);
+
+ // otherwise, just display the frameskip plus the level
+ else
+ util::stream_format(str, "skip %d/%d", effective_frameskip(), MAX_FRAMESKIP);
+
+ // append the speed for all cases except paused
+ if (!paused)
+ util::stream_format(str, "%4d%%", (int)(100 * m_speed_percent + 0.5));
+
+ // display the number of partial updates as well
+ int partials = 0;
+ for (screen_device &screen : screen_device_enumerator(machine().root_device()))
+ partials += screen.partial_updates();
+ if (partials > 1)
+ util::stream_format(str, "\n%d partial updates", partials);
+
+ return str.str();
+}
+
+
+//-------------------------------------------------
+// save_snapshot - save a snapshot to the given
+// file handle
+//-------------------------------------------------
+
+void video_manager::save_snapshot(screen_device *screen, emu_file &file)
+{
+ // validate
+ assert(!m_snap_native || screen != nullptr);
+
+ // create the bitmap to pass in
+ create_snapshot_bitmap(screen);
+
+ // add two text entries describing the image
+ std::string text1 = std::string(emulator_info::get_appname()).append(" ").append(emulator_info::get_build_version());
+ std::string text2 = std::string(machine().system().manufacturer).append(" ").append(machine().system().type.fullname());
+ util::png_info pnginfo;
+ pnginfo.add_text("Software", text1);
+ pnginfo.add_text("System", text2);
+
+ // now do the actual work
+ const rgb_t *palette = (screen != nullptr && screen->has_palette()) ? screen->palette().palette()->entry_list_adjusted() : nullptr;
+ int entries = (screen != nullptr && screen->has_palette()) ? screen->palette().entries() : 0;
+ std::error_condition const error = util::png_write_bitmap(file, &pnginfo, m_snap_bitmap, entries, palette);
+ if (error)
+ osd_printf_error("Error generating PNG for snapshot (%s:%d %s)\n", error.category().name(), error.value(), error.message());
+}
+
+
+//-------------------------------------------------
+// save_active_screen_snapshots - save a
+// snapshot of all active screens
+//-------------------------------------------------
+
+void video_manager::save_active_screen_snapshots()
+{
+ if (m_snap_native)
+ {
+ // if we're native, then write one snapshot per visible screen
+ for (screen_device &screen : screen_device_enumerator(machine().root_device()))
+ if (machine().render().is_live(screen))
+ {
+ emu_file file(machine().options().snapshot_directory(), OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS);
+ std::error_condition const filerr = open_next(file, "png");
+ if (!filerr)
+ save_snapshot(&screen, file);
+ }
+ }
+ else
+ {
+ // otherwise, just write a single snapshot
+ emu_file file(machine().options().snapshot_directory(), OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS);
+ std::error_condition const filerr = open_next(file, "png");
+ if (!filerr)
+ save_snapshot(nullptr, file);
+ }
+}
+
+
+//-------------------------------------------------
+// save_input_timecode - add a line of current
+// timestamp to inp.timecode file
+//-------------------------------------------------
+
+void video_manager::save_input_timecode()
+{
+ // if record timecode input is not active, do nothing
+ if (!m_timecode_enabled) {
+ return;
+ }
+ m_timecode_write = true;
+}
+
+std::string &video_manager::timecode_text(std::string &str)
+{
+ attotime elapsed_time = machine().time() - m_timecode_start;
+ str = string_format(" %s%s%02d:%02d %s",
+ m_timecode_text,
+ m_timecode_text.empty() ? "" : " ",
+ (elapsed_time.m_seconds / 60) % 60,
+ elapsed_time.m_seconds % 60,
+ machine().paused() ? "[paused] " : "");
+ return str;
+}
+
+std::string &video_manager::timecode_total_text(std::string &str)
+{
+ attotime elapsed_time = m_timecode_total;
+ if (machine().ui().show_timecode_counter()) {
+ elapsed_time += machine().time() - m_timecode_start;
+ }
+ str = string_format("TOTAL %02d:%02d ",
+ (elapsed_time.m_seconds / 60) % 60,
+ elapsed_time.m_seconds % 60);
+ return str;
+}
+
+
+//-------------------------------------------------
+// begin_recording_screen - begin recording a
+// movie for a specific screen
+//-------------------------------------------------
+
+void video_manager::begin_recording_screen(const std::string &filename, uint32_t index, screen_device *screen, movie_recording::format format)
+{
+ // determine the file extension
+ const char *extension = movie_recording::format_file_extension(format);
+
+ // create the emu_file
+ bool is_absolute_path = !filename.empty() && osd_is_absolute_path(filename);
+ std::unique_ptr<emu_file> movie_file = std::make_unique<emu_file>(
+ is_absolute_path ? "" : machine().options().snapshot_directory(),
+ OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS);
+
+ // and open the actual file
+ std::error_condition filerr = filename.empty()
+ ? open_next(*movie_file, extension)
+ : movie_file->open(filename);
+ if (filerr)
+ {
+ osd_printf_error("Error creating movie, %s:%d %s\n", filerr.category().name(), filerr.value(), filerr.message());
+ return;
+ }
+
+ // we have a file; try to create the recording
+ std::unique_ptr<movie_recording> recording = movie_recording::create(machine(), screen, format, std::move(movie_file), m_snap_bitmap);
+
+ // if successful push it onto the list
+ if (recording)
+ m_movie_recordings.push_back(std::move(recording));
+}
+
+
+//-------------------------------------------------
+// begin_recording - begin recording of a movie
+//-------------------------------------------------
+
+void video_manager::begin_recording(const char *name, movie_recording::format format)
+{
+ // create a snapshot bitmap so we know what the target size is
+ screen_device_enumerator iterator(machine().root_device());
+ screen_device_enumerator::iterator iter(iterator.begin());
+ uint32_t count = (uint32_t)iterator.count();
+ const bool no_screens(!count);
+
+ if (no_screens)
+ {
+ assert(!m_snap_native);
+ count = 1;
+ }
+
+ // clear out existing recordings
+ m_movie_recordings.clear();
+
+ if (m_snap_native)
+ {
+ for (uint32_t index = 0; index < count; index++, iter++)
+ {
+ create_snapshot_bitmap(iter.current());
+
+ std::string tempname;
+ if (name)
+ tempname = index > 0 ? name : util::string_format("%s%d", name, index);
+ begin_recording_screen(
+ tempname,
+ index,
+ iter.current(),
+ format);
+ }
+ }
+ else
+ {
+ create_snapshot_bitmap(nullptr);
+ begin_recording_screen(name ? name : "", 0, iter.current(), format);
+ }
+}
+
+
+//-------------------------------------------------
+// add_sound_to_recording - add sound to a movie
+// recording
+//-------------------------------------------------
+
+void video_manager::add_sound_to_recording(const s16 *sound, int numsamples)
+{
+ for (auto &recording : m_movie_recordings)
+ recording->add_sound_to_recording(sound, numsamples);
+}
+
+
+//-------------------------------------------------
+// video_exit - close down the video system
+//-------------------------------------------------
+
+void video_manager::exit()
+{
+ // stop recording any movie
+ m_movie_recordings.clear();
+
+ // free the snapshot target
+ machine().render().target_free(m_snap_target);
+ m_snap_bitmap.reset();
+
+ // print a final result if we have at least 2 seconds' worth of data
+ if (!emulator_info::standalone() && m_overall_emutime.seconds() >= 1)
+ {
+ osd_ticks_t tps = osd_ticks_per_second();
+ double final_real_time = (double)m_overall_real_seconds + (double)m_overall_real_ticks / (double)tps;
+ double final_emu_time = m_overall_emutime.as_double();
+ osd_printf_info("Average speed: %.2f%% (%d seconds)\n", 100 * final_emu_time / final_real_time, (m_overall_emutime + attotime(0, ATTOSECONDS_PER_SECOND / 2)).seconds());
+ }
+}
+
+
+//-------------------------------------------------
+// screenless_update_callback - update generator
+// when there are no screens to drive it
+//-------------------------------------------------
+
+void video_manager::screenless_update_callback(void *ptr, int param)
+{
+ // force an update
+ frame_update(false);
+}
+
+
+//-------------------------------------------------
+// postload - callback for resetting things after
+// state has been loaded
+//-------------------------------------------------
+
+void video_manager::postload()
+{
+ for (const auto &x : m_movie_recordings)
+ x->set_next_frame_time(machine().time());
+}
+
+
+//-------------------------------------------------
+// effective_autoframeskip - return the effective
+// autoframeskip value, accounting for fast
+// forward
+//-------------------------------------------------
+
+inline bool video_manager::effective_autoframeskip() const
+{
+ // if we're fast forwarding or paused, autoframeskip is disabled
+ if (m_fastforward || machine().paused())
+ return false;
+
+ // otherwise, it's up to the user
+ return m_auto_frameskip;
+}
+
+
+//-------------------------------------------------
+// effective_frameskip - return the effective
+// frameskip value, accounting for fast
+// forward
+//-------------------------------------------------
+
+int video_manager::effective_frameskip() const
+{
+ // if we're fast forwarding, use the maximum frameskip
+ if (m_fastforward)
+ return FRAMESKIP_LEVELS - 1;
+
+ // otherwise, it's up to the user
+ return m_frameskip_level;
+}
+
+
+//-------------------------------------------------
+// effective_throttle - return the effective
+// throttle value, accounting for fast
+// forward and user interface
+//-------------------------------------------------
+
+inline bool video_manager::effective_throttle() const
+{
+ // if we're paused, or if the UI is active, we always throttle
+ if (machine().paused() && !machine().options().update_in_pause()) //|| machine().ui().is_menu_active())
+ return true;
+
+ // if we're fast forwarding, we don't throttle
+ if (m_fastforward)
+ return false;
+
+ // otherwise, it's up to the user
+ return throttled();
+}
+
+
+//-------------------------------------------------
+// original_speed_setting - return the original
+// speed setting
+//-------------------------------------------------
+
+inline int video_manager::original_speed_setting() const
+{
+ return machine().options().speed() * 1000.0f + 0.5f;
+}
+
+
+//-------------------------------------------------
+// finish_screen_updates - finish updating all
+// the screens
+//-------------------------------------------------
+
+bool video_manager::finish_screen_updates()
+{
+ // finish updating the screens
+ screen_device_enumerator iter(machine().root_device());
+
+ bool has_live_screen = false;
+ for (screen_device &screen : iter)
+ {
+ if (screen.partial_scan_hpos() >= 0) // previous update ended mid-scanline
+ screen.update_now();
+ screen.update_partial(screen.visible_area().max_y);
+
+ if (machine().render().is_live(screen))
+ has_live_screen = true;
+ }
+
+ bool anything_changed = !has_live_screen || m_output_changed;
+ m_output_changed = false;
+
+ // now add the quads for all the screens
+ for (screen_device &screen : iter)
+ if (screen.update_quads())
+ anything_changed = true;
+
+ // draw HUD from LUA callback (if any)
+ anything_changed |= emulator_info::frame_hook();
+
+ // update our movie recording and burn-in state
+ if (!machine().paused())
+ {
+ record_frame();
+
+ // iterate over screens and update the burnin for the ones that care
+ for (screen_device &screen : iter)
+ screen.update_burnin();
+ }
+
+ // draw any crosshairs
+ for (screen_device &screen : iter)
+ machine().crosshair().render(screen);
+
+ return anything_changed;
+}
+
+
+
+//-------------------------------------------------
+// update_throttle - throttle to the game's
+// natural speed
+//-------------------------------------------------
+
+void video_manager::update_throttle(attotime emutime)
+{
+/*
+
+ Throttling theory:
+
+ This routine is called periodically with an up-to-date emulated time.
+ The idea is to synchronize real time with emulated time. We do this
+ by "throttling", or waiting for real time to catch up with emulated
+ time.
+
+ In an ideal world, it will take less real time to emulate and render
+ each frame than the emulated time, so we need to slow things down to
+ get both times in sync.
+
+ There are many complications to this model:
+
+ * some games run too slow, so each frame we get further and
+ further behind real time; our only choice here is to not
+ throttle
+
+ * some games have very uneven frame rates; one frame will take
+ a long time to emulate, and the next frame may be very fast
+
+ * we run on top of multitasking OSes; sometimes execution time
+ is taken away from us, and this means we may not get enough
+ time to emulate one frame
+
+ * we may be paused, and emulated time may not be marching
+ forward
+
+ * emulated time could jump due to resetting the machine or
+ restoring from a saved state
+
+*/
+
+ // outer scope so we can break out in case of a resync
+ while (1)
+ {
+ // apply speed factor to emu time
+ if (m_speed != 0 && m_speed != 1000)
+ {
+ // multiply emutime by 1000, then divide by the global speed factor
+ emutime = (emutime * 1000) / m_speed;
+ }
+
+ // compute conversion factors up front
+ osd_ticks_t ticks_per_second = osd_ticks_per_second();
+ attoseconds_t attoseconds_per_tick = ATTOSECONDS_PER_SECOND / ticks_per_second * m_throttle_rate;
+
+ // if we're paused, emutime will not advance; instead, we subtract a fixed
+ // amount of time (1/60th of a second) from the emulated time that was passed in,
+ // and explicitly reset our tracked real and emulated timers to that value ...
+ // this means we pretend that the last update was exactly 1/60th of a second
+ // ago, and was in sync in both real and emulated time
+ if (machine().paused())
+ {
+ m_throttle_emutime = emutime - attotime(0, ATTOSECONDS_PER_SECOND / PAUSED_REFRESH_RATE);
+ m_throttle_realtime = m_throttle_emutime;
+ }
+
+ // attempt to detect anomalies in the emulated time by subtracting the previously
+ // reported value from our current value; this should be a small value somewhere
+ // between 0 and 1/10th of a second ... anything outside of this range is obviously
+ // wrong and requires a resync
+ attoseconds_t emu_delta_attoseconds = (emutime - m_throttle_emutime).as_attoseconds();
+ if (emu_delta_attoseconds < 0 || emu_delta_attoseconds > ATTOSECONDS_PER_SECOND / 10)
+ {
+ if (LOG_THROTTLE)
+ machine().logerror("Resync due to weird emutime delta: %s\n", attotime(0, emu_delta_attoseconds).as_string(18));
+ break;
+ }
+
+ // now determine the current real time in OSD-specified ticks; we have to be careful
+ // here because counters can wrap, so we only use the difference between the last
+ // read value and the current value in our computations
+ osd_ticks_t diff_ticks = osd_ticks() - m_throttle_last_ticks;
+ m_throttle_last_ticks += diff_ticks;
+
+ // if it has been more than a full second of real time since the last call to this
+ // function, we just need to resynchronize
+ if (diff_ticks >= ticks_per_second)
+ {
+ if (LOG_THROTTLE)
+ machine().logerror("Resync due to real time advancing by more than 1 second\n");
+ break;
+ }
+
+ // convert this value into attoseconds for easier comparison
+ attoseconds_t real_delta_attoseconds = diff_ticks * attoseconds_per_tick;
+
+ // now update our real and emulated timers with the current values
+ m_throttle_emutime = emutime;
+ m_throttle_realtime += attotime(0, real_delta_attoseconds);
+
+ // keep a history of whether or not emulated time beat real time over the last few
+ // updates; this can be used for future heuristics
+ m_throttle_history = (m_throttle_history << 1) | (emu_delta_attoseconds > real_delta_attoseconds);
+
+ // determine how far ahead real time is versus emulated time; note that we use the
+ // accumulated times for this instead of the deltas for the current update because
+ // we want to track time over a longer duration than a single update
+ attoseconds_t real_is_ahead_attoseconds = (m_throttle_emutime - m_throttle_realtime).as_attoseconds();
+
+ // if we're more than 1/10th of a second out, or if we are behind at all and emulation
+ // is taking longer than the real frame, we just need to resync
+ if (real_is_ahead_attoseconds < -ATTOSECONDS_PER_SECOND / 10 ||
+ (real_is_ahead_attoseconds < 0 && population_count_32(m_throttle_history & 0xff) < 6))
+ {
+ if (LOG_THROTTLE)
+ machine().logerror("Resync due to being behind: %s (history=%08X)\n", attotime(0, -real_is_ahead_attoseconds).as_string(18), m_throttle_history);
+ break;
+ }
+
+ // if we're behind, it's time to just get out
+ if (real_is_ahead_attoseconds < 0)
+ return;
+
+ // compute the target real time, in ticks, where we want to be
+ osd_ticks_t target_ticks = m_throttle_last_ticks + real_is_ahead_attoseconds / attoseconds_per_tick;
+
+ // throttle until we read the target, and update real time to match the final time
+ diff_ticks = throttle_until_ticks(target_ticks) - m_throttle_last_ticks;
+ m_throttle_last_ticks += diff_ticks;
+ m_throttle_realtime += attotime(0, diff_ticks * attoseconds_per_tick);
+ return;
+ }
+
+ // reset realtime and emutime to the same value
+ m_throttle_realtime = m_throttle_emutime = emutime;
+}
+
+
+//-------------------------------------------------
+// throttle_until_ticks - spin until the
+// specified target time, calling the OSD code
+// to sleep if possible
+//-------------------------------------------------
+
+osd_ticks_t video_manager::throttle_until_ticks(osd_ticks_t target_ticks)
+{
+ // we're allowed to sleep via the OSD code only if we're configured to do so
+ // and we're not frameskipping due to autoframeskip, or if we're paused
+ bool const allowed_to_sleep = (machine().options().sleep() && (!effective_autoframeskip() || effective_frameskip() == 0)) || machine().paused();
+
+ // loop until we reach our target
+ g_profiler.start(PROFILER_IDLE);
+ osd_ticks_t current_ticks = osd_ticks();
+ while (current_ticks < target_ticks)
+ {
+ // compute how much time to sleep for, taking into account the average oversleep
+ osd_ticks_t delta = target_ticks - current_ticks;
+ if (delta > m_average_oversleep / 1000)
+ delta -= m_average_oversleep / 1000;
+ else
+ delta = 0;
+
+ // see if we can sleep
+ bool const slept = allowed_to_sleep && delta;
+ if (slept)
+ osd_sleep(delta);
+
+ // read the new value
+ osd_ticks_t const new_ticks = osd_ticks();
+
+ // keep some metrics on the sleeping patterns of the OSD layer
+ if (slept)
+ {
+ // if we overslept, keep an average of the amount
+ osd_ticks_t const actual_ticks = new_ticks - current_ticks;
+ if (actual_ticks > delta)
+ {
+ // take 99% of the previous average plus 1% of the new value
+ osd_ticks_t const oversleep_milliticks = 1000 * (actual_ticks - delta);
+ m_average_oversleep = (m_average_oversleep * 99 + oversleep_milliticks) / 100;
+
+ if (LOG_THROTTLE)
+ machine().logerror("Slept for %d ticks, got %d ticks, avgover = %d\n", (int)delta, (int)actual_ticks, (int)m_average_oversleep);
+ }
+ }
+ current_ticks = new_ticks;
+ }
+ g_profiler.stop();
+
+ return current_ticks;
+}
+
+
+//-------------------------------------------------
+// update_frameskip - update frameskipping
+// counters and periodically update autoframeskip
+//-------------------------------------------------
+
+void video_manager::update_frameskip()
+{
+ // if we're throttling and autoframeskip is on, adjust
+ if (effective_throttle() && effective_autoframeskip() && m_frameskip_counter == 0)
+ {
+ // calibrate the "adjusted speed" based on the target
+ double adjusted_speed_percent = m_speed_percent / double(m_throttle_rate);
+
+ double speed = m_speed * 0.001;
+ if (adjusted_speed_percent >= 0.995 * speed)
+ {
+ // if we're too fast, attempt to decrease the frameskip
+ // but only after 3 consecutive frames where we are too fast
+ if (++m_frameskip_adjust >= 3)
+ {
+ m_frameskip_adjust = 0;
+ if (m_frameskip_level > 0)
+ m_frameskip_level--;
+ }
+ }
+ else
+ {
+ // if we're too slow, attempt to increase the frameskip
+ if (adjusted_speed_percent < 0.80 * speed) // if below 80% speed, be more aggressive
+ m_frameskip_adjust -= (0.90 * speed - m_speed_percent) / 0.05;
+ else if (m_frameskip_level < 8) // if we're close, only force it up to frameskip 8
+ m_frameskip_adjust--;
+
+ // perform the adjustment
+ while (m_frameskip_adjust <= -2)
+ {
+ m_frameskip_adjust += 2;
+ if (m_frameskip_level < (m_frameskip_max ? m_frameskip_max : MAX_FRAMESKIP))
+ m_frameskip_level++;
+ }
+ }
+ }
+
+ // increment the frameskip counter and determine if we will skip the next frame
+ m_frameskip_counter = (m_frameskip_counter + 1) % FRAMESKIP_LEVELS;
+ m_skipping_this_frame = s_skiptable[effective_frameskip()][m_frameskip_counter];
+}
+
+
+//-------------------------------------------------
+// update_refresh_speed - update the m_speed
+// based on the maximum refresh rate supported
+//-------------------------------------------------
+
+void video_manager::update_refresh_speed()
+{
+ // only do this if the refreshspeed option is used
+ if (machine().options().refresh_speed())
+ {
+ double minrefresh = machine().render().max_update_rate();
+ if (minrefresh != 0)
+ {
+ // find the screen with the shortest frame period (max refresh rate)
+ // note that we first check the token since this can get called before all screens are created
+ attoseconds_t min_frame_period = ATTOSECONDS_PER_SECOND;
+ for (screen_device &screen : screen_device_enumerator(machine().root_device()))
+ {
+ attoseconds_t period = screen.frame_period().attoseconds();
+ if (period != 0)
+ min_frame_period = std::min(min_frame_period, period);
+ }
+
+ // compute a target speed as an integral percentage
+ // note that we lop 0.25Hz off of the minrefresh when doing the computation to allow for
+ // the fact that most refresh rates are not accurate to 10 digits...
+ u32 target_speed = floor((minrefresh - 0.25) * 1000.0 / ATTOSECONDS_TO_HZ(min_frame_period));
+ u32 original_speed = original_speed_setting();
+ target_speed = std::min(target_speed, original_speed);
+
+ // if we changed, log that verbosely
+ if (target_speed != m_speed)
+ {
+ osd_printf_verbose("Adjusting target speed to %.1f%% (hw=%.2fHz, game=%.2fHz, adjusted=%.2fHz)\n", target_speed / 10.0, minrefresh, ATTOSECONDS_TO_HZ(min_frame_period), ATTOSECONDS_TO_HZ(min_frame_period * 1000.0 / target_speed));
+ m_speed = target_speed;
+ }
+ }
+ }
+}
+
+
+//-------------------------------------------------
+// recompute_speed - recompute the current
+// overall speed; we assume this is called only
+// if we did not skip a frame
+//-------------------------------------------------
+
+void video_manager::recompute_speed(const attotime &emutime)
+{
+ // if we don't have a starting time yet, or if we're paused, reset our starting point
+ if (m_speed_last_realtime == 0 || machine().paused())
+ {
+ m_speed_last_realtime = osd_ticks();
+ m_speed_last_emutime = emutime;
+ }
+
+ // if it has been more than the update interval, update the time
+ attotime delta_emutime = emutime - m_speed_last_emutime;
+ if (delta_emutime > attotime(0, ATTOSECONDS_PER_SPEED_UPDATE))
+ {
+ // convert from ticks to attoseconds
+ osd_ticks_t realtime = osd_ticks();
+ osd_ticks_t delta_realtime = realtime - m_speed_last_realtime;
+ osd_ticks_t tps = osd_ticks_per_second();
+ m_speed_percent = delta_emutime.as_double() * (double)tps / (double)delta_realtime;
+
+ // remember the last times
+ m_speed_last_realtime = realtime;
+ m_speed_last_emutime = emutime;
+
+ // if we're throttled, this time period counts for overall speed; otherwise, we reset the counter
+ if (!m_fastforward)
+ m_overall_valid_counter++;
+ else
+ m_overall_valid_counter = 0;
+
+ // if we've had at least 4 consecutive valid periods, accumulate stats
+ if (m_overall_valid_counter >= 4)
+ {
+ m_overall_real_ticks += delta_realtime;
+ while (m_overall_real_ticks >= tps)
+ {
+ m_overall_real_ticks -= tps;
+ m_overall_real_seconds++;
+ }
+ m_overall_emutime += delta_emutime;
+ }
+ }
+
+ // if we're past the "time-to-execute" requested, signal an exit
+ if (m_seconds_to_run != 0 && emutime.seconds() >= m_seconds_to_run)
+ {
+ // create a final screenshot
+ emu_file file(machine().options().snapshot_directory(), OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS);
+ std::error_condition const filerr = open_next(file, "png");
+ if (!filerr)
+ save_snapshot(nullptr, file);
+
+ //printf("Scheduled exit at %f\n", emutime.as_double());
+ // schedule our demise
+ machine().schedule_exit();
+ }
+}
+
+
+//-------------------------------------------------
+// create_snapshot_bitmap - creates a
+// bitmap containing the screenshot for the
+// given screen
+//-------------------------------------------------
+
+typedef software_renderer<u32, 0,0,0, 16,8,0, false, true> snap_renderer_bilinear;
+typedef software_renderer<u32, 0,0,0, 16,8,0, false, false> snap_renderer;
+
+void video_manager::create_snapshot_bitmap(screen_device *screen)
+{
+ // select the appropriate view in our dummy target
+ if (m_snap_native && screen)
+ {
+ screen_device_enumerator iter(machine().root_device());
+ int view_index = iter.indexof(*screen);
+ assert(view_index != -1);
+ m_snap_target->set_view(view_index);
+ }
+
+ // get the minimum width/height and set it on the target and bitmap
+ s32 width, height;
+ compute_snapshot_size(width, height);
+ m_snap_target->set_bounds(width, height);
+ if (width != m_snap_bitmap.width() || height != m_snap_bitmap.height())
+ m_snap_bitmap.resize(width, height);
+
+ // render the screen there
+ render_primitive_list &primlist = m_snap_target->get_primitives();
+ primlist.acquire_lock();
+ if (machine().options().snap_bilinear())
+ snap_renderer_bilinear::draw_primitives(primlist, &m_snap_bitmap.pix(0), width, height, m_snap_bitmap.rowpixels());
+ else
+ snap_renderer::draw_primitives(primlist, &m_snap_bitmap.pix(0), width, height, m_snap_bitmap.rowpixels());
+ primlist.release_lock();
+}
+
+
+//-------------------------------------------------
+// compute_snapshot_size - computes width and
+// height of the current snapshot target
+// accounting for OPTION_SNAPSIZE
+//-------------------------------------------------
+
+void video_manager::compute_snapshot_size(s32 &width, s32 &height)
+{
+ width = m_snap_width;
+ height = m_snap_height;
+ if (width == 0 || height == 0)
+ m_snap_target->compute_minimum_size(width, height);
+}
+
+
+//-------------------------------------------------
+// pixels - fills the specified buffer with the
+// RGB values of each pixel in the snapshot target
+//-------------------------------------------------
+
+void video_manager::pixels(u32 *buffer)
+{
+ create_snapshot_bitmap(nullptr);
+ for (int y = 0; y < m_snap_bitmap.height(); y++)
+ {
+ const u32 *src = &m_snap_bitmap.pix(y, 0);
+ for (int x = 0; x < m_snap_bitmap.width(); x++)
+ {
+ *buffer++ = *src++;
+ }
+ }
+}
+
+
+//-------------------------------------------------
+// open_next - open the next non-existing file of
+// type filetype according to our numbering
+// scheme
+//-------------------------------------------------
+
+std::error_condition video_manager::open_next(emu_file &file, const char *extension, uint32_t added_index)
+{
+ u32 origflags = file.openflags();
+
+ // handle defaults
+ const char *snapname = machine().options().snap_name();
+
+ if (snapname == nullptr || snapname[0] == 0)
+ snapname = "%g/%i";
+ std::string snapstr(snapname);
+
+ // strip any extension in the provided name
+ int index = snapstr.find_last_of('.');
+ if (index != -1)
+ snapstr = snapstr.substr(0, index);
+
+ // handle %d in the template (for image devices)
+ std::string snapdev("%d_");
+ int pos = snapstr.find(snapdev);
+
+ if (pos != -1)
+ {
+ // if more %d are found, revert to default and ignore them all
+ if (snapstr.find(snapdev, pos + 3) != -1)
+ snapstr.assign("%g/%i");
+ // else if there is a single %d, try to create the correct snapname
+ else
+ {
+ int name_found = 0;
+
+ // find length of the device name
+ int end = snapstr.find_first_not_of("abcdefghijklmnopqrstuvwxyz1234567890", pos + 3);
+ if (end == -1)
+ end = snapstr.length();
+
+ // copy the device name to an std::string
+ std::string snapdevname;
+ snapdevname.assign(snapstr.substr(pos + 3, end - pos - 3));
+ //printf("check template: %s\n", snapdevname.c_str());
+
+ // verify that there is such a device for this system
+ for (device_image_interface &image : image_interface_enumerator(machine().root_device()))
+ {
+ // get the device name
+ std::string tempdevname(image.brief_instance_name());
+ //printf("check device: %s\n", tempdevname.c_str());
+
+ if (snapdevname.compare(tempdevname) == 0)
+ {
+ // verify that such a device has an image mounted
+ if (image.basename() != nullptr)
+ {
+ std::string filename(image.basename());
+
+ // strip extension
+ filename = filename.substr(0, filename.find_last_of('.'));
+
+ // setup snapname and remove the %d_
+ strreplace(snapstr, snapdevname, filename);
+ snapstr.erase(pos, 3);
+ //printf("check image: %s\n", filename.c_str());
+
+ name_found = 1;
+ }
+ }
+ }
+
+ // or fallback to default
+ if (name_found == 0)
+ snapstr.assign("%g/%i");
+ }
+ }
+
+ // handle %t in the template (for timestamp)
+ std::string snaptime("%t");
+ int pos_time = snapstr.find(snaptime);
+
+ if (pos_time != -1)
+ {
+ char t_str[15];
+ const std::time_t cur_time = std::time(nullptr);
+ strftime(t_str, sizeof(t_str), "%Y%m%d_%H%M%S", std::localtime(&cur_time));
+ strreplace(snapstr, "%t", t_str);
+ }
+
+ // add our own extension
+ snapstr.append(".").append(extension);
+
+ // substitute path and gamename up front
+ strreplace(snapstr, "/", PATH_SEPARATOR);
+ strreplace(snapstr, "%g", machine().basename());
+
+ // determine if the template has an index; if not, we always use the same name
+ std::string fname;
+ if (snapstr.find("%i") == -1)
+ fname.assign(snapstr);
+
+ // otherwise, we scan for the next available filename
+ else
+ {
+ // try until we succeed
+ file.set_openflags(OPEN_FLAG_WRITE);
+ for (int seq = 0; ; seq++)
+ {
+ // build up the filename
+ fname.assign(snapstr);
+ strreplace(fname, "%i", string_format("%04d", seq));
+
+ // try to open the file; stop when we fail
+ std::error_condition const filerr = file.open(fname);
+ if (std::errc::no_such_file_or_directory == filerr)
+ break;
+ }
+ }
+
+ // create the final file
+ file.set_openflags(origflags);
+ return file.open(fname);
+}
+
+
+//-------------------------------------------------
+// record_frame - record a frame of a movie
+//-------------------------------------------------
+
+void video_manager::record_frame()
+{
+ // ignore if nothing to do
+ if (!is_recording())
+ return;
+
+ // start the profiler and get the current time
+ g_profiler.start(PROFILER_MOVIE_REC);
+ attotime curtime = machine().time();
+
+ bool error = false;
+ for (auto &recording : m_movie_recordings)
+ {
+ // create the bitmap
+ create_snapshot_bitmap(recording->screen());
+
+ // and append the frame
+ if (!recording->append_video_frame(m_snap_bitmap, curtime))
+ {
+ error = true;
+ break;
+ }
+ }
+
+ if (error)
+ end_recording();
+ g_profiler.stop();
+}
+
+
+//-------------------------------------------------
+// toggle_record_movie
+//-------------------------------------------------
+
+void video_manager::toggle_record_movie(movie_recording::format format)
+{
+ if (!is_recording())
+ {
+ begin_recording(nullptr, format);
+ machine().popmessage("REC START (%s)", format == movie_recording::format::MNG ? "MNG" : "AVI");
+ }
+ else
+ {
+ end_recording();
+ machine().popmessage("REC STOP");
+ }
+}
+
+void video_manager::end_recording()
+{
+ m_movie_recordings.clear();
+}
diff --git a/docs/release/src/emu/video.h b/docs/release/src/emu/video.h
new file mode 100644
index 00000000000..4ff0035a5c4
--- /dev/null
+++ b/docs/release/src/emu/video.h
@@ -0,0 +1,192 @@
+// license:BSD-3-Clause
+// copyright-holders:Aaron Giles
+/***************************************************************************
+
+ video.h
+
+ Core MAME video routines.
+
+***************************************************************************/
+
+#pragma once
+
+#ifndef __EMU_H__
+#error Dont include this file directly; include emu.h instead.
+#endif
+
+#ifndef MAME_EMU_VIDEO_H
+#define MAME_EMU_VIDEO_H
+
+#include "recording.h"
+
+#include <system_error>
+
+
+//**************************************************************************
+// CONSTANTS
+//**************************************************************************
+
+// number of levels of frameskipping supported
+constexpr int FRAMESKIP_LEVELS = 12;
+constexpr int MAX_FRAMESKIP = FRAMESKIP_LEVELS - 2;
+
+
+//**************************************************************************
+// TYPE DEFINITIONS
+//**************************************************************************
+
+// ======================> video_manager
+
+class video_manager
+{
+ friend class screen_device;
+
+public:
+ // construction/destruction
+ video_manager(running_machine &machine);
+
+ // getters
+ running_machine &machine() const { return m_machine; }
+ bool skip_this_frame() const { return m_skipping_this_frame; }
+ int speed_factor() const { return m_speed; }
+ int frameskip() const { return m_auto_frameskip ? -1 : m_frameskip_level; }
+ bool throttled() const { return m_throttled; }
+ float throttle_rate() const { return m_throttle_rate; }
+ bool fastforward() const { return m_fastforward; }
+
+ // setters
+ void set_speed_factor(int speed) { m_speed = speed; } // MESSUI
+ void set_frameskip(int frameskip);
+ void set_throttled(bool throttled) { m_throttled = throttled; }
+ void set_throttle_rate(float throttle_rate) { m_throttle_rate = throttle_rate; }
+ void set_fastforward(bool ffwd) { m_fastforward = ffwd; }
+ void set_output_changed() { m_output_changed = true; }
+
+ // misc
+ void toggle_record_movie(movie_recording::format format);
+ std::error_condition open_next(emu_file &file, const char *extension, uint32_t index = 0);
+ void compute_snapshot_size(s32 &width, s32 &height);
+ void pixels(u32 *buffer);
+
+ // render a frame
+ void frame_update(bool from_debugger = false);
+
+ // current speed helpers
+ std::string speed_text();
+ double speed_percent() const { return m_speed_percent; }
+ int effective_frameskip() const;
+
+ // snapshots
+ bool snap_native() const { return m_snap_native; }
+ render_target &snapshot_target() { return *m_snap_target; }
+ void save_snapshot(screen_device *screen, emu_file &file);
+ void save_active_screen_snapshots();
+ void save_input_timecode();
+
+ // movies
+ void begin_recording(const char *name, movie_recording::format format);
+ void end_recording();
+ void add_sound_to_recording(const s16 *sound, int numsamples);
+ bool is_recording() const { return !m_movie_recordings.empty(); }
+
+ void set_timecode_enabled(bool value) { m_timecode_enabled = value; }
+ bool get_timecode_enabled() { return m_timecode_enabled; }
+ bool get_timecode_write() { return m_timecode_write; }
+ void set_timecode_write(bool value) { m_timecode_write = value; }
+ void set_timecode_text(std::string &str) { m_timecode_text = str; }
+ void set_timecode_start(attotime time) { m_timecode_start = time; }
+ void add_to_total_time(attotime time) { m_timecode_total += time; }
+ std::string &timecode_text(std::string &str);
+ std::string &timecode_total_text(std::string &str);
+
+private:
+ // internal helpers
+ void exit();
+ void screenless_update_callback(void *ptr, int param);
+ void postload();
+
+ // effective value helpers
+ bool effective_autoframeskip() const;
+ bool effective_throttle() const;
+
+ // speed and throttling helpers
+ int original_speed_setting() const;
+ bool finish_screen_updates();
+ void update_throttle(attotime emutime);
+ osd_ticks_t throttle_until_ticks(osd_ticks_t target_ticks);
+ void update_frameskip();
+ void update_refresh_speed();
+ void recompute_speed(const attotime &emutime);
+
+ // snapshot/movie helpers
+ void create_snapshot_bitmap(screen_device *screen);
+ void record_frame();
+
+ // movies
+ void begin_recording_screen(const std::string &filename, uint32_t index, screen_device *screen, movie_recording::format format);
+
+ // internal state
+ running_machine & m_machine; // reference to our machine
+
+ // screenless systems
+ emu_timer * m_screenless_frame_timer; // timer to signal VBLANK start
+ bool m_output_changed; // did an output element change?
+
+ // throttling calculations
+ osd_ticks_t m_throttle_last_ticks; // osd_ticks the last call to throttle
+ attotime m_throttle_realtime; // real time the last call to throttle
+ attotime m_throttle_emutime; // emulated time the last call to throttle
+ u32 m_throttle_history; // history of frames where we were fast enough
+
+ // dynamic speed computation
+ osd_ticks_t m_speed_last_realtime; // real time at the last speed calculation
+ attotime m_speed_last_emutime; // emulated time at the last speed calculation
+ double m_speed_percent; // most recent speed percentage
+
+ // overall speed computation
+ u32 m_overall_real_seconds; // accumulated real seconds at normal speed
+ osd_ticks_t m_overall_real_ticks; // accumulated real ticks at normal speed
+ attotime m_overall_emutime; // accumulated emulated time at normal speed
+ u32 m_overall_valid_counter; // number of consecutive valid time periods
+
+ // configuration
+ bool m_throttled; // flag: true if we're currently throttled
+ float m_throttle_rate; // target rate for throttling
+ bool m_fastforward; // flag: true if we're currently fast-forwarding
+ u32 m_seconds_to_run; // number of seconds to run before quitting
+ bool m_auto_frameskip; // flag: true if we're automatically frameskipping
+ u32 m_speed; // overall speed (*1000)
+ bool m_low_latency; // flag: true if we are throttling after blitting
+
+ // frameskipping
+ u8 m_empty_skip_count; // number of empty frames we have skipped
+ u8 m_frameskip_max; // maximum frameskip level
+ u8 m_frameskip_level; // current frameskip level
+ u8 m_frameskip_counter; // counter that counts through the frameskip steps
+ s8 m_frameskip_adjust;
+ bool m_skipping_this_frame; // flag: true if we are skipping the current frame
+ osd_ticks_t m_average_oversleep; // average number of ticks the OSD oversleeps
+
+ // snapshot stuff
+ render_target * m_snap_target; // screen shapshot target
+ bitmap_rgb32 m_snap_bitmap; // screen snapshot bitmap
+ bool m_snap_native; // are we using native per-screen layouts?
+ s32 m_snap_width; // width of snapshots (0 == auto)
+ s32 m_snap_height; // height of snapshots (0 == auto)
+
+ // movie recordings
+ std::vector<movie_recording::ptr> m_movie_recordings;
+
+ static const bool s_skiptable[FRAMESKIP_LEVELS][FRAMESKIP_LEVELS];
+
+ static const attoseconds_t ATTOSECONDS_PER_SPEED_UPDATE = ATTOSECONDS_PER_SECOND / 4;
+ static const int PAUSED_REFRESH_RATE = 30;
+
+ bool m_timecode_enabled; // inp.timecode record enabled
+ bool m_timecode_write; // Show/hide timer at right (partial time)
+ std::string m_timecode_text; // Message for that video part (intro, gameplay, extra)
+ attotime m_timecode_start; // Starting timer for that video part (intro, gameplay, extra)
+ attotime m_timecode_total; // Show/hide timer at left (total elapsed on resulting video preview)
+};
+
+#endif // MAME_EMU_VIDEO_H