summaryrefslogtreecommitdiffstatshomepage
path: root/trunk/src/emu/ioport.c
diff options
context:
space:
mode:
Diffstat (limited to 'trunk/src/emu/ioport.c')
-rw-r--r--trunk/src/emu/ioport.c4571
1 files changed, 4571 insertions, 0 deletions
diff --git a/trunk/src/emu/ioport.c b/trunk/src/emu/ioport.c
new file mode 100644
index 00000000000..380e23c3b4d
--- /dev/null
+++ b/trunk/src/emu/ioport.c
@@ -0,0 +1,4571 @@
+/***************************************************************************
+
+ ioport.c
+
+ Input/output port handling.
+
+****************************************************************************
+
+ Copyright Aaron Giles
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are
+ met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the
+ distribution.
+ * Neither the name 'MAME' nor the names of its contributors may be
+ used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY AARON GILES ''AS IS'' AND ANY EXPRESS OR
+ IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL AARON GILES BE LIABLE FOR ANY DIRECT,
+ INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ POSSIBILITY OF SUCH DAMAGE.
+
+****************************************************************************
+
+ Theory of operation
+
+ ------------
+ OSD controls
+ ------------
+
+ There are three types of controls that the OSD can provide as potential
+ input devices: digital controls, absolute analog controls, and relative
+ analog controls.
+
+ Digital controls have only two states: on or off. They are generally
+ mapped to buttons and digital joystick directions (like a gamepad or a
+ joystick hat). The OSD layer must return either 0 (off) or 1 (on) for
+ these types of controls.
+
+ Absolute analog controls are analog in the sense that they return a
+ range of values depending on how much a given control is moved, but they
+ are physically bounded. This means that there is a minimum and maximum
+ limit to how far the control can be moved. They are generally mapped to
+ analog joystick axes, lightguns, most PC steering wheels, and pedals.
+ The OSD layer must determine the minimum and maximum range of each
+ analog device and scale that to a value between -65536 and +65536
+ representing the position of the control. -65536 generally refers to
+ the topmost or leftmost position, while +65536 refers to the bottommost
+ or rightmost position. Note that pedals are a special case here, the
+ OSD layer needs to return half axis as full -65536 to + 65536 range.
+
+ Relative analog controls are analog as well, but are not physically
+ bounded. They can be moved continually in one direction without limit.
+ They are generally mapped to trackballs and mice. Because they are
+ unbounded, the OSD layer can only return delta values since the last
+ read. Because of this, it is difficult to scale appropriately. For
+ MAME's purposes, when mapping a mouse devices to a relative analog
+ control, one pixel of movement should correspond to 512 units. Other
+ analog control types should be scaled to return values of a similar
+ magnitude. Like absolute analog controls, negative values refer to
+ upward or leftward movement, while positive values refer to downward
+ or rightward movement.
+
+ -------------
+ Game controls
+ -------------
+
+ Similarly, the types of controls used by arcade games fall into the same
+ three categories: digital, absolute analog, and relative analog. The
+ tricky part is how to map any arbitrary type of OSD control to an
+ arbitrary type of game control.
+
+ Digital controls: used for game buttons and standard 4/8-way joysticks,
+ as well as many other types of game controls. Mapping an OSD digital
+ control to a game's OSD control is trivial. For OSD analog controls,
+ the MAME core does not directly support mapping any OSD analog devices
+ to digital controls. However, the OSD layer is free to enumerate digital
+ equivalents for analog devices. For example, each analog axis in the
+ Windows OSD code enumerates to two digital controls, one for the
+ negative direction (up/left) and one for the position direction
+ (down/right). When these "digital" inputs are queried, the OSD layer
+ checks the axis position against the center, adding in a dead zone,
+ and returns 0 or 1 to indicate its position.
+
+ Absolute analog controls: used for analog joysticks, lightguns, pedals,
+ and wheel controls. Mapping an OSD absolute analog control to this type
+ is easy. OSD relative analog controls can be mapped here as well by
+ accumulating the deltas and bounding the results. OSD digital controls
+ are mapped to these types of controls in pairs, one for a decrement and
+ one for an increment, but apart from that, operate the same as the OSD
+ relative analog controls by accumulating deltas and applying bounds.
+ The speed of the digital delta is user-configurable per analog input.
+ In addition, most absolute analog control types have an autocentering
+ feature that is activated when using the digital increment/decrement
+ sequences, which returns the control back to the center at a user-
+ controllable speed if no digital sequences are pressed.
+
+ Relative analog controls: used for trackballs and dial controls. Again,
+ mapping an OSD relative analog control to this type is straightforward.
+ OSD absolute analog controls can't map directly to these, but if the OSD
+ layer provides a digital equivalent for each direction, it can be done.
+ OSD digital controls map just like they do for absolute analog controls,
+ except that the accumulated deltas are not bounded, but rather wrap.
+
+***************************************************************************/
+
+#include "emu.h"
+#include "emuopts.h"
+#include "config.h"
+#include "xmlfile.h"
+#include "profiler.h"
+#include "ui.h"
+#include "uiinput.h"
+#include "debug/debugcon.h"
+
+#include <ctype.h>
+#include <time.h>
+
+// temporary: set this to 1 to enable the originally defined behavior that
+// a field specified via PORT_MODIFY which intersects a previously-defined
+// field completely wipes out the previous definition
+#define INPUT_PORT_OVERRIDE_FULLY_NUKES_PREVIOUS 1
+
+
+//**************************************************************************
+// DEBUGGING
+//**************************************************************************
+
+#define LOG_NATURAL_KEYBOARD 0
+
+
+
+//**************************************************************************
+// CONSTANTS
+//**************************************************************************
+
+const int SPACE_COUNT = 3;
+const int KEY_BUFFER_SIZE = 4096;
+const unicode_char INVALID_CHAR = '?';
+
+
+
+//**************************************************************************
+// TYPE DEFINITIONS
+//**************************************************************************
+
+// live analog field information
+class analog_field
+{
+ friend class simple_list<analog_field>;
+ friend class ioport_manager;
+ friend void ioport_field::set_user_settings(const ioport_field::user_settings &settings);
+
+public:
+ // construction/destruction
+ analog_field(ioport_field &field);
+
+ // getters
+ analog_field *next() const { return m_next; }
+ ioport_manager &manager() const { return m_field.manager(); }
+ ioport_field &field() const { return m_field; }
+ INT32 sensitivity() const { return m_sensitivity; }
+ bool reverse() const { return m_reverse; }
+ INT32 delta() const { return m_delta; }
+ INT32 centerdelta() const { return m_centerdelta; }
+
+ // readers
+ void read(ioport_value &value);
+ float crosshair_read();
+ void frame_update(running_machine &machine);
+
+private:
+ // helpers
+ INT32 apply_min_max(INT32 value) const;
+ INT32 apply_settings(INT32 value) const;
+ INT32 apply_sensitivity(INT32 value) const;
+ INT32 apply_inverse_sensitivity(INT32 value) const;
+
+ // internal state
+ analog_field * m_next; // link to the next analog state for this port
+ ioport_field & m_field; // pointer to the input field referenced
+
+ // adjusted values (right-justified and tweaked)
+ UINT8 m_shift; // shift to align final value in the port
+ INT32 m_adjdefvalue; // adjusted default value from the config
+ INT32 m_adjmin; // adjusted minimum value from the config
+ INT32 m_adjmax; // adjusted maximum value from the config
+
+ // live values of configurable parameters
+ INT32 m_sensitivity; // current live sensitivity (100=normal)
+ bool m_reverse; // current live reverse flag
+ INT32 m_delta; // current live delta to apply each frame a digital inc/dec key is pressed
+ INT32 m_centerdelta; // current live delta to apply each frame no digital inputs are pressed
+
+ // live analog value tracking
+ INT32 m_accum; // accumulated value (including relative adjustments)
+ INT32 m_previous; // previous adjusted value
+ INT32 m_previousanalog; // previous analog value
+
+ // parameters for modifying live values
+ INT32 m_minimum; // minimum adjusted value
+ INT32 m_maximum; // maximum adjusted value
+ INT32 m_center; // center adjusted value for autocentering
+ INT32 m_reverse_val; // value where we subtract from to reverse directions
+
+ // scaling factors
+ INT64 m_scalepos; // scale factor to apply to positive adjusted values
+ INT64 m_scaleneg; // scale factor to apply to negative adjusted values
+ INT64 m_keyscalepos; // scale factor to apply to the key delta field when pos
+ INT64 m_keyscaleneg; // scale factor to apply to the key delta field when neg
+ INT64 m_positionalscale; // scale factor to divide a joystick into positions
+
+ // misc flags
+ bool m_absolute; // is this an absolute or relative input?
+ bool m_wraps; // does the control wrap around?
+ bool m_autocenter; // autocenter this input?
+ bool m_single_scale; // scale joystick differently if default is between min/max
+ bool m_interpolate; // should we do linear interpolation for mid-frame reads?
+ bool m_lastdigital; // was the last modification caused by a digital form?
+};
+
+
+// live device field information
+class dynamic_field
+{
+ friend class simple_list<dynamic_field>;
+
+public:
+ // construction/destruction
+ dynamic_field(ioport_field &field);
+
+ // getters
+ dynamic_field *next() const { return m_next; }
+ ioport_field &field() const { return m_field; }
+
+ // read/write
+ void read(ioport_value &result);
+ void write(ioport_value newval);
+
+private:
+ // internal state
+ dynamic_field * m_next; // linked list of info for this port
+ ioport_field & m_field; // reference to the input field
+ UINT8 m_shift; // shift to apply to the final result
+ ioport_value m_oldval; // last value
+};
+
+
+// internal live state of an input field
+struct ioport_field_live
+{
+ // construction/destruction
+ ioport_field_live(ioport_field &field, analog_field *analog);
+
+ // public state
+ analog_field * analog; // pointer to live analog data if this is an analog field
+ digital_joystick * joystick; // pointer to digital joystick information
+ input_seq seq[SEQ_TYPE_TOTAL];// currently configured input sequences
+ ioport_value value; // current value of this port
+ UINT8 impulse; // counter for impulse controls
+ bool last; // were we pressed last time?
+ digital_joystick::direction_t joydir; // digital joystick direction index
+ astring name; // overridden name
+};
+
+
+// internal live state of an input port
+struct ioport_port_live
+{
+ // construction/destruction
+ ioport_port_live(ioport_port &port);
+
+ // public state
+ simple_list<analog_field> analoglist; // list of analog port info
+ simple_list<dynamic_field> readlist; // list of dynamic read fields
+ simple_list<dynamic_field> writelist; // list of dynamic write fields
+ ioport_value defvalue; // combined default value across the port
+ ioport_value digital; // current value from all digital inputs
+ ioport_value outputvalue; // current value for outputs
+};
+
+
+// character information
+struct char_info
+{
+ unicode_char ch;
+ const char *name;
+ const char *alternate; // alternative string, in UTF-8
+
+ static const char_info *find(unicode_char target);
+};
+
+
+
+//**************************************************************************
+// INLINE FUNCTIONS
+//**************************************************************************
+
+//-------------------------------------------------
+// compute_scale -- compute an 8.24 scale value
+// from a numerator and a denominator
+//-------------------------------------------------
+
+inline INT64 compute_scale(INT32 num, INT32 den)
+{
+ return (INT64(num) << 24) / den;
+}
+
+
+//-------------------------------------------------
+// recip_scale -- compute an 8.24 reciprocal of
+// an 8.24 scale value
+//-------------------------------------------------
+
+inline INT64 recip_scale(INT64 scale)
+{
+ return (INT64(1) << 48) / scale;
+}
+
+
+//-------------------------------------------------
+// apply_scale -- apply an 8.24 scale value to
+// a 32-bit value
+//-------------------------------------------------
+
+inline INT32 apply_scale(INT32 value, INT64 scale)
+{
+ return (INT64(value) * scale) >> 24;
+}
+
+
+
+//**************************************************************************
+// GLOBAL VARIABLES
+//**************************************************************************
+
+// XML attributes for the different types
+static const char *const seqtypestrings[] = { "standard", "increment", "decrement" };
+
+// master character info table
+static const char_info charinfo[] =
+{
+ { 0x0008, "Backspace", NULL }, // Backspace
+ { 0x0009, "Tab", " " }, // Tab
+ { 0x000c, "Clear", NULL }, // Clear
+ { 0x000d, "Enter", NULL }, // Enter
+ { 0x001a, "Esc", NULL }, // Esc
+ { 0x0020, "Space", " " }, // Space
+ { 0x0061, NULL, "A" }, // a
+ { 0x0062, NULL, "B" }, // b
+ { 0x0063, NULL, "C" }, // c
+ { 0x0064, NULL, "D" }, // d
+ { 0x0065, NULL, "E" }, // e
+ { 0x0066, NULL, "F" }, // f
+ { 0x0067, NULL, "G" }, // g
+ { 0x0068, NULL, "H" }, // h
+ { 0x0069, NULL, "I" }, // i
+ { 0x006a, NULL, "J" }, // j
+ { 0x006b, NULL, "K" }, // k
+ { 0x006c, NULL, "L" }, // l
+ { 0x006d, NULL, "M" }, // m
+ { 0x006e, NULL, "N" }, // n
+ { 0x006f, NULL, "O" }, // o
+ { 0x0070, NULL, "P" }, // p
+ { 0x0071, NULL, "Q" }, // q
+ { 0x0072, NULL, "R" }, // r
+ { 0x0073, NULL, "S" }, // s
+ { 0x0074, NULL, "T" }, // t
+ { 0x0075, NULL, "U" }, // u
+ { 0x0076, NULL, "V" }, // v
+ { 0x0077, NULL, "W" }, // w
+ { 0x0078, NULL, "X" }, // x
+ { 0x0079, NULL, "Y" }, // y
+ { 0x007a, NULL, "Z" }, // z
+ { 0x00a0, NULL, " " }, // non breaking space
+ { 0x00a1, NULL, "!" }, // inverted exclaimation mark
+ { 0x00a6, NULL, "|" }, // broken bar
+ { 0x00a9, NULL, "(c)" }, // copyright sign
+ { 0x00ab, NULL, "<<" }, // left pointing double angle
+ { 0x00ae, NULL, "(r)" }, // registered sign
+ { 0x00bb, NULL, ">>" }, // right pointing double angle
+ { 0x00bc, NULL, "1/4" }, // vulgar fraction one quarter
+ { 0x00bd, NULL, "1/2" }, // vulgar fraction one half
+ { 0x00be, NULL, "3/4" }, // vulgar fraction three quarters
+ { 0x00bf, NULL, "?" }, // inverted question mark
+ { 0x00c0, NULL, "A" }, // 'A' grave
+ { 0x00c1, NULL, "A" }, // 'A' acute
+ { 0x00c2, NULL, "A" }, // 'A' circumflex
+ { 0x00c3, NULL, "A" }, // 'A' tilde
+ { 0x00c4, NULL, "A" }, // 'A' diaeresis
+ { 0x00c5, NULL, "A" }, // 'A' ring above
+ { 0x00c6, NULL, "AE" }, // 'AE' ligature
+ { 0x00c7, NULL, "C" }, // 'C' cedilla
+ { 0x00c8, NULL, "E" }, // 'E' grave
+ { 0x00c9, NULL, "E" }, // 'E' acute
+ { 0x00ca, NULL, "E" }, // 'E' circumflex
+ { 0x00cb, NULL, "E" }, // 'E' diaeresis
+ { 0x00cc, NULL, "I" }, // 'I' grave
+ { 0x00cd, NULL, "I" }, // 'I' acute
+ { 0x00ce, NULL, "I" }, // 'I' circumflex
+ { 0x00cf, NULL, "I" }, // 'I' diaeresis
+ { 0x00d0, NULL, "D" }, // 'ETH'
+ { 0x00d1, NULL, "N" }, // 'N' tilde
+ { 0x00d2, NULL, "O" }, // 'O' grave
+ { 0x00d3, NULL, "O" }, // 'O' acute
+ { 0x00d4, NULL, "O" }, // 'O' circumflex
+ { 0x00d5, NULL, "O" }, // 'O' tilde
+ { 0x00d6, NULL, "O" }, // 'O' diaeresis
+ { 0x00d7, NULL, "X" }, // multiplication sign
+ { 0x00d8, NULL, "O" }, // 'O' stroke
+ { 0x00d9, NULL, "U" }, // 'U' grave
+ { 0x00da, NULL, "U" }, // 'U' acute
+ { 0x00db, NULL, "U" }, // 'U' circumflex
+ { 0x00dc, NULL, "U" }, // 'U' diaeresis
+ { 0x00dd, NULL, "Y" }, // 'Y' acute
+ { 0x00df, NULL, "SS" }, // sharp S
+ { 0x00e0, NULL, "a" }, // 'a' grave
+ { 0x00e1, NULL, "a" }, // 'a' acute
+ { 0x00e2, NULL, "a" }, // 'a' circumflex
+ { 0x00e3, NULL, "a" }, // 'a' tilde
+ { 0x00e4, NULL, "a" }, // 'a' diaeresis
+ { 0x00e5, NULL, "a" }, // 'a' ring above
+ { 0x00e6, NULL, "ae" }, // 'ae' ligature
+ { 0x00e7, NULL, "c" }, // 'c' cedilla
+ { 0x00e8, NULL, "e" }, // 'e' grave
+ { 0x00e9, NULL, "e" }, // 'e' acute
+ { 0x00ea, NULL, "e" }, // 'e' circumflex
+ { 0x00eb, NULL, "e" }, // 'e' diaeresis
+ { 0x00ec, NULL, "i" }, // 'i' grave
+ { 0x00ed, NULL, "i" }, // 'i' acute
+ { 0x00ee, NULL, "i" }, // 'i' circumflex
+ { 0x00ef, NULL, "i" }, // 'i' diaeresis
+ { 0x00f0, NULL, "d" }, // 'eth'
+ { 0x00f1, NULL, "n" }, // 'n' tilde
+ { 0x00f2, NULL, "o" }, // 'o' grave
+ { 0x00f3, NULL, "o" }, // 'o' acute
+ { 0x00f4, NULL, "o" }, // 'o' circumflex
+ { 0x00f5, NULL, "o" }, // 'o' tilde
+ { 0x00f6, NULL, "o" }, // 'o' diaeresis
+ { 0x00f8, NULL, "o" }, // 'o' stroke
+ { 0x00f9, NULL, "u" }, // 'u' grave
+ { 0x00fa, NULL, "u" }, // 'u' acute
+ { 0x00fb, NULL, "u" }, // 'u' circumflex
+ { 0x00fc, NULL, "u" }, // 'u' diaeresis
+ { 0x00fd, NULL, "y" }, // 'y' acute
+ { 0x00ff, NULL, "y" }, // 'y' diaeresis
+ { 0x2010, NULL, "-" }, // hyphen
+ { 0x2011, NULL, "-" }, // non-breaking hyphen
+ { 0x2012, NULL, "-" }, // figure dash
+ { 0x2013, NULL, "-" }, // en dash
+ { 0x2014, NULL, "-" }, // em dash
+ { 0x2015, NULL, "-" }, // horizontal dash
+ { 0x2018, NULL, "\'" }, // left single quotation mark
+ { 0x2019, NULL, "\'" }, // right single quotation mark
+ { 0x201a, NULL, "\'" }, // single low quotation mark
+ { 0x201b, NULL, "\'" }, // single high reversed quotation mark
+ { 0x201c, NULL, "\"" }, // left double quotation mark
+ { 0x201d, NULL, "\"" }, // right double quotation mark
+ { 0x201e, NULL, "\"" }, // double low quotation mark
+ { 0x201f, NULL, "\"" }, // double high reversed quotation mark
+ { 0x2024, NULL, "." }, // one dot leader
+ { 0x2025, NULL, ".." }, // two dot leader
+ { 0x2026, NULL, "..." }, // horizontal ellipsis
+ { 0x2047, NULL, "??" }, // double question mark
+ { 0x2048, NULL, "?!" }, // question exclamation mark
+ { 0x2049, NULL, "!?" }, // exclamation question mark
+ { 0xff01, NULL, "!" }, // fullwidth exclamation point
+ { 0xff02, NULL, "\"" }, // fullwidth quotation mark
+ { 0xff03, NULL, "#" }, // fullwidth number sign
+ { 0xff04, NULL, "$" }, // fullwidth dollar sign
+ { 0xff05, NULL, "%" }, // fullwidth percent sign
+ { 0xff06, NULL, "&" }, // fullwidth ampersand
+ { 0xff07, NULL, "\'" }, // fullwidth apostrophe
+ { 0xff08, NULL, "(" }, // fullwidth left parenthesis
+ { 0xff09, NULL, ")" }, // fullwidth right parenthesis
+ { 0xff0a, NULL, "*" }, // fullwidth asterisk
+ { 0xff0b, NULL, "+" }, // fullwidth plus
+ { 0xff0c, NULL, "," }, // fullwidth comma
+ { 0xff0d, NULL, "-" }, // fullwidth minus
+ { 0xff0e, NULL, "." }, // fullwidth period
+ { 0xff0f, NULL, "/" }, // fullwidth slash
+ { 0xff10, NULL, "0" }, // fullwidth zero
+ { 0xff11, NULL, "1" }, // fullwidth one
+ { 0xff12, NULL, "2" }, // fullwidth two
+ { 0xff13, NULL, "3" }, // fullwidth three
+ { 0xff14, NULL, "4" }, // fullwidth four
+ { 0xff15, NULL, "5" }, // fullwidth five
+ { 0xff16, NULL, "6" }, // fullwidth six
+ { 0xff17, NULL, "7" }, // fullwidth seven
+ { 0xff18, NULL, "8" }, // fullwidth eight
+ { 0xff19, NULL, "9" }, // fullwidth nine
+ { 0xff1a, NULL, ":" }, // fullwidth colon
+ { 0xff1b, NULL, ";" }, // fullwidth semicolon
+ { 0xff1c, NULL, "<" }, // fullwidth less than sign
+ { 0xff1d, NULL, "=" }, // fullwidth equals sign
+ { 0xff1e, NULL, ">" }, // fullwidth greater than sign
+ { 0xff1f, NULL, "?" }, // fullwidth question mark
+ { 0xff20, NULL, "@" }, // fullwidth at sign
+ { 0xff21, NULL, "A" }, // fullwidth 'A'
+ { 0xff22, NULL, "B" }, // fullwidth 'B'
+ { 0xff23, NULL, "C" }, // fullwidth 'C'
+ { 0xff24, NULL, "D" }, // fullwidth 'D'
+ { 0xff25, NULL, "E" }, // fullwidth 'E'
+ { 0xff26, NULL, "F" }, // fullwidth 'F'
+ { 0xff27, NULL, "G" }, // fullwidth 'G'
+ { 0xff28, NULL, "H" }, // fullwidth 'H'
+ { 0xff29, NULL, "I" }, // fullwidth 'I'
+ { 0xff2a, NULL, "J" }, // fullwidth 'J'
+ { 0xff2b, NULL, "K" }, // fullwidth 'K'
+ { 0xff2c, NULL, "L" }, // fullwidth 'L'
+ { 0xff2d, NULL, "M" }, // fullwidth 'M'
+ { 0xff2e, NULL, "N" }, // fullwidth 'N'
+ { 0xff2f, NULL, "O" }, // fullwidth 'O'
+ { 0xff30, NULL, "P" }, // fullwidth 'P'
+ { 0xff31, NULL, "Q" }, // fullwidth 'Q'
+ { 0xff32, NULL, "R" }, // fullwidth 'R'
+ { 0xff33, NULL, "S" }, // fullwidth 'S'
+ { 0xff34, NULL, "T" }, // fullwidth 'T'
+ { 0xff35, NULL, "U" }, // fullwidth 'U'
+ { 0xff36, NULL, "V" }, // fullwidth 'V'
+ { 0xff37, NULL, "W" }, // fullwidth 'W'
+ { 0xff38, NULL, "X" }, // fullwidth 'X'
+ { 0xff39, NULL, "Y" }, // fullwidth 'Y'
+ { 0xff3a, NULL, "Z" }, // fullwidth 'Z'
+ { 0xff3b, NULL, "[" }, // fullwidth left bracket
+ { 0xff3c, NULL, "\\" }, // fullwidth backslash
+ { 0xff3d, NULL, "]" }, // fullwidth right bracket
+ { 0xff3e, NULL, "^" }, // fullwidth caret
+ { 0xff3f, NULL, "_" }, // fullwidth underscore
+ { 0xff40, NULL, "`" }, // fullwidth backquote
+ { 0xff41, NULL, "a" }, // fullwidth 'a'
+ { 0xff42, NULL, "b" }, // fullwidth 'b'
+ { 0xff43, NULL, "c" }, // fullwidth 'c'
+ { 0xff44, NULL, "d" }, // fullwidth 'd'
+ { 0xff45, NULL, "e" }, // fullwidth 'e'
+ { 0xff46, NULL, "f" }, // fullwidth 'f'
+ { 0xff47, NULL, "g" }, // fullwidth 'g'
+ { 0xff48, NULL, "h" }, // fullwidth 'h'
+ { 0xff49, NULL, "i" }, // fullwidth 'i'
+ { 0xff4a, NULL, "j" }, // fullwidth 'j'
+ { 0xff4b, NULL, "k" }, // fullwidth 'k'
+ { 0xff4c, NULL, "l" }, // fullwidth 'l'
+ { 0xff4d, NULL, "m" }, // fullwidth 'm'
+ { 0xff4e, NULL, "n" }, // fullwidth 'n'
+ { 0xff4f, NULL, "o" }, // fullwidth 'o'
+ { 0xff50, NULL, "p" }, // fullwidth 'p'
+ { 0xff51, NULL, "q" }, // fullwidth 'q'
+ { 0xff52, NULL, "r" }, // fullwidth 'r'
+ { 0xff53, NULL, "s" }, // fullwidth 's'
+ { 0xff54, NULL, "t" }, // fullwidth 't'
+ { 0xff55, NULL, "u" }, // fullwidth 'u'
+ { 0xff56, NULL, "v" }, // fullwidth 'v'
+ { 0xff57, NULL, "w" }, // fullwidth 'w'
+ { 0xff58, NULL, "x" }, // fullwidth 'x'
+ { 0xff59, NULL, "y" }, // fullwidth 'y'
+ { 0xff5a, NULL, "z" }, // fullwidth 'z'
+ { 0xff5b, NULL, "{" }, // fullwidth left brace
+ { 0xff5c, NULL, "|" }, // fullwidth vertical bar
+ { 0xff5d, NULL, "}" }, // fullwidth right brace
+ { 0xff5e, NULL, "~" }, // fullwidth tilde
+ { 0xff5f, NULL, "((" }, // fullwidth double left parenthesis
+ { 0xff60, NULL, "))" }, // fullwidth double right parenthesis
+ { 0xffe0, NULL, "\xC2\xA2" }, // fullwidth cent sign
+ { 0xffe1, NULL, "\xC2\xA3" }, // fullwidth pound sign
+ { 0xffe4, NULL, "\xC2\xA4" }, // fullwidth broken bar
+ { 0xffe5, NULL, "\xC2\xA5" }, // fullwidth yen sign
+ { 0xffe6, NULL, "\xE2\x82\xA9" }, // fullwidth won sign
+ { 0xffe9, NULL, "\xE2\x86\x90" }, // fullwidth left arrow
+ { 0xffea, NULL, "\xE2\x86\x91" }, // fullwidth up arrow
+ { 0xffeb, NULL, "\xE2\x86\x92" }, // fullwidth right arrow
+ { 0xffec, NULL, "\xE2\x86\x93" }, // fullwidth down arrow
+ { 0xffed, NULL, "\xE2\x96\xAA" }, // fullwidth solid box
+ { 0xffee, NULL, "\xE2\x97\xA6" }, // fullwidth open circle
+ { UCHAR_SHIFT_1, "Shift", NULL }, // Shift key
+ { UCHAR_SHIFT_2, "Ctrl", NULL }, // Ctrl key
+ { UCHAR_MAMEKEY(F1), "F1", NULL }, // F1 function key
+ { UCHAR_MAMEKEY(F2), "F2", NULL }, // F2 function key
+ { UCHAR_MAMEKEY(F3), "F3", NULL }, // F3 function key
+ { UCHAR_MAMEKEY(F4), "F4", NULL }, // F4 function key
+ { UCHAR_MAMEKEY(F5), "F5", NULL }, // F5 function key
+ { UCHAR_MAMEKEY(F6), "F6", NULL }, // F6 function key
+ { UCHAR_MAMEKEY(F7), "F7", NULL }, // F7 function key
+ { UCHAR_MAMEKEY(F8), "F8", NULL }, // F8 function key
+ { UCHAR_MAMEKEY(F9), "F9", NULL }, // F9 function key
+ { UCHAR_MAMEKEY(F10), "F10", NULL }, // F10 function key
+ { UCHAR_MAMEKEY(F11), "F11", NULL }, // F11 function key
+ { UCHAR_MAMEKEY(F12), "F12", NULL }, // F12 function key
+ { UCHAR_MAMEKEY(F13), "F13", NULL }, // F13 function key
+ { UCHAR_MAMEKEY(F14), "F14", NULL }, // F14 function key
+ { UCHAR_MAMEKEY(F15), "F15", NULL }, // F15 function key
+ { UCHAR_MAMEKEY(ESC), "Esc", "\033" }, // Esc key
+ { UCHAR_MAMEKEY(INSERT), "Insert", NULL }, // Insert key
+ { UCHAR_MAMEKEY(DEL), "Delete", "\010" }, // Delete key
+ { UCHAR_MAMEKEY(HOME), "Home", "\014" }, // Home key
+ { UCHAR_MAMEKEY(END), "End", NULL }, // End key
+ { UCHAR_MAMEKEY(PGUP), "Page Up", NULL }, // Page Up key
+ { UCHAR_MAMEKEY(PGDN), "Page Down", NULL }, // Page Down key
+ { UCHAR_MAMEKEY(LEFT), "Cursor Left", NULL }, // Cursor Left
+ { UCHAR_MAMEKEY(RIGHT), "Cursor Right", NULL }, // Cursor Right
+ { UCHAR_MAMEKEY(UP), "Cursor Up", NULL }, // Cursor Up
+ { UCHAR_MAMEKEY(DOWN), "Cursor Down", NULL }, // Cursor Down
+ { UCHAR_MAMEKEY(0_PAD), "Keypad 0", NULL }, // 0 on the numeric keypad
+ { UCHAR_MAMEKEY(1_PAD), "Keypad 1", NULL }, // 1 on the numeric keypad
+ { UCHAR_MAMEKEY(2_PAD), "Keypad 2", NULL }, // 2 on the numeric keypad
+ { UCHAR_MAMEKEY(3_PAD), "Keypad 3", NULL }, // 3 on the numeric keypad
+ { UCHAR_MAMEKEY(4_PAD), "Keypad 4", NULL }, // 4 on the numeric keypad
+ { UCHAR_MAMEKEY(5_PAD), "Keypad 5", NULL }, // 5 on the numeric keypad
+ { UCHAR_MAMEKEY(6_PAD), "Keypad 6", NULL }, // 6 on the numeric keypad
+ { UCHAR_MAMEKEY(7_PAD), "Keypad 7", NULL }, // 7 on the numeric keypad
+ { UCHAR_MAMEKEY(8_PAD), "Keypad 8", NULL }, // 8 on the numeric keypad
+ { UCHAR_MAMEKEY(9_PAD), "Keypad 9", NULL }, // 9 on the numeric keypad
+ { UCHAR_MAMEKEY(SLASH_PAD), "Keypad /", NULL }, // / on the numeric keypad
+ { UCHAR_MAMEKEY(ASTERISK), "Keypad *", NULL }, // * on the numeric keypad
+ { UCHAR_MAMEKEY(MINUS_PAD), "Keypad -", NULL }, // - on the numeric Keypad
+ { UCHAR_MAMEKEY(PLUS_PAD), "Keypad +", NULL }, // + on the numeric Keypad
+ { UCHAR_MAMEKEY(DEL_PAD), "Keypad .", NULL }, // . on the numeric keypad
+ { UCHAR_MAMEKEY(ENTER_PAD), "Keypad Enter", NULL }, // Enter on the numeric keypad
+ { UCHAR_MAMEKEY(PRTSCR), "Print Screen", NULL }, // Print Screen key
+ { UCHAR_MAMEKEY(PAUSE), "Pause", NULL }, // Pause key
+ { UCHAR_MAMEKEY(LSHIFT), "Left Shift", NULL }, // Left Shift key
+ { UCHAR_MAMEKEY(RSHIFT), "Right Shift", NULL }, // Right Shift key
+ { UCHAR_MAMEKEY(LCONTROL), "Left Ctrl", NULL }, // Left Control key
+ { UCHAR_MAMEKEY(RCONTROL), "Right Ctrl", NULL }, // Right Control key
+ { UCHAR_MAMEKEY(LALT), "Left Alt", NULL }, // Left Alt key
+ { UCHAR_MAMEKEY(RALT), "Right Alt", NULL }, // Right Alt key
+ { UCHAR_MAMEKEY(SCRLOCK), "Scroll Lock", NULL }, // Scroll Lock key
+ { UCHAR_MAMEKEY(NUMLOCK), "Num Lock", NULL }, // Num Lock key
+ { UCHAR_MAMEKEY(CAPSLOCK), "Caps Lock", NULL }, // Caps Lock key
+ { UCHAR_MAMEKEY(LWIN), "Left Win", NULL }, // Left Win key
+ { UCHAR_MAMEKEY(RWIN), "Right Win", NULL }, // Right Win key
+ { UCHAR_MAMEKEY(MENU), "Menu", NULL }, // Menu key
+ { UCHAR_MAMEKEY(CANCEL), "Break", NULL } // Break/Pause key
+};
+
+
+
+//**************************************************************************
+// COMMON SHARED STRINGS
+//**************************************************************************
+
+static const struct
+{
+ UINT32 id;
+ const char *string;
+} input_port_default_strings[] =
+{
+ { INPUT_STRING_Off, "Off" },
+ { INPUT_STRING_On, "On" },
+ { INPUT_STRING_No, "No" },
+ { INPUT_STRING_Yes, "Yes" },
+ { INPUT_STRING_Lives, "Lives" },
+ { INPUT_STRING_Bonus_Life, "Bonus Life" },
+ { INPUT_STRING_Difficulty, "Difficulty" },
+ { INPUT_STRING_Demo_Sounds, "Demo Sounds" },
+ { INPUT_STRING_Coinage, "Coinage" },
+ { INPUT_STRING_Coin_A, "Coin A" },
+ { INPUT_STRING_Coin_B, "Coin B" },
+ { INPUT_STRING_9C_1C, "9 Coins/1 Credit" },
+ { INPUT_STRING_8C_1C, "8 Coins/1 Credit" },
+ { INPUT_STRING_7C_1C, "7 Coins/1 Credit" },
+ { INPUT_STRING_6C_1C, "6 Coins/1 Credit" },
+ { INPUT_STRING_5C_1C, "5 Coins/1 Credit" },
+ { INPUT_STRING_4C_1C, "4 Coins/1 Credit" },
+ { INPUT_STRING_3C_1C, "3 Coins/1 Credit" },
+ { INPUT_STRING_8C_3C, "8 Coins/3 Credits" },
+ { INPUT_STRING_4C_2C, "4 Coins/2 Credits" },
+ { INPUT_STRING_2C_1C, "2 Coins/1 Credit" },
+ { INPUT_STRING_5C_3C, "5 Coins/3 Credits" },
+ { INPUT_STRING_3C_2C, "3 Coins/2 Credits" },
+ { INPUT_STRING_4C_3C, "4 Coins/3 Credits" },
+ { INPUT_STRING_4C_4C, "4 Coins/4 Credits" },
+ { INPUT_STRING_3C_3C, "3 Coins/3 Credits" },
+ { INPUT_STRING_2C_2C, "2 Coins/2 Credits" },
+ { INPUT_STRING_1C_1C, "1 Coin/1 Credit" },
+ { INPUT_STRING_4C_5C, "4 Coins/5 Credits" },
+ { INPUT_STRING_3C_4C, "3 Coins/4 Credits" },
+ { INPUT_STRING_2C_3C, "2 Coins/3 Credits" },
+ { INPUT_STRING_4C_7C, "4 Coins/7 Credits" },
+ { INPUT_STRING_2C_4C, "2 Coins/4 Credits" },
+ { INPUT_STRING_1C_2C, "1 Coin/2 Credits" },
+ { INPUT_STRING_2C_5C, "2 Coins/5 Credits" },
+ { INPUT_STRING_2C_6C, "2 Coins/6 Credits" },
+ { INPUT_STRING_1C_3C, "1 Coin/3 Credits" },
+ { INPUT_STRING_2C_7C, "2 Coins/7 Credits" },
+ { INPUT_STRING_2C_8C, "2 Coins/8 Credits" },
+ { INPUT_STRING_1C_4C, "1 Coin/4 Credits" },
+ { INPUT_STRING_1C_5C, "1 Coin/5 Credits" },
+ { INPUT_STRING_1C_6C, "1 Coin/6 Credits" },
+ { INPUT_STRING_1C_7C, "1 Coin/7 Credits" },
+ { INPUT_STRING_1C_8C, "1 Coin/8 Credits" },
+ { INPUT_STRING_1C_9C, "1 Coin/9 Credits" },
+ { INPUT_STRING_Free_Play, "Free Play" },
+ { INPUT_STRING_Cabinet, "Cabinet" },
+ { INPUT_STRING_Upright, "Upright" },
+ { INPUT_STRING_Cocktail, "Cocktail" },
+ { INPUT_STRING_Flip_Screen, "Flip Screen" },
+ { INPUT_STRING_Service_Mode, "Service Mode" },
+ { INPUT_STRING_Pause, "Pause" },
+ { INPUT_STRING_Test, "Test" },
+ { INPUT_STRING_Tilt, "Tilt" },
+ { INPUT_STRING_Version, "Version" },
+ { INPUT_STRING_Region, "Region" },
+ { INPUT_STRING_International, "International" },
+ { INPUT_STRING_Japan, "Japan" },
+ { INPUT_STRING_USA, "USA" },
+ { INPUT_STRING_Europe, "Europe" },
+ { INPUT_STRING_Asia, "Asia" },
+ { INPUT_STRING_China, "China" },
+ { INPUT_STRING_Hong_Kong, "Hong Kong" },
+ { INPUT_STRING_Korea, "Korea" },
+ { INPUT_STRING_Southeast_Asia, "Southeast Asia" },
+ { INPUT_STRING_Taiwan, "Taiwan" },
+ { INPUT_STRING_World, "World" },
+ { INPUT_STRING_Language, "Language" },
+ { INPUT_STRING_English, "English" },
+ { INPUT_STRING_Japanese, "Japanese" },
+ { INPUT_STRING_Chinese, "Chinese" },
+ { INPUT_STRING_French, "French" },
+ { INPUT_STRING_German, "German" },
+ { INPUT_STRING_Italian, "Italian" },
+ { INPUT_STRING_Korean, "Korean" },
+ { INPUT_STRING_Spanish, "Spanish" },
+ { INPUT_STRING_Very_Easy, "Very Easy" },
+ { INPUT_STRING_Easiest, "Easiest" },
+ { INPUT_STRING_Easier, "Easier" },
+ { INPUT_STRING_Easy, "Easy" },
+ { INPUT_STRING_Medium_Easy, "Medium Easy" },
+ { INPUT_STRING_Normal, "Normal" },
+ { INPUT_STRING_Medium, "Medium" },
+ { INPUT_STRING_Medium_Hard, "Medium Hard" },
+ { INPUT_STRING_Hard, "Hard" },
+ { INPUT_STRING_Harder, "Harder" },
+ { INPUT_STRING_Hardest, "Hardest" },
+ { INPUT_STRING_Very_Hard, "Very Hard" },
+ { INPUT_STRING_Medium_Difficult, "Medium Difficult" },
+ { INPUT_STRING_Difficult, "Difficult" },
+ { INPUT_STRING_Very_Difficult, "Very Difficult" },
+ { INPUT_STRING_Very_Low, "Very Low" },
+ { INPUT_STRING_Low, "Low" },
+ { INPUT_STRING_High, "High" },
+ { INPUT_STRING_Higher, "Higher" },
+ { INPUT_STRING_Highest, "Highest" },
+ { INPUT_STRING_Very_High, "Very High" },
+ { INPUT_STRING_Players, "Players" },
+ { INPUT_STRING_Controls, "Controls" },
+ { INPUT_STRING_Dual, "Dual" },
+ { INPUT_STRING_Single, "Single" },
+ { INPUT_STRING_Game_Time, "Game Time" },
+ { INPUT_STRING_Continue_Price, "Continue Price" },
+ { INPUT_STRING_Controller, "Controller" },
+ { INPUT_STRING_Light_Gun, "Light Gun" },
+ { INPUT_STRING_Joystick, "Joystick" },
+ { INPUT_STRING_Trackball, "Trackball" },
+ { INPUT_STRING_Continues, "Continues" },
+ { INPUT_STRING_Allow_Continue, "Allow Continue" },
+ { INPUT_STRING_Level_Select, "Level Select" },
+ { INPUT_STRING_Infinite, "Infinite" },
+ { INPUT_STRING_Stereo, "Stereo" },
+ { INPUT_STRING_Mono, "Mono" },
+ { INPUT_STRING_Unused, "Unused" },
+ { INPUT_STRING_Unknown, "Unknown" },
+ { INPUT_STRING_Standard, "Standard" },
+ { INPUT_STRING_Reverse, "Reverse" },
+ { INPUT_STRING_Alternate, "Alternate" },
+ { INPUT_STRING_None, "None" },
+};
+
+
+
+//**************************************************************************
+// BUILT-IN CORE MAPPINGS
+//**************************************************************************
+
+#include "inpttype.h"
+
+
+
+//**************************************************************************
+// PORT CONFIGURATIONS
+//**************************************************************************
+
+//**************************************************************************
+// I/O PORT LIST
+//**************************************************************************
+
+//-------------------------------------------------
+// append - append the given device's input ports
+// to the current list
+//-------------------------------------------------
+
+void ioport_list::append(device_t &device, astring &errorbuf)
+{
+ // no constructor, no list
+ ioport_constructor constructor = device.input_ports();
+ if (constructor == NULL)
+ return;
+
+ // reset error buffer
+ errorbuf.reset();
+
+ // detokenize into the list
+ (*constructor)(device, *this, errorbuf);
+
+ // collapse fields and sort the list
+ for (ioport_port *port = first(); port != NULL; port = port->next())
+ port->collapse_fields(errorbuf);
+}
+
+
+
+//**************************************************************************
+// INPUT TYPE ENTRY
+//**************************************************************************
+
+//-------------------------------------------------
+// input_type_entry - constructors
+//-------------------------------------------------
+
+input_type_entry::input_type_entry(ioport_type type, ioport_group group, int player, const char *token, const char *name, input_seq standard)
+ : m_next(NULL),
+ m_type(type),
+ m_group(group),
+ m_player(player),
+ m_token(token),
+ m_name(name)
+{
+ m_defseq[SEQ_TYPE_STANDARD] = m_seq[SEQ_TYPE_STANDARD] = standard;
+}
+
+input_type_entry::input_type_entry(ioport_type type, ioport_group group, int player, const char *token, const char *name, input_seq standard, input_seq decrement, input_seq increment)
+ : m_next(NULL),
+ m_type(type),
+ m_group(group),
+ m_player(player),
+ m_token(token),
+ m_name(name)
+{
+ m_defseq[SEQ_TYPE_STANDARD] = m_seq[SEQ_TYPE_STANDARD] = standard;
+ m_defseq[SEQ_TYPE_INCREMENT] = m_seq[SEQ_TYPE_INCREMENT] = increment;
+ m_defseq[SEQ_TYPE_DECREMENT] = m_seq[SEQ_TYPE_DECREMENT] = decrement;
+}
+
+
+//-------------------------------------------------
+// configure_osd - set the token and name of an
+// OSD entry
+//-------------------------------------------------
+
+void input_type_entry::configure_osd(const char *token, const char *name)
+{
+ assert(m_type >= IPT_OSD_1 && m_type <= IPT_OSD_16);
+ m_token = token;
+ m_name = name;
+}
+
+
+//**************************************************************************
+// DIGITAL JOYSTICKS
+//**************************************************************************
+
+//-------------------------------------------------
+// digital_joystick - constructor
+//-------------------------------------------------
+
+digital_joystick::digital_joystick(int player, int number)
+ : m_player(player),
+ m_number(number),
+ m_current(0),
+ m_current4way(0),
+ m_previous(0)
+{
+ memset(m_field, 0, sizeof(m_field));
+}
+
+
+//-------------------------------------------------
+// set_axis - configure a single axis of a
+// digital joystick
+//-------------------------------------------------
+
+digital_joystick::direction_t digital_joystick::set_axis(ioport_field &field)
+{
+ direction_t direction = direction_t((field.type() - (IPT_DIGITAL_JOYSTICK_FIRST + 1)) % 4);
+ m_field[direction] = &field;
+ return direction;
+}
+
+
+//-------------------------------------------------
+// frame_update - update the state of digital
+// joysticks prior to accumulating the results
+// in a port
+//-------------------------------------------------
+
+void digital_joystick::frame_update()
+{
+ // remember previous state and reset current state
+ m_previous = m_current;
+ m_current = 0;
+
+ // read all the associated ports
+ running_machine *machine = NULL;
+ for (direction_t direction = JOYDIR_UP; direction < JOYDIR_COUNT; direction++)
+ if (m_field[direction] != NULL)
+ {
+ machine = &m_field[direction]->machine();
+ if (machine->input().seq_pressed(m_field[direction]->seq(SEQ_TYPE_STANDARD)))
+ m_current |= 1 << direction;
+ }
+
+ // lock out opposing directions (left + right or up + down)
+ if ((m_current & (UP_BIT | DOWN_BIT)) == (UP_BIT | DOWN_BIT))
+ m_current &= ~(UP_BIT | DOWN_BIT);
+ if ((m_current & (LEFT_BIT | RIGHT_BIT)) == (LEFT_BIT | RIGHT_BIT))
+ m_current &= ~(LEFT_BIT | RIGHT_BIT);
+
+ // only update 4-way case if joystick has moved
+ if (m_current != m_previous)
+ {
+ m_current4way = m_current;
+
+ //
+ // If joystick is pointing at a diagonal, acknowledge that the player moved
+ // the joystick by favoring a direction change. This minimizes frustration
+ // when using a keyboard for input, and maximizes responsiveness.
+ //
+ // For example, if you are holding "left" then switch to "up" (where both left
+ // and up are briefly pressed at the same time), we'll transition immediately
+ // to "up."
+ //
+ // Zero any switches that didn't change from the previous to current state.
+ //
+ if ((m_current4way & (UP_BIT | DOWN_BIT)) &&
+ (m_current4way & (LEFT_BIT | RIGHT_BIT)))
+ {
+ m_current4way ^= m_current4way & m_previous;
+ }
+
+ //
+ // If we are still pointing at a diagonal, we are in an indeterminant state.
+ //
+ // This could happen if the player moved the joystick from the idle position directly
+ // to a diagonal, or from one diagonal directly to an extreme diagonal.
+ //
+ // The chances of this happening with a keyboard are slim, but we still need to
+ // constrain this case.
+ //
+ // For now, just resolve randomly.
+ //
+ if ((m_current4way & (UP_BIT | DOWN_BIT)) &&
+ (m_current4way & (LEFT_BIT | RIGHT_BIT)))
+ {
+ if (machine->rand() & 1)
+ m_current4way &= ~(LEFT_BIT | RIGHT_BIT);
+ else
+ m_current4way &= ~(UP_BIT | DOWN_BIT);
+ }
+ }
+}
+
+
+
+//**************************************************************************
+// NATURAL KEYBOARD
+//**************************************************************************
+
+//-------------------------------------------------
+// natural_keyboard - constructor
+//-------------------------------------------------
+
+natural_keyboard::natural_keyboard(running_machine &machine)
+ : m_machine(machine),
+ m_bufbegin(0),
+ m_bufend(0),
+ m_status_keydown(false),
+ m_last_cr(false),
+ m_timer(NULL),
+ m_current_rate(attotime::zero)
+{
+ m_queue_chars = ioport_queue_chars_delegate();
+ m_accept_char = ioport_accept_char_delegate();
+ m_charqueue_empty = ioport_charqueue_empty_delegate();
+
+ // reigster debugger commands
+ if (machine.debug_flags & DEBUG_FLAG_ENABLED)
+ {
+ debug_console_register_command(machine, "input", CMDFLAG_NONE, 0, 1, 1, execute_input);
+ debug_console_register_command(machine, "dumpkbd", CMDFLAG_NONE, 0, 0, 1, execute_dumpkbd);
+ }
+}
+
+//-------------------------------------------------
+// initialize - initialize natural keyboard
+// support
+//-------------------------------------------------
+
+void natural_keyboard::initialize()
+{
+ // posting keys directly only makes sense for a computer
+ if (machine().ioport().has_keyboard())
+ {
+ m_buffer.resize(KEY_BUFFER_SIZE);
+ m_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(natural_keyboard::timer), this));
+ build_codes(machine().ioport());
+ }
+}
+//-------------------------------------------------
+// configure - configure callbacks for full-
+// featured keyboard support
+//-------------------------------------------------
+
+void natural_keyboard::configure(ioport_queue_chars_delegate queue_chars, ioport_accept_char_delegate accept_char, ioport_charqueue_empty_delegate charqueue_empty)
+{
+ // set the callbacks
+ m_queue_chars = queue_chars;
+ m_accept_char = accept_char;
+ m_charqueue_empty = charqueue_empty;
+}
+
+
+//-------------------------------------------------
+// post - post a single character
+//-------------------------------------------------
+
+void natural_keyboard::post(unicode_char ch)
+{
+ // ignore any \n that are preceded by \r
+ if (m_last_cr && ch == '\n')
+ {
+ m_last_cr = false;
+ return;
+ }
+
+ // change all eolns to '\r'
+ if (ch == '\n')
+ ch = '\r';
+ else
+ m_last_cr = (ch == '\r');
+
+ // logging
+ if (LOG_NATURAL_KEYBOARD)
+ {
+ const keycode_map_entry *code = find_code(ch);
+ astring tempstr;
+ logerror("natural_keyboard::post(): code=%i (%s) field->name='%s'\n", int(ch), unicode_to_string(tempstr, ch), (code != NULL && code->field[0] != NULL) ? code->field[0]->name() : "<null>");
+ }
+
+ // can we post this key in the queue directly?
+ if (can_post_directly(ch))
+ internal_post(ch);
+
+ // can we post this key with an alternate representation?
+ else if (can_post_alternate(ch))
+ {
+ const char_info *info = char_info::find(ch);
+ assert(info != NULL && info->alternate != NULL);
+ const char *altstring = info->alternate;
+ while (*altstring != 0)
+ {
+ altstring += uchar_from_utf8(&ch, altstring, strlen(altstring));
+ internal_post(ch);
+ }
+ }
+}
+
+
+//-------------------------------------------------
+// post - post a unicode encoded string
+//-------------------------------------------------
+
+void natural_keyboard::post(const unicode_char *text, size_t length, attotime rate)
+{
+ // set the fixed rate
+ m_current_rate = rate;
+
+ // 0 length means strlen
+ if (length == 0)
+ for (const unicode_char *scan = text; *scan != 0; scan++)
+ length++;
+
+ // iterate over characters or until the buffer is full up
+ while (length > 0 && !full())
+ {
+ // fetch next character
+ post(*text++);
+ length--;
+ }
+}
+
+
+//-------------------------------------------------
+// post_utf8 - post a UTF-8 encoded string
+//-------------------------------------------------
+
+void natural_keyboard::post_utf8(const char *text, size_t length, attotime rate)
+{
+ // set the fixed rate
+ m_current_rate = rate;
+
+ // 0-length means strlen
+ if (length == 0)
+ length = strlen(text);
+
+ // iterate until out of characters
+ while (length > 0)
+ {
+ // decode the next character
+ unicode_char uc;
+ int count = uchar_from_utf8(&uc, text, length);
+ if (count < 0)
+ {
+ count = 1;
+ uc = INVALID_CHAR;
+ }
+
+ // append to the buffer
+ post(uc);
+ text += count;
+ length -= count;
+ }
+}
+
+
+//-------------------------------------------------
+// post_coded - post a coded string
+//-------------------------------------------------
+
+void natural_keyboard::post_coded(const char *text, size_t length, attotime rate)
+{
+ static const struct
+ {
+ const char *key;
+ unicode_char code;
+ } codes[] =
+ {
+ { "BACKSPACE", 8 },
+ { "BS", 8 },
+ { "BKSP", 8 },
+ { "DEL", UCHAR_MAMEKEY(DEL) },
+ { "DELETE", UCHAR_MAMEKEY(DEL) },
+ { "END", UCHAR_MAMEKEY(END) },
+ { "ENTER", 13 },
+ { "ESC", '\033' },
+ { "HOME", UCHAR_MAMEKEY(HOME) },
+ { "INS", UCHAR_MAMEKEY(INSERT) },
+ { "INSERT", UCHAR_MAMEKEY(INSERT) },
+ { "PGDN", UCHAR_MAMEKEY(PGDN) },
+ { "PGUP", UCHAR_MAMEKEY(PGUP) },
+ { "SPACE", 32 },
+ { "TAB", 9 },
+ { "F1", UCHAR_MAMEKEY(F1) },
+ { "F2", UCHAR_MAMEKEY(F2) },
+ { "F3", UCHAR_MAMEKEY(F3) },
+ { "F4", UCHAR_MAMEKEY(F4) },
+ { "F5", UCHAR_MAMEKEY(F5) },
+ { "F6", UCHAR_MAMEKEY(F6) },
+ { "F7", UCHAR_MAMEKEY(F7) },
+ { "F8", UCHAR_MAMEKEY(F8) },
+ { "F9", UCHAR_MAMEKEY(F9) },
+ { "F10", UCHAR_MAMEKEY(F10) },
+ { "F11", UCHAR_MAMEKEY(F11) },
+ { "F12", UCHAR_MAMEKEY(F12) },
+ { "QUOTE", '\"' }
+ };
+
+ // set the fixed rate
+ m_current_rate = rate;
+
+ // 0-length means strlen
+ if (length == 0)
+ length = strlen(text);
+
+ // iterate through the source string
+ size_t curpos = 0;
+ while (curpos < length)
+ {
+ // extract next character
+ unicode_char ch = text[curpos];
+ size_t increment = 1;
+
+ // look for escape characters
+ if (ch == '{')
+ for (int codenum = 0; codenum < ARRAY_LENGTH(codes); codenum++)
+ {
+ size_t keylen = strlen(codes[codenum].key);
+ if (curpos + keylen + 2 <= length)
+ if (core_strnicmp(codes[codenum].key, &text[curpos + 1], keylen) == 0 && text[curpos + keylen + 1] == '}')
+ {
+ ch = codes[codenum].code;
+ increment = keylen + 2;
+ }
+ }
+
+ // if we got a code, post it
+ if (ch != 0)
+ post(ch);
+ curpos += increment;
+ }
+}
+
+
+//-------------------------------------------------
+// build_codes - given an input port table, create
+// an input code table useful for mapping unicode
+// chars
+//-------------------------------------------------
+
+void natural_keyboard::build_codes(ioport_manager &manager)
+{
+ // iterate over shift keys
+ ioport_field *shift[UCHAR_SHIFT_END + 1 - UCHAR_SHIFT_BEGIN] = { 0 };
+ for (int curshift = 0; curshift <= ARRAY_LENGTH(shift); curshift++)
+ if (curshift == 0 || shift[curshift - 1] != NULL)
+
+ // iterate over ports and fields
+ for (ioport_port *port = manager.first_port(); port != NULL; port = port->next())
+ for (ioport_field *field = port->first_field(); field != NULL; field = field->next())
+ if (field->type() == IPT_KEYBOARD)
+ {
+ // fetch the code, ignoring 0
+ unicode_char code = field->keyboard_code(curshift);
+ if (code == 0)
+ continue;
+
+ // is this a shifter key?
+ if (code >= UCHAR_SHIFT_BEGIN && code <= UCHAR_SHIFT_END)
+ shift[code - UCHAR_SHIFT_BEGIN] = field;
+
+ // not a shifter key; record normally
+ else
+ {
+ keycode_map_entry newcode;
+ if (curshift == 0)
+ {
+ newcode.field[0] = field;
+ newcode.field[1] = 0;
+ }
+ else
+ {
+ newcode.field[0] = shift[curshift - 1];
+ newcode.field[1] = field;
+ }
+ newcode.ch = code;
+ m_keycode_map.append(newcode);
+
+ if (LOG_NATURAL_KEYBOARD)
+ {
+ astring tempstr;
+ logerror("natural_keyboard: code=%i (%s) port=%p field->name='%s'\n", int(code), unicode_to_string(tempstr, code), port, field->name());
+ }
+ }
+ }
+}
+
+
+//-------------------------------------------------
+// can_post_directly - determine if the given
+// unicode character can be directly posted
+//-------------------------------------------------
+
+bool natural_keyboard::can_post_directly(unicode_char ch)
+{
+ // if we have a queueing callback, then it depends on whether we can accept the character
+ if (!m_queue_chars.isnull())
+ return m_accept_char.isnull() ? true : m_accept_char(ch);
+
+ // otherwise, it depends on the input codes
+ const keycode_map_entry *code = find_code(ch);
+ return (code != NULL && code->field[0] != NULL);
+}
+
+
+//-------------------------------------------------
+// can_post_alternate - determine if the given
+// unicode character can be posted via translation
+//-------------------------------------------------
+
+bool natural_keyboard::can_post_alternate(unicode_char ch)
+{
+ const char_info *info = char_info::find(ch);
+ if (info == NULL)
+ return false;
+
+ const char *altstring = info->alternate;
+ if (altstring == NULL)
+ return false;
+
+ while (*altstring != 0)
+ {
+ unicode_char uchar;
+ int count = uchar_from_utf8(&uchar, altstring, strlen(altstring));
+ if (count <= 0)
+ return false;
+ if (!can_post_directly(uchar))
+ return false;
+ altstring += count;
+ }
+ return true;
+}
+
+
+//-------------------------------------------------
+// choose_delay - determine the delay between
+// posting keyboard events
+//-------------------------------------------------
+
+attotime natural_keyboard::choose_delay(unicode_char ch)
+{
+ // if we have a live rate, just use that
+ if (m_current_rate != attotime::zero)
+ return m_current_rate;
+
+ // systems with queue_chars can afford a much smaller delay
+ if (!m_queue_chars.isnull())
+ return attotime::from_msec(10);
+
+ // otherwise, default to constant delay with a longer delay on CR
+ return attotime::from_msec((ch == '\r') ? 200 : 50);
+}
+
+
+//-------------------------------------------------
+// internal_post - post a keyboard event
+//-------------------------------------------------
+
+void natural_keyboard::internal_post(unicode_char ch)
+{
+ // need to start up the timer?
+ if (empty())
+ {
+ m_timer->adjust(choose_delay(ch));
+ m_status_keydown = 0;
+ }
+
+ // add to the buffer, resizing if necessary
+ m_buffer[m_bufend++] = ch;
+ if ((m_bufend + 1) % m_buffer.count() == m_bufbegin)
+ m_buffer.resize(m_buffer.count() + KEY_BUFFER_SIZE, true);
+ m_bufend %= m_buffer.count();
+}
+
+
+//-------------------------------------------------
+// timer - timer callback to keep things flowing
+// when posting a string of characters
+//-------------------------------------------------
+
+void natural_keyboard::timer(void *ptr, int param)
+{
+ // the driver has a queue_chars handler
+ if (!m_queue_chars.isnull())
+ {
+ while (!empty() && m_queue_chars(&m_buffer[m_bufbegin], 1))
+ {
+ m_bufbegin = (m_bufbegin + 1) % m_buffer.count();
+ if (m_current_rate != attotime::zero)
+ break;
+ }
+ }
+
+ // the driver does not have a queue_chars handler
+ else
+ {
+ if (m_status_keydown)
+ m_bufbegin = (m_bufbegin + 1) % m_buffer.count();
+ m_status_keydown = !m_status_keydown;
+ }
+
+ // need to make sure timerproc is called again if buffer not empty
+ if (!empty())
+ m_timer->adjust(choose_delay(m_buffer[m_bufbegin]));
+}
+
+
+//-------------------------------------------------
+// unicode_to_string - obtain a string
+// representation of a given code; used for
+// logging and debugging
+//-------------------------------------------------
+
+const char *natural_keyboard::unicode_to_string(astring &buffer, unicode_char ch)
+{
+ buffer.reset();
+ switch (ch)
+ {
+ // check some magic values
+ case '\0': buffer.cpy("\\0"); break;
+ case '\r': buffer.cpy("\\r"); break;
+ case '\n': buffer.cpy("\\n"); break;
+ case '\t': buffer.cpy("\\t"); break;
+
+ default:
+ // seven bit ASCII is easy
+ if (ch >= 32 && ch < 128)
+ {
+ char temp[2] = { char(ch), 0 };
+ buffer.cpy(temp);
+ }
+ else if (ch >= UCHAR_MAMEKEY_BEGIN)
+ {
+ // try to obtain a codename with code_name(); this can result in an empty string
+ input_code code(DEVICE_CLASS_KEYBOARD, 0, ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, input_item_id(ch - UCHAR_MAMEKEY_BEGIN));
+ machine().input().code_name(buffer, code);
+ }
+
+ // did we fail to resolve? if so, we have a last resort
+ if (buffer.len() == 0)
+ buffer.format("U+%04X", unsigned(ch));
+ break;
+ }
+ return buffer;
+}
+
+
+//-------------------------------------------------
+// find_code - find a code in our lookup table
+//-------------------------------------------------
+
+const natural_keyboard::keycode_map_entry *natural_keyboard::find_code(unicode_char ch) const
+{
+ for (int index = 0; index < m_keycode_map.count(); index++)
+ {
+ if (m_keycode_map[index].ch == ch)
+ return &m_keycode_map[index];
+ }
+ return NULL;
+}
+
+
+//-------------------------------------------------
+// frame_update - once per frame update of the
+// natural keyboard state
+//-------------------------------------------------
+
+void natural_keyboard::frame_update(ioport_port &port, ioport_value &digital)
+{
+ // is there currently a key down?
+ if (m_status_keydown && !empty())
+ {
+ // loop through this character's component codes
+ const keycode_map_entry *code = find_code(m_buffer[m_bufbegin]);
+ if (code != NULL)
+ for (int fieldnum = 0; fieldnum < ARRAY_LENGTH(code->field) && code->field[fieldnum] != NULL; fieldnum++)
+ if (&code->field[fieldnum]->port() == &port)
+ digital |= code->field[fieldnum]->mask();
+ }
+}
+
+
+//-------------------------------------------------
+// key_name - returns the name of a specific key
+//-------------------------------------------------
+
+const char *natural_keyboard::key_name(astring &string, unicode_char ch)
+{
+ // attempt to get the string from the character info table
+ const char_info *ci = char_info::find(ch);
+ const char *result = (ci != NULL) ? ci->name : NULL;
+ if (result != NULL)
+ string.cpy(result);
+
+ // if that doesn't work, convert to UTF-8
+ else if (ch > 0x7F || isprint(ch))
+ {
+ char buf[10];
+ int count = utf8_from_uchar(buf, ARRAY_LENGTH(buf), ch);
+ buf[count] = 0;
+ string.cpy(buf);
+ }
+
+ // otherwise, opt for question marks
+ else
+ string.cpy("???");
+ return string;
+}
+
+
+//-------------------------------------------------
+// execute_input - debugger command to enter
+// natural keyboard input
+//-------------------------------------------------
+
+void natural_keyboard::execute_input(running_machine &machine, int ref, int params, const char *param[])
+{
+ machine.ioport().natkeyboard().post_coded(param[0]);
+}
+
+
+//-------------------------------------------------
+// execute_dumpkbd - debugger command to natural
+// keyboard codes
+//-------------------------------------------------
+
+void natural_keyboard::execute_dumpkbd(running_machine &machine, int ref, int params, const char *param[])
+{
+ // was there a file specified?
+ const char *filename = (params > 0) ? param[0] : NULL;
+ FILE *file = NULL;
+ if (filename != NULL)
+ {
+ // if so, open it
+ file = fopen(filename, "w");
+ if (file == NULL)
+ {
+ debug_console_printf(machine, "Cannot open \"%s\"\n", filename);
+ return;
+ }
+ }
+
+ // loop through all codes
+ natural_keyboard &natkeyboard = machine.ioport().natkeyboard();
+ dynamic_array<keycode_map_entry> &keycode_map = natkeyboard.m_keycode_map;
+ astring buffer, tempstr;
+ const size_t left_column_width = 24;
+ for (int index = 0; index < keycode_map.count(); index++)
+ {
+ // describe the character code
+ const keycode_map_entry &code = keycode_map[index];
+ buffer.printf("%08X (%s) ", code.ch, natkeyboard.unicode_to_string(tempstr, code.ch));
+
+ // pad with spaces
+ while (buffer.len() < left_column_width)
+ buffer.cat(' ');
+
+ // identify the keys used
+ for (int field = 0; field < ARRAY_LENGTH(code.field) && code.field[field] != 0; field++)
+ buffer.catprintf("%s'%s'", (field > 0) ? ", " : "", code.field[field]->name());
+
+ // and output it as appropriate
+ if (file != NULL)
+ fprintf(file, "%s\n", buffer.cstr());
+ else
+ debug_console_printf(machine, "%s\n", buffer.cstr());
+ }
+
+ // cleanup
+ if (file != NULL)
+ fclose(file);
+}
+
+
+
+//**************************************************************************
+// I/O PORT CONDITION
+//**************************************************************************
+
+//-------------------------------------------------
+// eval - evaluate condition
+//-------------------------------------------------
+
+bool ioport_condition::eval(device_t &device) const
+{
+ // always condition is always true
+ if (m_condition == ALWAYS)
+ return true;
+
+ // otherwise, read the referenced port and switch off the condition type
+ ioport_value condvalue = device.ioport(m_tag)->read();
+ switch (m_condition)
+ {
+ case ALWAYS: return true;
+ case EQUALS: return ((condvalue & m_mask) == m_value);
+ case NOTEQUALS: return ((condvalue & m_mask) != m_value);
+ case GREATERTHAN: return ((condvalue & m_mask) > m_value);
+ case NOTGREATERTHAN: return ((condvalue & m_mask) <= m_value);
+ case LESSTHAN: return ((condvalue & m_mask) < m_value);
+ case NOTLESSTHAN: return ((condvalue & m_mask) >= m_value);
+ }
+ return true;
+}
+
+
+
+//**************************************************************************
+// I/O PORT SETTING
+//**************************************************************************
+
+//-------------------------------------------------
+// ioport_setting - constructor
+//-------------------------------------------------
+
+ioport_setting::ioport_setting(ioport_field &field, ioport_value _value, const char *_name)
+ : m_next(NULL),
+ m_field(field),
+ m_value(_value),
+ m_name(_name)
+{
+}
+
+
+
+//**************************************************************************
+// I/O PORT DIP LOCATION
+//**************************************************************************
+
+//-------------------------------------------------
+// ioport_diplocation - constructor
+//-------------------------------------------------
+
+ioport_diplocation::ioport_diplocation(const char *name, UINT8 swnum, bool invert)
+ : m_next(NULL),
+ m_name(name),
+ m_number(swnum),
+ m_invert(invert)
+{
+}
+
+
+
+//**************************************************************************
+// I/O PORT FIELD
+//**************************************************************************
+
+//-------------------------------------------------
+// ioport_field - constructor
+//-------------------------------------------------
+
+ioport_field::ioport_field(ioport_port &port, ioport_type type, ioport_value defvalue, ioport_value maskbits, const char *name)
+ : m_next(NULL),
+ m_port(port),
+ m_live(NULL),
+ m_modcount(port.modcount()),
+ m_mask(maskbits),
+ m_defvalue(defvalue & maskbits),
+ m_type(type),
+ m_player(0),
+ m_flags(0),
+ m_impulse(0),
+ m_name(name),
+ m_read_param(NULL),
+ m_write_param(NULL),
+ m_min(0),
+ m_max(maskbits),
+ m_sensitivity(0),
+ m_delta(0),
+ m_centerdelta(0),
+ m_crosshair_axis(CROSSHAIR_AXIS_NONE),
+ m_crosshair_scale(1.0),
+ m_crosshair_offset(0),
+ m_crosshair_altaxis(0),
+ m_full_turn_count(0),
+ m_remap_table(NULL),
+ m_way(0)
+{
+ // reset sequences and chars
+ for (input_seq_type seqtype = SEQ_TYPE_STANDARD; seqtype < SEQ_TYPE_TOTAL; seqtype++)
+ m_seq[seqtype].set_default();
+ m_chars[0] = m_chars[1] = m_chars[2] = m_chars[3] = unicode_char(0);
+
+ // for DIP switches and configs, look for a default value from the owner
+ if (type == IPT_DIPSWITCH || type == IPT_CONFIG)
+ {
+ const input_device_default *def = device().input_ports_defaults();
+ if (def != NULL)
+ {
+ const char *fulltag = port.tag();
+ astring fullpath;
+ for ( ; def->tag != NULL; def++)
+ if (device().subtag(fullpath, def->tag) == fulltag && def->mask == m_mask)
+ m_defvalue = def->defvalue & m_mask;
+ }
+ }
+}
+
+
+//-------------------------------------------------
+// ~ioport_field - destructor
+//-------------------------------------------------
+
+ioport_field::~ioport_field()
+{
+ global_free(m_live);
+}
+
+
+//-------------------------------------------------
+// name - return the field name for a given input
+// field
+//-------------------------------------------------
+
+const char *ioport_field::name() const
+{
+ // if we have a non-default name, use that
+ if (m_live != NULL && m_live->name)
+ return m_live->name;
+ if (m_name != NULL)
+ return m_name;
+
+ // otherwise, return the name associated with the type
+ return manager().type_name(m_type, m_player);
+}
+
+
+//-------------------------------------------------
+// seq - return the live input sequence for the
+// given input field
+//-------------------------------------------------
+
+const input_seq &ioport_field::seq(input_seq_type seqtype) const
+{
+ // if no live state, return default
+ if (m_live == NULL)
+ return defseq(seqtype);
+
+ // if the field is disabled, return no key
+ if (unused())
+ return input_seq::empty_seq;
+
+ // if the sequence is the special default code, return the expanded default value
+ if (m_live->seq[seqtype].is_default())
+ return manager().type_seq(m_type, m_player, seqtype);
+
+ // otherwise, return the sequence as-is
+ return m_live->seq[seqtype];
+}
+
+
+//-------------------------------------------------
+// defseq - return the default input sequence for
+// the given input field
+//-------------------------------------------------
+
+const input_seq &ioport_field::defseq(input_seq_type seqtype) const
+{
+ // if the field is disabled, return no key
+ if (unused())
+ return input_seq::empty_seq;
+
+ // if the sequence is the special default code, return the expanded default value
+ if (m_seq[seqtype].is_default())
+ return manager().type_seq(m_type, m_player, seqtype);
+
+ // otherwise, return the sequence as-is
+ return m_seq[seqtype];
+}
+
+
+//-------------------------------------------------
+// type_class - return the type class for this
+// field
+//-------------------------------------------------
+
+ioport_type_class ioport_field::type_class() const
+{
+ ioport_type_class result;
+
+ switch (m_type)
+ {
+ case IPT_JOYSTICK_UP:
+ case IPT_JOYSTICK_DOWN:
+ case IPT_JOYSTICK_LEFT:
+ case IPT_JOYSTICK_RIGHT:
+ case IPT_JOYSTICKLEFT_UP:
+ case IPT_JOYSTICKLEFT_DOWN:
+ case IPT_JOYSTICKLEFT_LEFT:
+ case IPT_JOYSTICKLEFT_RIGHT:
+ case IPT_JOYSTICKRIGHT_UP:
+ case IPT_JOYSTICKRIGHT_DOWN:
+ case IPT_JOYSTICKRIGHT_LEFT:
+ case IPT_JOYSTICKRIGHT_RIGHT:
+ case IPT_BUTTON1:
+ case IPT_BUTTON2:
+ case IPT_BUTTON3:
+ case IPT_BUTTON4:
+ case IPT_BUTTON5:
+ case IPT_BUTTON6:
+ case IPT_BUTTON7:
+ case IPT_BUTTON8:
+ case IPT_BUTTON9:
+ case IPT_BUTTON10:
+ case IPT_AD_STICK_X:
+ case IPT_AD_STICK_Y:
+ case IPT_AD_STICK_Z:
+ case IPT_TRACKBALL_X:
+ case IPT_TRACKBALL_Y:
+ case IPT_LIGHTGUN_X:
+ case IPT_LIGHTGUN_Y:
+ case IPT_MOUSE_X:
+ case IPT_MOUSE_Y:
+ case IPT_START:
+ case IPT_SELECT:
+ result = INPUT_CLASS_CONTROLLER;
+ break;
+
+ case IPT_KEYPAD:
+ case IPT_KEYBOARD:
+ result = INPUT_CLASS_KEYBOARD;
+ break;
+
+ case IPT_CONFIG:
+ result = INPUT_CLASS_CONFIG;
+ break;
+
+ case IPT_DIPSWITCH:
+ result = INPUT_CLASS_DIPSWITCH;
+ break;
+
+ case 0:
+ if (m_name != NULL && m_name != (const char *)-1)
+ result = INPUT_CLASS_MISC;
+ else
+ result = INPUT_CLASS_INTERNAL;
+ break;
+
+ default:
+ result = INPUT_CLASS_INTERNAL;
+ break;
+ }
+ return result;
+}
+
+
+//-------------------------------------------------
+// keyboard_code - accesses a particular keyboard
+// code
+//-------------------------------------------------
+
+unicode_char ioport_field::keyboard_code(int which) const
+{
+ unicode_char ch;
+
+ if (which >= ARRAY_LENGTH(m_chars))
+ throw emu_fatalerror("Tried to access keyboard_code with out-of-range index %d\n", which);
+
+ ch = m_chars[which];
+
+ // special hack to allow for PORT_CODE('\xA3')
+ if (ch >= 0xffffff80 && ch <= 0xffffffff)
+ ch &= 0xff;
+ return ch;
+}
+
+
+//-------------------------------------------------
+// get_user_settings - return the current
+// settings for the given input field
+//-------------------------------------------------
+
+void ioport_field::get_user_settings(user_settings &settings)
+{
+ // zap the entire structure
+ memset(&settings, 0, sizeof(settings));
+
+ // copy the basics
+ for (input_seq_type seqtype = SEQ_TYPE_STANDARD; seqtype < SEQ_TYPE_TOTAL; seqtype++)
+ settings.seq[seqtype] = seq(seqtype);
+
+ // if there's a list of settings or we're an adjuster, copy the current value
+ if (first_setting() != NULL || m_type == IPT_ADJUSTER)
+ settings.value = m_live->value;
+
+ // if there's analog data, extract the analog settings
+ if (m_live->analog != NULL)
+ {
+ settings.sensitivity = m_live->analog->sensitivity();
+ settings.delta = m_live->analog->delta();
+ settings.centerdelta = m_live->analog->centerdelta();
+ settings.reverse = m_live->analog->reverse();
+ }
+}
+
+
+//-------------------------------------------------
+// set_user_settings - modify the current
+// settings for the given input field
+//-------------------------------------------------
+
+void ioport_field::set_user_settings(const user_settings &settings)
+{
+ // copy the basics
+ for (input_seq_type seqtype = SEQ_TYPE_STANDARD; seqtype < SEQ_TYPE_TOTAL; seqtype++)
+ {
+ const input_seq &defseq = manager().type_seq(m_type, m_player, input_seq_type(seqtype));
+ if (defseq == settings.seq[seqtype])
+ m_live->seq[seqtype].set_default();
+ else
+ m_live->seq[seqtype] = settings.seq[seqtype];
+ }
+
+ // if there's a list of settings or we're an adjuster, copy the current value
+ if (first_setting() != NULL || m_type == IPT_ADJUSTER)
+ m_live->value = settings.value;
+
+ // if there's analog data, extract the analog settings
+ if (m_live->analog != NULL)
+ {
+ m_live->analog->m_sensitivity = settings.sensitivity;
+ m_live->analog->m_delta = settings.delta;
+ m_live->analog->m_centerdelta = settings.centerdelta;
+ m_live->analog->m_reverse = settings.reverse;
+ }
+}
+
+
+//-------------------------------------------------
+// setting_name - return the expanded setting
+// name for a field
+//-------------------------------------------------
+
+const char *ioport_field::setting_name() const
+{
+ // only makes sense if we have settings
+ assert(first_setting() != NULL);
+
+ // scan the list of settings looking for a match on the current value
+ for (ioport_setting *setting = first_setting(); setting != NULL; setting = setting->next())
+ if (setting->enabled())
+ if (setting->value() == m_live->value)
+ return setting->name();
+
+ return "INVALID";
+}
+
+
+//-------------------------------------------------
+// has_previous_setting - return true if the
+// given field has a "previous" setting
+//-------------------------------------------------
+
+bool ioport_field::has_previous_setting() const
+{
+ // only makes sense if we have settings
+ assert(first_setting() != NULL);
+
+ // scan the list of settings looking for a match on the current value
+ for (ioport_setting *setting = first_setting(); setting != NULL; setting = setting->next())
+ if (setting->enabled())
+ return (setting->value() != m_live->value);
+
+ return false;
+}
+
+
+//-------------------------------------------------
+// select_previous_setting - select the previous
+// item for a DIP switch or configuration field
+//-------------------------------------------------
+
+void ioport_field::select_previous_setting()
+{
+ // only makes sense if we have settings
+ assert(first_setting() != NULL);
+
+ // scan the list of settings looking for a match on the current value
+ ioport_setting *prevsetting = NULL;
+ bool found_match = false;
+ for (ioport_setting *setting = first_setting(); setting != NULL; setting = setting->next())
+ if (setting->enabled())
+ {
+ if (setting->value() == m_live->value)
+ {
+ found_match = true;
+ if (prevsetting != NULL)
+ break;
+ }
+ prevsetting = setting;
+ }
+
+ // if we didn't find a matching value, select the first
+ if (!found_match)
+ {
+ for (prevsetting = first_setting(); prevsetting != NULL; prevsetting = prevsetting->next())
+ if (prevsetting->enabled())
+ break;
+ }
+
+ // update the value to the previous one
+ if (prevsetting != NULL)
+ m_live->value = prevsetting->value();
+}
+
+
+//-------------------------------------------------
+// has_next_setting - return true if the given
+// field has a "next" setting
+//-------------------------------------------------
+
+bool ioport_field::has_next_setting() const
+{
+ // only makes sense if we have settings
+ assert(first_setting() != NULL);
+
+ // scan the list of settings looking for a match on the current value
+ bool found = false;
+ for (ioport_setting *setting = first_setting(); setting != NULL; setting = setting->next())
+ if (setting->enabled())
+ {
+ if (found)
+ return true;
+ if (setting->value() == m_live->value)
+ found = true;
+ }
+
+ return false;
+}
+
+
+//-------------------------------------------------
+// select_next_setting - select the next item for
+// a DIP switch or configuration field
+//-------------------------------------------------
+
+void ioport_field::select_next_setting()
+{
+ // only makes sense if we have settings
+ assert(first_setting() != NULL);
+
+ // scan the list of settings looking for a match on the current value
+ ioport_setting *nextsetting = NULL;
+ ioport_setting *setting;
+ for (setting = first_setting(); setting != NULL; setting = setting->next())
+ if (setting->enabled())
+ if (setting->value() == m_live->value)
+ break;
+
+ // if we found one, scan forward for the next valid one
+ if (setting != NULL)
+ for (nextsetting = setting->next(); nextsetting != NULL; nextsetting = nextsetting->next())
+ if (nextsetting->enabled())
+ break;
+
+ // if we hit the end, search from the beginning
+ if (nextsetting == NULL)
+ for (nextsetting = first_setting(); nextsetting != NULL; nextsetting = nextsetting->next())
+ if (nextsetting->enabled())
+ break;
+
+ // update the value to the previous one
+ if (nextsetting != NULL)
+ m_live->value = nextsetting->value();
+}
+
+
+//-------------------------------------------------
+// frame_update_digital - get the state of a
+// digital field
+//-------------------------------------------------
+
+void ioport_field::frame_update(ioport_value &result, bool mouse_down)
+{
+ // skip if not enabled
+ if (!enabled())
+ return;
+
+ // handle analog inputs first
+ if (m_live->analog != NULL)
+ {
+ m_live->analog->frame_update(machine());
+ return;
+ }
+
+ // if UI is active, ignore digital inputs
+ if (ui_is_menu_active())
+ return;
+
+ // if the state changed, look for switch down/switch up
+ bool curstate = mouse_down || machine().input().seq_pressed(seq());
+ bool changed = false;
+ if (curstate != m_live->last)
+ {
+ m_live->last = curstate;
+ changed = true;
+ }
+
+ // if we're a keyboard type and using natural keyboard, bail
+ if (m_type == IPT_KEYBOARD && ui_get_use_natural_keyboard(machine()))
+ return;
+
+ // coin impulse option
+ int effective_impulse = m_impulse;
+ int impulse_option_val = machine().options().coin_impulse();
+ if (impulse_option_val != 0)
+ {
+ if (impulse_option_val < 0)
+ effective_impulse = 0;
+ else if ((m_type >= IPT_COIN1 && m_type <= IPT_COIN12) || m_impulse != 0)
+ effective_impulse = impulse_option_val;
+ }
+
+ // if this is a switch-down event, handle impulse and toggle
+ if (changed && curstate)
+ {
+ // impluse controls: reset the impulse counter
+ if (effective_impulse != 0 && m_live->impulse == 0)
+ m_live->impulse = effective_impulse;
+
+ // toggle controls: flip the toggle state or advance to the next setting
+ if (toggle())
+ {
+ if (m_settinglist.count() == 0)
+ m_live->value ^= m_mask;
+ else
+ select_next_setting();
+ }
+ }
+
+ // update the current state with the impulse state
+ if (effective_impulse != 0)
+ {
+ curstate = (m_live->impulse != 0);
+ if (curstate)
+ m_live->impulse--;
+ }
+
+ // for toggle switches, the current value is folded into the port's default value
+ // so we always return FALSE here
+ if (toggle())
+ curstate = false;
+
+ // additional logic to restrict digital joysticks
+ if (curstate && !mouse_down && m_live->joystick != NULL && m_way != 16 && !machine().options().joystick_contradictory())
+ {
+ UINT8 mask = (m_way == 4) ? m_live->joystick->current4way() : m_live->joystick->current();
+ if (!(mask & (1 << m_live->joydir)))
+ curstate = false;
+ }
+
+ // skip locked-out coin inputs
+ if (curstate && m_type >= IPT_COIN1 && m_type <= IPT_COIN12 && coin_lockout_get_state(machine(), m_type - IPT_COIN1))
+ {
+ bool verbose = machine().options().verbose();
+#ifdef MAME_DEBUG
+ verbose = true;
+#endif
+ if (machine().options().coin_lockout())
+ {
+ if (verbose)
+ ui_popup_time(3, "Coinlock disabled %s.", name());
+ curstate = false;
+ }
+ else
+ if (verbose)
+ ui_popup_time(3, "Coinlock disabled, but broken through %s.", name());
+ }
+
+ // if we're active, set the appropriate bits in the digital state
+ if (curstate)
+ result |= m_mask;
+}
+
+
+//-------------------------------------------------
+// crosshair_position - compute the crosshair
+// position
+//-------------------------------------------------
+
+void ioport_field::crosshair_position(float &x, float &y, bool &gotx, bool &goty)
+{
+ float value = m_live->analog->crosshair_read();
+
+ // apply the scale and offset
+ if (m_crosshair_scale < 0)
+ value = -(1.0 - value) * m_crosshair_scale;
+ else
+ value *= m_crosshair_scale;
+ value += m_crosshair_offset;
+
+ // apply custom mapping if necessary
+ if (!m_crosshair_mapper.isnull())
+ value = m_crosshair_mapper(*this, value);
+
+ // handle X axis
+ if (m_crosshair_axis == CROSSHAIR_AXIS_X)
+ {
+ x = value;
+ gotx = true;
+ if (m_crosshair_altaxis != 0)
+ {
+ y = m_crosshair_altaxis;
+ goty = true;
+ }
+ }
+
+ // handle Y axis
+ else
+ {
+ y = value;
+ goty = true;
+ if (m_crosshair_altaxis != 0)
+ {
+ x = m_crosshair_altaxis;
+ gotx = true;
+ }
+ }
+}
+
+
+//-------------------------------------------------
+// expand_diplocation - expand a string-based
+// DIP location into a linked list of
+// descriptions
+//-------------------------------------------------
+
+void ioport_field::expand_diplocation(const char *location, astring &errorbuf)
+{
+ // if nothing present, bail
+ if (location == NULL)
+ return;
+
+ m_diploclist.reset();
+
+ // parse the string
+ astring name; // Don't move this variable inside the loop, lastname's lifetime depends on it being outside
+ const char *lastname = NULL;
+ const char *curentry = location;
+ int entries = 0;
+ while (*curentry != 0)
+ {
+ // find the end of this entry
+ const char *comma = strchr(curentry, ',');
+ if (comma == NULL)
+ comma = curentry + strlen(curentry);
+
+ // extract it to tempbuf
+ astring tempstr;
+ tempstr.cpy(curentry, comma - curentry);
+
+ // first extract the switch name if present
+ const char *number = tempstr;
+ const char *colon = strchr(tempstr, ':');
+
+ // allocate and copy the name if it is present
+ if (colon != NULL)
+ {
+ lastname = name.cpy(number, colon - number);
+ number = colon + 1;
+ }
+
+ // otherwise, just copy the last name
+ else
+ {
+ if (lastname == NULL)
+ {
+ errorbuf.catprintf("Switch location '%s' missing switch name!\n", location);
+ lastname = (char *)"UNK";
+ }
+ name.cpy(lastname);
+ }
+
+ // if the number is preceded by a '!' it's active high
+ bool invert = false;
+ if (*number == '!')
+ {
+ invert = true;
+ number++;
+ }
+
+ // now scan the switch number
+ int swnum = -1;
+ if (sscanf(number, "%d", &swnum) != 1)
+ errorbuf.catprintf("Switch location '%s' has invalid format!\n", location);
+
+ // allocate a new entry
+ m_diploclist.append(*global_alloc(ioport_diplocation(name, swnum, invert)));
+ entries++;
+
+ // advance to the next item
+ curentry = comma;
+ if (*curentry != 0)
+ curentry++;
+ }
+
+ // then verify the number of bits in the mask matches
+ ioport_value temp;
+ int bits;
+ for (bits = 0, temp = m_mask; temp != 0 && bits < 32; bits++)
+ temp &= temp - 1;
+ if (bits != entries)
+ errorbuf.catprintf("Switch location '%s' does not describe enough bits for mask %X\n", location, m_mask);
+}
+
+
+//-------------------------------------------------
+// init_live_state - create live state structures
+//-------------------------------------------------
+
+void ioport_field::init_live_state(analog_field *analog)
+{
+ // resolve callbacks
+ m_read.bind_relative_to(device());
+ m_write.bind_relative_to(device());
+ m_crosshair_mapper.bind_relative_to(device());
+
+ // allocate live state
+ m_live = global_alloc(ioport_field_live(*this, analog));
+}
+
+
+
+//**************************************************************************
+// I/O PORT FIELD LIVE
+//**************************************************************************
+
+//-------------------------------------------------
+// ioport_field_live - constructor
+//-------------------------------------------------
+
+ioport_field_live::ioport_field_live(ioport_field &field, analog_field *analog)
+ : analog(analog),
+ joystick(NULL),
+ value(field.defvalue()),
+ impulse(0),
+ last(0),
+ joydir(digital_joystick::JOYDIR_COUNT)
+{
+ // fill in the basic values
+ for (input_seq_type seqtype = SEQ_TYPE_STANDARD; seqtype < SEQ_TYPE_TOTAL; seqtype++)
+ seq[seqtype] = field.defseq_unresolved(seqtype);
+
+ // if this is a digital joystick field, make a note of it
+ if (field.is_digital_joystick())
+ {
+ joystick = &field.manager().digjoystick(field.player(), (field.type() - (IPT_DIGITAL_JOYSTICK_FIRST + 1)) / 4);
+ joydir = joystick->set_axis(field);
+ }
+
+ // Name keyboard key names
+ if (field.type_class() == INPUT_CLASS_KEYBOARD && field.specific_name() == NULL)
+ {
+ // loop through each character on the field
+ astring tempstr;
+ for (int which = 0; ; which++)
+ {
+ unicode_char ch = field.keyboard_code(which);
+ if (ch == 0)
+ break;
+ name.catprintf("%-*s ", MAX(SPACE_COUNT - 1, 0), field.manager().natkeyboard().key_name(tempstr, ch));
+ }
+
+ // trim extra spaces
+ name.trimspace();
+
+ // special case
+ if (name.len() == 0)
+ name.cpy("Unnamed Key");
+ }
+}
+
+
+
+//**************************************************************************
+// I/O PORT
+//**************************************************************************
+
+//-------------------------------------------------
+// ioport_port - constructor
+//-------------------------------------------------
+
+ioport_port::ioport_port(device_t &owner, const char *tag)
+ : m_next(NULL),
+ m_device(owner),
+ m_tag(tag),
+ m_modcount(0),
+ m_active(0),
+ m_live(NULL)
+{
+}
+
+
+//-------------------------------------------------
+// ~ioport_port - destructor
+//-------------------------------------------------
+
+ioport_port::~ioport_port()
+{
+ global_free(m_live);
+}
+
+
+//-------------------------------------------------
+// machine - return a reference to the running
+// machine
+//-------------------------------------------------
+
+running_machine &ioport_port::machine() const
+{
+ return m_device.machine();
+}
+
+
+//-------------------------------------------------
+// manager - return a reference to the
+// ioport_manager on the running machine
+//-------------------------------------------------
+
+ioport_manager &ioport_port::manager() const
+{
+ return machine().ioport();
+}
+
+
+//-------------------------------------------------
+// field - return a pointer to the first field
+// that intersects the given mask
+//-------------------------------------------------
+
+ioport_field *ioport_port::field(ioport_value mask)
+{
+ // if we got the port, look for the field
+ for (ioport_field *field = first_field(); field != NULL; field = field->next())
+ if ((field->mask() & mask) != 0)
+ return field;
+ return NULL;
+}
+
+
+//-------------------------------------------------
+// read - return the value of an I/O port
+//-------------------------------------------------
+
+ioport_value ioport_port::read()
+{
+ assert_always(manager().safe_to_read(), "Input ports cannot be read at init time!");
+
+ // start with the digital state
+ ioport_value result = m_live->digital;
+
+ // insert dynamic read values
+ for (dynamic_field *dynfield = m_live->readlist.first(); dynfield != NULL; dynfield = dynfield->next())
+ dynfield->read(result);
+
+ // apply active high/low state to digital and dynamic read inputs
+ result ^= m_live->defvalue;
+
+ // insert analog portions
+ for (analog_field *analog = m_live->analoglist.first(); analog != NULL; analog = analog->next())
+ analog->read(result);
+
+ return result;
+}
+
+
+//-------------------------------------------------
+// write - write a value to a port
+//-------------------------------------------------
+
+void ioport_port::write(ioport_value data, ioport_value mem_mask)
+{
+ // call device line write handlers
+ COMBINE_DATA(&m_live->outputvalue);
+ for (dynamic_field *dynfield = m_live->writelist.first(); dynfield != NULL; dynfield = dynfield->next())
+ if (dynfield->field().type() == IPT_OUTPUT)
+ dynfield->write(m_live->outputvalue ^ dynfield->field().defvalue());
+}
+
+
+//-------------------------------------------------
+// frame_update - once/frame update
+//-------------------------------------------------
+
+void ioport_port::frame_update(ioport_field *mouse_field)
+{
+ // start with 0 values for the digital bits
+ m_live->digital = 0;
+
+ // now loop back and modify based on the inputs
+ for (ioport_field *field = first_field(); field != NULL; field = field->next())
+ field->frame_update(m_live->digital, field == mouse_field);
+
+ // hook for MESS's natural keyboard support
+ manager().natkeyboard().frame_update(*this, m_live->digital);
+
+ // call device line write handlers
+ ioport_value newvalue = read();
+ for (dynamic_field *dynfield = m_live->writelist.first(); dynfield != NULL; dynfield = dynfield->next())
+ if (dynfield->field().type() != IPT_OUTPUT)
+ dynfield->write(newvalue);
+}
+
+
+//-------------------------------------------------
+// collapse_fields - remove any fields that are
+// wholly overlapped by other fields
+//-------------------------------------------------
+
+void ioport_port::collapse_fields(astring &errorbuf)
+{
+ ioport_value maskbits = 0;
+ int lastmodcount = -1;
+
+ // remove the whole list and start from scratch
+ ioport_field *field = m_fieldlist.detach_all();
+ while (field != NULL)
+ {
+ // if this modcount doesn't match, reset
+ if (field->modcount() != lastmodcount)
+ {
+ lastmodcount = field->modcount();
+ maskbits = 0;
+ }
+
+ // reinsert this field
+ ioport_field *current = field;
+ field = field->next();
+ insert_field(*current, maskbits, errorbuf);
+ }
+}
+
+
+//-------------------------------------------------
+// insert_field - insert a new field, checking
+// for errors
+//-------------------------------------------------
+
+void ioport_port::insert_field(ioport_field &newfield, ioport_value &disallowedbits, astring &errorbuf)
+{
+ // verify against the disallowed bits, but only if we are condition-free
+ if (newfield.condition().none())
+ {
+ if ((newfield.mask() & disallowedbits) != 0)
+ errorbuf.catprintf("INPUT_TOKEN_FIELD specifies duplicate port bits (port=%s mask=%X)\n", tag(), newfield.mask());
+ disallowedbits |= newfield.mask();
+ }
+
+ // first modify/nuke any entries that intersect our maskbits
+ ioport_field *nextfield;
+ for (ioport_field *field = first_field(); field != NULL; field = nextfield)
+ {
+ nextfield = field->next();
+ if ((field->mask() & newfield.mask()) != 0 &&
+ (newfield.condition().none() || field->condition().none() || field->condition() == newfield.condition()))
+ {
+ // reduce the mask of the field we found
+ field->reduce_mask(newfield.mask());
+
+ // if the new entry fully overrides the previous one, we nuke
+ if (INPUT_PORT_OVERRIDE_FULLY_NUKES_PREVIOUS || field->mask() == 0)
+ m_fieldlist.remove(*field);
+ }
+ }
+
+ // make a mask of just the low bit
+ ioport_value lowbit = (newfield.mask() ^ (newfield.mask() - 1)) & newfield.mask();
+
+ // scan forward to find where to insert ourselves
+ ioport_field *field;
+ for (field = first_field(); field != NULL; field = field->next())
+ if (field->mask() > lowbit)
+ break;
+
+ // insert it into the list
+ m_fieldlist.insert_before(newfield, field);
+}
+
+
+//-------------------------------------------------
+// init_live_state - create the live state
+//-------------------------------------------------
+
+void ioport_port::init_live_state()
+{
+ m_live = global_alloc(ioport_port_live(*this));
+}
+
+
+
+//**************************************************************************
+// I/O PORT LIVE STATE
+//**************************************************************************
+
+//-------------------------------------------------
+// ioport_port_live - constructor
+//-------------------------------------------------
+
+ioport_port_live::ioport_port_live(ioport_port &port)
+ : defvalue(0),
+ digital(0),
+ outputvalue(0)
+{
+ // iterate over fields
+ for (ioport_field *field = port.first_field(); field != NULL; field = field->next())
+ {
+ // allocate analog state if it's analog
+ analog_field *analog = NULL;
+ if (field->is_analog())
+ analog = &analoglist.append(*global_alloc(analog_field(*field)));
+
+ // allocate a dynamic field for reading
+ if (field->has_dynamic_read())
+ readlist.append(*global_alloc(dynamic_field(*field)));
+
+ // allocate a dynamic field for writing
+ if (field->has_dynamic_write())
+ writelist.append(*global_alloc(dynamic_field(*field)));
+
+ // let the field initialize its live state
+ field->init_live_state(analog);
+ }
+}
+
+
+
+//**************************************************************************
+// I/O PORT MANAGER
+//**************************************************************************
+
+//-------------------------------------------------
+// ioport_manager - constructor
+//-------------------------------------------------
+
+ioport_manager::ioport_manager(running_machine &machine)
+ : m_machine(machine),
+ m_safe_to_read(false),
+ m_natkeyboard(machine),
+ m_last_frame_time(attotime::zero),
+ m_last_delta_nsec(0),
+ m_record_file(machine.options().input_directory(), OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS),
+ m_playback_file(machine.options().input_directory(), OPEN_FLAG_READ),
+ m_playback_accumulated_speed(0),
+ m_playback_accumulated_frames(0)
+{
+ memset(m_type_to_entry, 0, sizeof(m_type_to_entry));
+}
+
+
+//-------------------------------------------------
+// initialize - walk the configured ports and
+// create live state information
+//-------------------------------------------------
+
+time_t ioport_manager::initialize()
+{
+ // add an exit callback and a frame callback
+ machine().add_notifier(MACHINE_NOTIFY_EXIT, machine_notify_delegate(FUNC(ioport_manager::exit), this));
+ machine().add_notifier(MACHINE_NOTIFY_FRAME, machine_notify_delegate(FUNC(ioport_manager::frame_update_callback), this));
+
+ // initialize the default port info from the OSD
+ init_port_types();
+
+ // if we have a token list, proceed
+ device_iterator iter(machine().root_device());
+ for (device_t *device = iter.first(); device != NULL; device = iter.next())
+ {
+ astring errors;
+ m_portlist.append(*device, errors);
+ if (errors)
+ mame_printf_error("Input port errors:\n%s", errors.cstr());
+ }
+
+ // allocate live structures to mirror the configuration
+ for (ioport_port *port = first_port(); port != NULL; port = port->next())
+ port->init_live_state();
+
+ // handle autoselection of devices
+ init_autoselect_devices(IPT_AD_STICK_X, IPT_AD_STICK_Y, IPT_AD_STICK_Z, OPTION_ADSTICK_DEVICE, "analog joystick");
+ init_autoselect_devices(IPT_PADDLE, IPT_PADDLE_V, 0, OPTION_PADDLE_DEVICE, "paddle");
+ init_autoselect_devices(IPT_PEDAL, IPT_PEDAL2, IPT_PEDAL3, OPTION_PEDAL_DEVICE, "pedal");
+ init_autoselect_devices(IPT_LIGHTGUN_X, IPT_LIGHTGUN_Y, 0, OPTION_LIGHTGUN_DEVICE, "lightgun");
+ init_autoselect_devices(IPT_POSITIONAL, IPT_POSITIONAL_V, 0, OPTION_POSITIONAL_DEVICE, "positional");
+ init_autoselect_devices(IPT_DIAL, IPT_DIAL_V, 0, OPTION_DIAL_DEVICE, "dial");
+ init_autoselect_devices(IPT_TRACKBALL_X, IPT_TRACKBALL_Y, 0, OPTION_TRACKBALL_DEVICE, "trackball");
+ init_autoselect_devices(IPT_MOUSE_X, IPT_MOUSE_Y, 0, OPTION_MOUSE_DEVICE, "mouse");
+
+ // look for 4-way joysticks and change the default map if we find any
+ const char *joystick_map_default = machine().options().joystick_map();
+ if (joystick_map_default[0] == 0 || strcmp(joystick_map_default, "auto") == 0)
+ for (ioport_port *port = first_port(); port != NULL; port = port->next())
+ for (ioport_field *field = port->first_field(); field != NULL; field = field->next())
+ if (field->live().joystick != NULL && field->way() == 4)
+ {
+ machine().input().set_global_joystick_map(field->rotated() ? joystick_map_4way_diagonal : joystick_map_4way_sticky);
+ break;
+ }
+
+ m_natkeyboard.initialize();
+ // register callbacks for when we load configurations
+ config_register(machine(), "input", config_saveload_delegate(FUNC(ioport_manager::load_config), this), config_saveload_delegate(FUNC(ioport_manager::save_config), this));
+
+ // open playback and record files if specified
+ time_t basetime = playback_init();
+ record_init();
+ return basetime;
+}
+
+
+//-------------------------------------------------
+// init_port_types - initialize the default
+// type list
+//-------------------------------------------------
+
+void ioport_manager::init_port_types()
+{
+ // convert the array into a list of type states that can be modified
+ construct_core_types(m_typelist);
+
+ // ask the OSD to customize the list
+ machine().osd().customize_input_type_list(m_typelist);
+
+ // now iterate over the OSD-modified types
+ for (input_type_entry *curtype = first_type(); curtype != NULL; curtype = curtype->next())
+ {
+ // first copy all the OSD-updated sequences into our current state
+ for (input_seq_type seqtype = SEQ_TYPE_STANDARD; seqtype < SEQ_TYPE_TOTAL; seqtype++)
+ curtype->m_seq[seqtype] = curtype->defseq(seqtype);
+
+ // also make a lookup table mapping type/player to the appropriate type list entry
+ m_type_to_entry[curtype->type()][curtype->player()] = curtype;
+ }
+}
+
+
+//-------------------------------------------------
+// init_autoselect_devices - autoselect a single
+// device based on the input port list passed
+// in and the corresponding option
+//-------------------------------------------------
+
+void ioport_manager::init_autoselect_devices(int type1, int type2, int type3, const char *option, const char *ananame)
+{
+ // if nothing specified, ignore the option
+ const char *stemp = machine().options().value(option);
+ if (stemp[0] == 0)
+ return;
+
+ // extract valid strings
+ const char *autostring = "keyboard";
+ input_device_class autoenable = DEVICE_CLASS_KEYBOARD;
+ if (strcmp(stemp, "mouse") == 0)
+ {
+ autoenable = DEVICE_CLASS_MOUSE;
+ autostring = "mouse";
+ }
+ else if (strcmp(stemp, "joystick") == 0)
+ {
+ autoenable = DEVICE_CLASS_JOYSTICK;
+ autostring = "joystick";
+ }
+ else if (strcmp(stemp, "lightgun") == 0)
+ {
+ autoenable = DEVICE_CLASS_LIGHTGUN;
+ autostring = "lightgun";
+ }
+ else if (strcmp(stemp, "none") == 0)
+ {
+ // nothing specified
+ return;
+ }
+ else if (strcmp(stemp, "keyboard") != 0)
+ mame_printf_error("Invalid %s value %s; reverting to keyboard\n", option, stemp);
+
+ // only scan the list if we haven't already enabled this class of control
+ if (first_port() != NULL && !machine().input().device_class(autoenable).enabled())
+ for (ioport_port *port = first_port(); port != NULL; port = port->next())
+ for (ioport_field *field = port->first_field(); field != NULL; field = field->next())
+
+ // if this port type is in use, apply the autoselect criteria
+ if ((type1 != 0 && field->type() == type1) || (type2 != 0 && field->type() == type2) || (type3 != 0 && field->type() == type3))
+ {
+ mame_printf_verbose("Input: Autoenabling %s due to presence of a %s\n", autostring, ananame);
+ machine().input().device_class(autoenable).enable();
+ break;
+ }
+}
+
+
+//-------------------------------------------------
+// exit - exit callback to ensure we clean up
+// and close our files
+//-------------------------------------------------
+
+void ioport_manager::exit()
+{
+ // close any playback or recording files
+ playback_end();
+ record_end();
+}
+
+
+//-------------------------------------------------
+// type_name - return the name for the given
+// type/player
+//-------------------------------------------------
+
+const char *ioport_manager::type_name(ioport_type type, UINT8 player)
+{
+ // if we have a machine, use the live state and quick lookup
+ input_type_entry *entry = m_type_to_entry[type][player];
+ if (entry != NULL)
+ return entry->name();
+
+ // if we find nothing, return an invalid group
+ return "???";
+}
+
+
+//-------------------------------------------------
+// type_group - return the group for the given
+// type/player
+//-------------------------------------------------
+
+ioport_group ioport_manager::type_group(ioport_type type, int player)
+{
+ input_type_entry *entry = m_type_to_entry[type][player];
+ if (entry != NULL)
+ return entry->group();
+
+ // if we find nothing, return an invalid group
+ return IPG_INVALID;
+}
+
+
+//-------------------------------------------------
+// type_seq - return the input sequence for the
+// given type/player
+//-------------------------------------------------
+
+const input_seq &ioport_manager::type_seq(ioport_type type, int player, input_seq_type seqtype)
+{
+ assert(type >= 0 && type < IPT_COUNT);
+ assert(player >= 0 && player < MAX_PLAYERS);
+
+ // if we have a machine, use the live state and quick lookup
+ input_type_entry *entry = m_type_to_entry[type][player];
+ if (entry != NULL)
+ return entry->seq(seqtype);
+
+ // if we find nothing, return an empty sequence
+ return input_seq::empty_seq;
+}
+
+
+//-------------------------------------------------
+// set_type_seq - change the input sequence for
+// the given type/player
+//-------------------------------------------------
+
+void ioport_manager::set_type_seq(ioport_type type, int player, input_seq_type seqtype, const input_seq &newseq)
+{
+ input_type_entry *entry = m_type_to_entry[type][player];
+ if (entry != NULL)
+ entry->m_seq[seqtype] = newseq;
+}
+
+
+//-------------------------------------------------
+// type_pressed - return true if the sequence for
+// the given input type/player is pressed
+//-------------------------------------------------
+
+bool ioport_manager::type_pressed(ioport_type type, int player)
+{
+ return machine().input().seq_pressed(type_seq(type, player));
+}
+
+
+//-------------------------------------------------
+// type_class_present - return true if the given
+// ioport_type_class exists in at least one port
+//-------------------------------------------------
+
+bool ioport_manager::type_class_present(ioport_type_class inputclass)
+{
+ for (ioport_port *port = first_port(); port != NULL; port = port->next())
+ for (ioport_field *field = port->first_field(); field != NULL; field = field->next())
+ if (field->type_class() == inputclass)
+ return true;
+ return false;
+}
+
+
+//-------------------------------------------------
+// has_keyboard - determine if there is a
+// keyboard present in the control list
+//-------------------------------------------------
+
+bool ioport_manager::has_keyboard() const
+{
+ // iterate over ports and fields
+ for (ioport_port *port = first_port(); port != NULL; port = port->next())
+ for (ioport_field *field = port->first_field(); field != NULL; field = field->next())
+ {
+ // if we are at init, check IPT_KEYBOARD
+ if (!m_safe_to_read && field->type() == IPT_KEYBOARD)
+ return true;
+
+ // else, check if there is a keyboard and if such a keyboard is enabled
+ if (field->type() == IPT_KEYBOARD && field->enabled())
+ return true;
+ }
+
+ return false;
+}
+
+//-------------------------------------------------
+// count_players - counts the number of active
+// players
+//-------------------------------------------------
+
+int ioport_manager::count_players() const
+{
+ int max_player = 0;
+ for (ioport_port *port = first_port(); port != NULL; port = port->next())
+ for (ioport_field *field = port->first_field(); field != NULL; field = field->next())
+ if (field->type_class() == INPUT_CLASS_CONTROLLER && max_player <= field->player() + 1)
+ max_player = field->player() + 1;
+
+ return max_player;
+}
+
+
+//-------------------------------------------------
+// crosshair_position - return the extracted
+// crosshair values for the given player
+//-------------------------------------------------
+
+bool ioport_manager::crosshair_position(int player, float &x, float &y)
+{
+ // read all the lightgun values
+ bool gotx = false, goty = false;
+ for (ioport_port *port = first_port(); port != NULL; port = port->next())
+ for (ioport_field *field = port->first_field(); field != NULL; field = field->next())
+ if (field->player() == player && field->crosshair_axis() != CROSSHAIR_AXIS_NONE && field->enabled())
+ {
+ field->crosshair_position(x, y, gotx, goty);
+
+ // if we got both, stop
+ if (gotx && goty)
+ break;
+ }
+
+ return (gotx && goty);
+}
+
+
+//-------------------------------------------------
+// update_defaults - force an update to the input
+// port values based on current conditions
+//-------------------------------------------------
+
+void ioport_manager::update_defaults()
+{
+ // two passes to catch conditionals properly
+ for (int loopnum = 0; loopnum < 2; loopnum++)
+ {
+ // loop over all input ports
+ for (ioport_port *port = first_port(); port != NULL; port = port->next())
+ {
+ // only clear on the first pass
+ if (loopnum == 0)
+ port->live().defvalue = 0;
+
+ // first compute the default value for the entire port
+ for (ioport_field *field = port->first_field(); field != NULL; field = field->next())
+ if (field->enabled())
+ port->live().defvalue = (port->live().defvalue & ~field->mask()) | (field->live().value & field->mask());
+ }
+ }
+}
+
+
+//-------------------------------------------------
+// frame_update - core logic for per-frame input
+// port updating
+//-------------------------------------------------
+
+digital_joystick &ioport_manager::digjoystick(int player, int number)
+{
+ // find it in the list
+ for (digital_joystick *joystick = m_joystick_list.first(); joystick != NULL; joystick = joystick->next())
+ if (joystick->player() == player && joystick->number() == number)
+ return *joystick;
+
+ // create a new one
+ return m_joystick_list.append(*global_alloc(digital_joystick(player, number)));
+}
+
+
+//-------------------------------------------------
+// frame_update - callback for once/frame updating
+//-------------------------------------------------
+
+void ioport_manager::frame_update_callback()
+{
+ // if we're paused, don't do anything
+ if (!machine().paused())
+ frame_update();
+}
+
+
+//-------------------------------------------------
+// frame_update_internal - core logic for
+// per-frame input port updating
+//-------------------------------------------------
+
+void ioport_manager::frame_update()
+{
+g_profiler.start(PROFILER_INPUT);
+
+ // record/playback information about the current frame
+ attotime curtime = machine().time();
+ playback_frame(curtime);
+ record_frame(curtime);
+
+ // track the duration of the previous frame
+ m_last_delta_nsec = (curtime - m_last_frame_time).as_attoseconds() / ATTOSECONDS_PER_NANOSECOND;
+ m_last_frame_time = curtime;
+
+ // update the digital joysticks
+ for (digital_joystick *joystick = m_joystick_list.first(); joystick != NULL; joystick = joystick->next())
+ joystick->frame_update();
+
+ // compute default values for all the ports
+ update_defaults();
+
+ // perform mouse hit testing
+ INT32 mouse_target_x, mouse_target_y;
+ int mouse_button;
+ render_target *mouse_target = ui_input_find_mouse(machine(), &mouse_target_x, &mouse_target_y, &mouse_button);
+
+ // if the button is pressed, map the point and determine what was hit
+ ioport_field *mouse_field = NULL;
+ if (mouse_button && mouse_target != NULL)
+ {
+ const char *tag = NULL;
+ ioport_value mask;
+ float x, y;
+ if (mouse_target->map_point_input(mouse_target_x, mouse_target_y, tag, mask, x, y))
+ {
+ ioport_port *port = machine().root_device().ioport(tag);
+ if (port != NULL)
+ mouse_field = port->field(mask);
+ }
+ }
+
+ // loop over all input ports
+ for (ioport_port *port = first_port(); port != NULL; port = port->next())
+ {
+ port->frame_update(mouse_field);
+
+ // handle playback/record
+ playback_port(*port);
+ record_port(*port);
+ }
+
+g_profiler.stop();
+}
+
+
+//-------------------------------------------------
+// frame_interpolate - interpolate between two
+// values based on the time between frames
+//-------------------------------------------------
+
+INT32 ioport_manager::frame_interpolate(INT32 oldval, INT32 newval)
+{
+ // if no last delta, just use new value
+ if (m_last_delta_nsec == 0)
+ return newval;
+
+ // otherwise, interpolate
+ attoseconds_t nsec_since_last = (machine().time() - m_last_frame_time).as_attoseconds() / ATTOSECONDS_PER_NANOSECOND;
+ return oldval + (INT64(newval - oldval) * nsec_since_last / m_last_delta_nsec);
+}
+
+
+//-------------------------------------------------
+// load_config - callback to extract configuration
+// data from the XML nodes
+//-------------------------------------------------
+
+void ioport_manager::load_config(int config_type, xml_data_node *parentnode)
+{
+ // in the completion phase, we finish the initialization with the final ports
+ if (config_type == CONFIG_TYPE_FINAL)
+ {
+ m_safe_to_read = true;
+ frame_update();
+ }
+
+ // early exit if no data to parse
+ if (parentnode == NULL)
+ return;
+
+ // iterate over all the remap nodes for controller configs only
+ if (config_type == CONFIG_TYPE_CONTROLLER)
+ load_remap_table(parentnode);
+
+ // iterate over all the port nodes
+ for (xml_data_node *portnode = xml_get_sibling(parentnode->child, "port"); portnode; portnode = xml_get_sibling(portnode->next, "port"))
+ {
+ // get the basic port info from the attributes
+ int player;
+ int type = token_to_input_type(xml_get_attribute_string(portnode, "type", ""), player);
+
+ // initialize sequences to invalid defaults
+ input_seq newseq[SEQ_TYPE_TOTAL];
+ for (input_seq_type seqtype = SEQ_TYPE_STANDARD; seqtype < SEQ_TYPE_TOTAL; seqtype++)
+ newseq[seqtype].set(INPUT_CODE_INVALID);
+
+ // loop over new sequences
+ for (xml_data_node *seqnode = xml_get_sibling(portnode->child, "newseq"); seqnode; seqnode = xml_get_sibling(seqnode->next, "newseq"))
+ {
+ // with a valid type, parse out the new sequence
+ input_seq_type seqtype = token_to_seq_type(xml_get_attribute_string(seqnode, "type", ""));
+ if (seqtype != -1 && seqnode->value != NULL)
+ {
+ if (strcmp(seqnode->value, "NONE") == 0)
+ newseq[seqtype].set();
+ else
+ machine().input().seq_from_tokens(newseq[seqtype], seqnode->value);
+ }
+ }
+
+ // if we're loading default ports, apply to the defaults
+ if (config_type != CONFIG_TYPE_GAME)
+ load_default_config(portnode, type, player, newseq);
+ else
+ load_game_config(portnode, type, player, newseq);
+ }
+
+ // after applying the controller config, push that back into the backup, since that is
+ // what we will diff against
+ if (config_type == CONFIG_TYPE_CONTROLLER)
+ for (input_type_entry *entry = m_typelist.first(); entry != NULL; entry = entry->next())
+ for (input_seq_type seqtype = SEQ_TYPE_STANDARD; seqtype < SEQ_TYPE_TOTAL; seqtype++)
+ entry->defseq(seqtype) = entry->seq(seqtype);
+}
+
+
+//-------------------------------------------------
+// load_remap_table - extract and apply the
+// global remapping table
+//-------------------------------------------------
+
+void ioport_manager::load_remap_table(xml_data_node *parentnode)
+{
+ // count items first so we can allocate
+ int count = 0;
+ for (xml_data_node *remapnode = xml_get_sibling(parentnode->child, "remap"); remapnode != NULL; remapnode = xml_get_sibling(remapnode->next, "remap"))
+ count++;
+
+ // if we have some, deal with them
+ if (count > 0)
+ {
+ // allocate tables
+ dynamic_array<input_code> oldtable(count);
+ dynamic_array<input_code> newtable(count);
+
+ // build up the remap table
+ count = 0;
+ for (xml_data_node *remapnode = xml_get_sibling(parentnode->child, "remap"); remapnode != NULL; remapnode = xml_get_sibling(remapnode->next, "remap"))
+ {
+ input_code origcode = machine().input().code_from_token(xml_get_attribute_string(remapnode, "origcode", ""));
+ input_code newcode = machine().input().code_from_token(xml_get_attribute_string(remapnode, "newcode", ""));
+ if (origcode != INPUT_CODE_INVALID && newcode != INPUT_CODE_INVALID)
+ {
+ oldtable[count] = origcode;
+ newtable[count] = newcode;
+ count++;
+ }
+ }
+
+ // loop over the remapping table, then over default ports, replacing old with new
+ for (int remapnum = 0; remapnum < count; remapnum++)
+ for (input_type_entry *entry = m_typelist.first(); entry != NULL; entry = entry->next())
+ for (input_seq_type seqtype = SEQ_TYPE_STANDARD; seqtype < SEQ_TYPE_TOTAL; seqtype++)
+ entry->m_seq[seqtype].replace(oldtable[remapnum], newtable[remapnum]);
+ }
+}
+
+
+//-------------------------------------------------
+// load_default_config - apply configuration
+// data to the default mappings
+//-------------------------------------------------
+
+bool ioport_manager::load_default_config(xml_data_node *portnode, int type, int player, const input_seq *newseq)
+{
+ // find a matching port in the list
+ for (input_type_entry *entry = m_typelist.first(); entry != NULL; entry = entry->next())
+ if (entry->type() == type && entry->player() == player)
+ {
+ for (input_seq_type seqtype = SEQ_TYPE_STANDARD; seqtype < SEQ_TYPE_TOTAL; seqtype++)
+ if (newseq[seqtype][0] != INPUT_CODE_INVALID)
+ entry->m_seq[seqtype] = newseq[seqtype];
+ return true;
+ }
+
+ return false;
+}
+
+
+//-------------------------------------------------
+// load_game_config - apply configuration
+// data to the current set of input ports
+//-------------------------------------------------
+
+bool ioport_manager::load_game_config(xml_data_node *portnode, int type, int player, const input_seq *newseq)
+{
+ // read the mask, index, and defvalue attributes
+ const char *tag = xml_get_attribute_string(portnode, "tag", NULL);
+ ioport_value mask = xml_get_attribute_int(portnode, "mask", 0);
+ ioport_value defvalue = xml_get_attribute_int(portnode, "defvalue", 0);
+
+ // find the port we want; if no tag, search them all
+ for (ioport_port *port = first_port(); port != NULL; port = port->next())
+ if (tag == NULL || strcmp(port->tag(), tag) == 0)
+ for (ioport_field *field = port->first_field(); field != NULL; field = field->next())
+
+ // find the matching mask and defvalue
+ if (field->type() == type && field->player() == player &&
+ field->mask() == mask && (field->defvalue() & mask) == (defvalue & mask))
+ {
+ // if a sequence was specified, copy it in
+ for (input_seq_type seqtype = SEQ_TYPE_STANDARD; seqtype < SEQ_TYPE_TOTAL; seqtype++)
+ if (newseq[seqtype][0] != INPUT_CODE_INVALID)
+ field->live().seq[seqtype] = newseq[seqtype];
+
+ // for non-analog fields, fetch the value
+ if (field->live().analog == NULL)
+ field->live().value = xml_get_attribute_int(portnode, "value", field->defvalue());
+
+ // for analog fields, fetch configurable analog attributes
+ else
+ {
+ // get base attributes
+ field->live().analog->m_delta = xml_get_attribute_int(portnode, "keydelta", field->delta());
+ field->live().analog->m_centerdelta = xml_get_attribute_int(portnode, "centerdelta", field->centerdelta());
+ field->live().analog->m_sensitivity = xml_get_attribute_int(portnode, "sensitivity", field->sensitivity());
+
+ // fetch yes/no for reverse setting
+ const char *revstring = xml_get_attribute_string(portnode, "reverse", NULL);
+ if (revstring != NULL)
+ field->live().analog->m_reverse = (strcmp(revstring, "yes") == 0);
+ }
+ return true;
+ }
+
+ return false;
+}
+
+
+
+//**************************************************************************
+// SETTINGS SAVE
+//**************************************************************************
+
+//-------------------------------------------------
+// save_config - config callback for saving input
+// port configuration
+//-------------------------------------------------
+
+void ioport_manager::save_config(int config_type, xml_data_node *parentnode)
+{
+ // if no parentnode, ignore
+ if (parentnode == NULL)
+ return;
+
+ // default ports save differently
+ if (config_type == CONFIG_TYPE_DEFAULT)
+ save_default_inputs(parentnode);
+ else
+ save_game_inputs(parentnode);
+}
+
+
+//-------------------------------------------------
+// save_sequence - add a node for an input
+// sequence
+//-------------------------------------------------
+
+void ioport_manager::save_sequence(xml_data_node *parentnode, input_seq_type type, ioport_type porttype, const input_seq &seq)
+{
+ // get the string for the sequence
+ astring seqstring;
+ if (seq.length() == 0)
+ seqstring.cpy("NONE");
+ else
+ machine().input().seq_to_tokens(seqstring, seq);
+
+ // add the new node
+ xml_data_node *seqnode = xml_add_child(parentnode, "newseq", seqstring);
+ if (seqnode != NULL)
+ xml_set_attribute(seqnode, "type", seqtypestrings[type]);
+}
+
+
+//-------------------------------------------------
+// save_this_input_field_type - determine if the
+// given port type is worth saving
+//-------------------------------------------------
+
+bool ioport_manager::save_this_input_field_type(ioport_type type)
+{
+ switch (type)
+ {
+ case IPT_UNUSED:
+ case IPT_END:
+ case IPT_PORT:
+ case IPT_UNKNOWN:
+ return false;
+
+ default:
+ break;
+ }
+ return true;
+}
+
+
+//-------------------------------------------------
+// save_default_inputs - add nodes for any default
+// mappings that have changed
+//-------------------------------------------------
+
+void ioport_manager::save_default_inputs(xml_data_node *parentnode)
+{
+ // iterate over ports
+ for (input_type_entry *entry = m_typelist.first(); entry != NULL; entry = entry->next())
+ {
+ // only save if this port is a type we save
+ if (save_this_input_field_type(entry->type()))
+ {
+ // see if any of the sequences have changed
+ input_seq_type seqtype;
+ for (seqtype = SEQ_TYPE_STANDARD; seqtype < SEQ_TYPE_TOTAL; seqtype++)
+ if (entry->seq(seqtype) != entry->defseq(seqtype))
+ break;
+
+ // if so, we need to add a node
+ if (seqtype < SEQ_TYPE_TOTAL)
+ {
+ // add a new port node
+ xml_data_node *portnode = xml_add_child(parentnode, "port", NULL);
+ if (portnode != NULL)
+ {
+ // add the port information and attributes
+ astring tempstr;
+ xml_set_attribute(portnode, "type", input_type_to_token(tempstr, entry->type(), entry->player()));
+
+ // add only the sequences that have changed from the defaults
+ for (input_seq_type seqtype = SEQ_TYPE_STANDARD; seqtype < SEQ_TYPE_TOTAL; seqtype++)
+ if (entry->seq(seqtype) != entry->defseq(seqtype))
+ save_sequence(portnode, seqtype, entry->type(), entry->seq(seqtype));
+ }
+ }
+ }
+ }
+}
+
+
+//-------------------------------------------------
+// save_game_inputs - add nodes for any game
+// mappings that have changed
+//-------------------------------------------------
+
+void ioport_manager::save_game_inputs(xml_data_node *parentnode)
+{
+ // iterate over ports
+ for (ioport_port *port = first_port(); port != NULL; port = port->next())
+ for (ioport_field *field = port->first_field(); field != NULL; field = field->next())
+ if (save_this_input_field_type(field->type()))
+ {
+ // determine if we changed
+ bool changed = false;
+ for (input_seq_type seqtype = SEQ_TYPE_STANDARD; seqtype < SEQ_TYPE_TOTAL; seqtype++)
+ changed |= (field->seq(seqtype) != field->defseq(seqtype));
+
+ // non-analog changes
+ if (!field->is_analog())
+ changed |= ((field->live().value & field->mask()) != (field->defvalue() & field->mask()));
+
+ // analog changes
+ else
+ {
+ changed |= (field->live().analog->m_delta != field->delta());
+ changed |= (field->live().analog->m_centerdelta != field->centerdelta());
+ changed |= (field->live().analog->m_sensitivity != field->sensitivity());
+ changed |= (field->live().analog->m_reverse != field->analog_reverse());
+ }
+
+ // if we did change, add a new node
+ if (changed)
+ {
+ // add a new port node
+ xml_data_node *portnode = xml_add_child(parentnode, "port", NULL);
+ if (portnode != NULL)
+ {
+ // add the identifying information and attributes
+ astring tempstr;
+ xml_set_attribute(portnode, "tag", port->tag());
+ xml_set_attribute(portnode, "type", input_type_to_token(tempstr, field->type(), field->player()));
+ xml_set_attribute_int(portnode, "mask", field->mask());
+ xml_set_attribute_int(portnode, "defvalue", field->defvalue() & field->mask());
+
+ // add sequences if changed
+ for (input_seq_type seqtype = SEQ_TYPE_STANDARD; seqtype < SEQ_TYPE_TOTAL; seqtype++)
+ if (field->seq(seqtype) != field->defseq(seqtype))
+ save_sequence(portnode, seqtype, field->type(), field->seq(seqtype));
+
+ // write out non-analog changes
+ if (!field->is_analog())
+ {
+ if ((field->live().value & field->mask()) != (field->defvalue() & field->mask()))
+ xml_set_attribute_int(portnode, "value", field->live().value & field->mask());
+ }
+
+ // write out analog changes
+ else
+ {
+ if (field->live().analog->m_delta != field->delta())
+ xml_set_attribute_int(portnode, "keydelta", field->live().analog->m_delta);
+ if (field->live().analog->m_centerdelta != field->centerdelta())
+ xml_set_attribute_int(portnode, "centerdelta", field->live().analog->m_centerdelta);
+ if (field->live().analog->m_sensitivity != field->sensitivity())
+ xml_set_attribute_int(portnode, "sensitivity", field->live().analog->m_sensitivity);
+ if (field->live().analog->m_reverse != field->analog_reverse())
+ xml_set_attribute(portnode, "reverse", field->live().analog->m_reverse ? "yes" : "no");
+ }
+ }
+ }
+ }
+}
+
+
+
+//**************************************************************************
+// INPUT PLAYBACK
+//**************************************************************************
+
+//-------------------------------------------------
+// playback_read - read a value from the playback
+// file
+//-------------------------------------------------
+
+template<typename _Type>
+_Type ioport_manager::playback_read(_Type &result)
+{
+ // protect against NULL handles if previous reads fail
+ if (!m_playback_file.is_open())
+ result = 0;
+
+ // read the value; if we fail, end playback
+ else if (m_playback_file.read(&result, sizeof(result)) != sizeof(result))
+ {
+ playback_end("End of file");
+ result = 0;
+ }
+
+ // return the appropriate value
+ else if (sizeof(result) == 8)
+ result = LITTLE_ENDIANIZE_INT64(result);
+ else if (sizeof(result) == 4)
+ result = LITTLE_ENDIANIZE_INT32(result);
+ else if (sizeof(result) == 2)
+ result = LITTLE_ENDIANIZE_INT16(result);
+ return result;
+}
+
+template<>
+bool ioport_manager::playback_read<bool>(bool &result)
+{
+ UINT8 temp;
+ playback_read(temp);
+ return result = bool(temp);
+}
+
+
+//-------------------------------------------------
+// playback_init - initialize INP playback
+//-------------------------------------------------
+
+time_t ioport_manager::playback_init()
+{
+ // if no file, nothing to do
+ const char *filename = machine().options().playback();
+ if (filename[0] == 0)
+ return 0;
+
+ // open the playback file
+ file_error filerr = m_playback_file.open(filename);
+ assert_always(filerr == FILERR_NONE, "Failed to open file for playback");
+
+ // read the header and verify that it is a modern version; if not, print an error
+ UINT8 header[INP_HEADER_SIZE];
+ if (m_playback_file.read(header, sizeof(header)) != sizeof(header))
+ fatalerror("Input file is corrupt or invalid (missing header)");
+ if (memcmp(header, "MAMEINP\0", 8) != 0)
+ fatalerror("Input file invalid or in an older, unsupported format");
+ if (header[0x10] != INP_HEADER_MAJVERSION)
+ fatalerror("Input file format version mismatch");
+
+ // output info to console
+ mame_printf_info("Input file: %s\n", filename);
+ mame_printf_info("INP version %d.%d\n", header[0x10], header[0x11]);
+ time_t basetime = header[0x08] | (header[0x09] << 8) | (header[0x0a] << 16) | (header[0x0b] << 24) |
+ ((UINT64)header[0x0c] << 32) | ((UINT64)header[0x0d] << 40) | ((UINT64)header[0x0e] << 48) | ((UINT64)header[0x0f] << 56);
+ mame_printf_info("Created %s", ctime(&basetime));
+ mame_printf_info("Recorded using %s\n", header + 0x20);
+
+ // verify the header against the current game
+ if (memcmp(machine().system().name, header + 0x14, strlen(machine().system().name) + 1) != 0)
+ mame_printf_info("Input file is for %s '%s', not for current %s '%s'\n", emulator_info::get_gamenoun(), header + 0x14, emulator_info::get_gamenoun(), machine().system().name);
+
+ // enable compression
+ m_playback_file.compress(FCOMPRESS_MEDIUM);
+ return basetime;
+}
+
+
+//-------------------------------------------------
+// playback_end - end INP playback
+//-------------------------------------------------
+
+void ioport_manager::playback_end(const char *message)
+{
+ // only applies if we have a live file
+ if (m_playback_file.is_open())
+ {
+ // close the file
+ m_playback_file.close();
+
+ // pop a message
+ if (message != NULL)
+ popmessage("Playback Ended\nReason: %s", message);
+
+ // display speed stats
+ m_playback_accumulated_speed /= m_playback_accumulated_frames;
+ mame_printf_info("Total playback frames: %d\n", UINT32(m_playback_accumulated_frames));
+ mame_printf_info("Average recorded speed: %d%%\n", UINT32((m_playback_accumulated_speed * 200 + 1) >> 21));
+ }
+}
+
+
+//-------------------------------------------------
+// playback_frame - start of frame callback for
+// playback
+//-------------------------------------------------
+
+void ioport_manager::playback_frame(attotime curtime)
+{
+ // if playing back, fetch the information and verify
+ if (m_playback_file.is_open())
+ {
+ // first the absolute time
+ attotime readtime;
+ playback_read(readtime.seconds);
+ playback_read(readtime.attoseconds);
+ if (readtime != curtime)
+ playback_end("Out of sync");
+
+ // then the speed
+ UINT32 curspeed;
+ m_playback_accumulated_speed += playback_read(curspeed);
+ m_playback_accumulated_frames++;
+ }
+}
+
+
+//-------------------------------------------------
+// playback_port - per-port callback for playback
+//-------------------------------------------------
+
+void ioport_manager::playback_port(ioport_port &port)
+{
+ // if playing back, fetch information about this port
+ if (m_playback_file.is_open())
+ {
+ // read the default value and the digital state
+ playback_read(port.live().defvalue);
+ playback_read(port.live().digital);
+
+ // loop over analog ports and save their data
+ for (analog_field *analog = port.live().analoglist.first(); analog != NULL; analog = analog->next())
+ {
+ // read current and previous values
+ playback_read(analog->m_accum);
+ playback_read(analog->m_previous);
+
+ // read configuration information
+ playback_read(analog->m_sensitivity);
+ playback_read(analog->m_reverse);
+ }
+ }
+}
+
+
+//-------------------------------------------------
+// record_write - write a value to the record file
+//-------------------------------------------------
+
+template<typename _Type>
+void ioport_manager::record_write(_Type value)
+{
+ // protect against NULL handles if previous reads fail
+ if (!m_record_file.is_open())
+ return;
+
+ // read the value; if we fail, end playback
+ if (m_record_file.write(&value, sizeof(value)) != sizeof(value))
+ record_end("Out of space");
+}
+
+template<>
+void ioport_manager::record_write<bool>(bool value)
+{
+ UINT8 byte = UINT8(value);
+ record_write(byte);
+}
+
+
+//-------------------------------------------------
+// record_init - initialize INP recording
+//-------------------------------------------------
+
+void ioport_manager::record_init()
+{
+ // if no file, nothing to do
+ const char *filename = machine().options().record();
+ if (filename[0] == 0)
+ return;
+
+ // open the record file
+ file_error filerr = m_record_file.open(filename);
+ assert_always(filerr == FILERR_NONE, "Failed to open file for recording");
+
+ // get the base time
+ system_time systime;
+ machine().base_datetime(systime);
+
+ // fill in the header
+ UINT8 header[INP_HEADER_SIZE] = { 0 };
+ memcpy(header, "MAMEINP\0", 8);
+ header[0x08] = systime.time >> 0;
+ header[0x09] = systime.time >> 8;
+ header[0x0a] = systime.time >> 16;
+ header[0x0b] = systime.time >> 24;
+ header[0x0c] = systime.time >> 32;
+ header[0x0d] = systime.time >> 40;
+ header[0x0e] = systime.time >> 48;
+ header[0x0f] = systime.time >> 56;
+ header[0x10] = INP_HEADER_MAJVERSION;
+ header[0x11] = INP_HEADER_MINVERSION;
+ strcpy((char *)header + 0x14, machine().system().name);
+ sprintf((char *)header + 0x20, "%s %s", emulator_info::get_appname(), build_version);
+
+ // write it
+ m_record_file.write(header, sizeof(header));
+
+ // enable compression
+ m_record_file.compress(FCOMPRESS_MEDIUM);
+}
+
+
+//-------------------------------------------------
+// record_end - end INP recording
+//-------------------------------------------------
+
+void ioport_manager::record_end(const char *message)
+{
+ // only applies if we have a live file
+ if (m_record_file.is_open())
+ {
+ // close the file
+ m_record_file.close();
+
+ // pop a message
+ if (message != NULL)
+ popmessage("Recording Ended\nReason: %s", message);
+ }
+}
+
+
+//-------------------------------------------------
+// record_frame - start of frame callback for
+// recording
+//-------------------------------------------------
+
+void ioport_manager::record_frame(attotime curtime)
+{
+ // if recording, record information about the current frame
+ if (m_record_file.is_open())
+ {
+ // first the absolute time
+ record_write(curtime.seconds);
+ record_write(curtime.attoseconds);
+
+ // then the current speed
+ record_write(UINT32(machine().video().speed_percent() * double(1 << 20)));
+ }
+}
+
+
+//-------------------------------------------------
+// record_port - per-port callback for record
+//-------------------------------------------------
+
+void ioport_manager::record_port(ioport_port &port)
+{
+ // if recording, store information about this port
+ if (m_record_file.is_open())
+ {
+ // store the default value and digital state
+ record_write(port.live().defvalue);
+ record_write(port.live().digital);
+
+ // loop over analog ports and save their data
+ for (analog_field *analog = port.live().analoglist.first(); analog != NULL; analog = analog->next())
+ {
+ // store current and previous values
+ record_write(analog->m_accum);
+ record_write(analog->m_previous);
+
+ // store configuration information
+ record_write(analog->m_sensitivity);
+ record_write(analog->m_reverse);
+ }
+ }
+}
+
+
+
+//**************************************************************************
+// I/O PORT CONFIGURER
+//**************************************************************************
+
+//-------------------------------------------------
+// ioport_configurer - constructor
+//-------------------------------------------------
+
+ioport_configurer::ioport_configurer(device_t &owner, ioport_list &portlist, astring &errorbuf)
+ : m_owner(owner),
+ m_portlist(portlist),
+ m_errorbuf(errorbuf),
+ m_curport(NULL),
+ m_curfield(NULL),
+ m_cursetting(NULL)
+{
+}
+
+
+//-------------------------------------------------
+// string_from_token - convert an
+// ioport_token to a default string
+//-------------------------------------------------
+
+const char *ioport_configurer::string_from_token(const char *string)
+{
+ // 0 is an invalid index
+ if (string == NULL)
+ return NULL;
+
+ // if the index is greater than the count, assume it to be a pointer
+ if (FPTR(string) >= INPUT_STRING_COUNT)
+ return string;
+
+#if FALSE // Set TRUE, If you want to take care missing-token or wrong-sorting
+
+ // otherwise, scan the list for a matching string and return it
+ {
+ int index;
+ for (index = 0; index < ARRAY_LENGTH(input_port_default_strings); index++)
+ if (input_port_default_strings[index].id == FPTR(string))
+ return input_port_default_strings[index].string;
+ }
+ return "(Unknown Default)";
+
+#else
+
+ return input_port_default_strings[FPTR(string)-1].string;
+
+#endif
+}
+
+
+//-------------------------------------------------
+// port_alloc - allocate a new port
+//-------------------------------------------------
+
+void ioport_configurer::port_alloc(const char *tag)
+{
+ // create the full tag
+ astring fulltag;
+ m_owner.subtag(fulltag, tag);
+
+ // add it to the list, and reset current field/setting
+ m_curport = &m_portlist.append(fulltag, *global_alloc(ioport_port(m_owner, fulltag)));
+ m_curfield = NULL;
+ m_cursetting = NULL;
+}
+
+
+//-------------------------------------------------
+// port_modify - find an existing port and
+// modify it
+//-------------------------------------------------
+
+void ioport_configurer::port_modify(const char *tag)
+{
+ // create the full tag
+ astring fulltag;
+ m_owner.subtag(fulltag, tag);
+
+ // find the existing port
+ m_curport = m_portlist.find(fulltag.cstr());
+ if (m_curport == NULL)
+ throw emu_fatalerror("Requested to modify nonexistent port '%s'", fulltag.cstr());
+
+ // bump the modification count, and reset current field/setting
+ m_curport->m_modcount++;
+ m_curfield = NULL;
+ m_cursetting = NULL;
+}
+
+
+//-------------------------------------------------
+// field_alloc - allocate a new field
+//-------------------------------------------------
+
+void ioport_configurer::field_alloc(ioport_type type, ioport_value defval, ioport_value mask, const char *name)
+{
+ // make sure we have a port
+ if (m_curport == NULL)
+ throw emu_fatalerror("alloc_field called with no active port (mask=%X defval=%X)\n", mask, defval); \
+
+ // append the field
+ if (type != IPT_UNKNOWN && type != IPT_UNUSED)
+ m_curport->m_active |= mask;
+ m_curfield = &m_curport->m_fieldlist.append(*global_alloc(ioport_field(*m_curport, type, defval, mask, string_from_token(name))));
+
+ // reset the current setting
+ m_cursetting = NULL;
+}
+
+
+//-------------------------------------------------
+// field_add_char - add a character to a field
+//-------------------------------------------------
+
+void ioport_configurer::field_add_char(unicode_char ch)
+{
+ for (int index = 0; index < ARRAY_LENGTH(m_curfield->m_chars); index++)
+ if (m_curfield->m_chars[index] == 0)
+ {
+ m_curfield->m_chars[index] = ch;
+ return;
+ }
+
+ throw emu_fatalerror("PORT_CHAR(%d) could not be added - maximum amount exceeded\n", ch);
+}
+
+
+//-------------------------------------------------
+// field_add_code - add a character to a field
+//-------------------------------------------------
+
+void ioport_configurer::field_add_code(input_seq_type which, input_code code)
+{
+ m_curfield->m_seq[which] |= code;
+}
+
+
+//-------------------------------------------------
+// setting_alloc - allocate a new setting
+//-------------------------------------------------
+
+void ioport_configurer::setting_alloc(ioport_value value, const char *name)
+{
+ // make sure we have a field
+ if (m_curfield == NULL)
+ throw emu_fatalerror("alloc_setting called with no active field (value=%X name=%s)\n", value, name);
+
+ m_cursetting = global_alloc(ioport_setting(*m_curfield, value & m_curfield->mask(), string_from_token(name)));
+ // append a new setting
+ m_curfield->m_settinglist.append(*m_cursetting);
+}
+
+
+//-------------------------------------------------
+// set_condition - set the condition for either
+// the current setting or field
+//-------------------------------------------------
+
+void ioport_configurer::set_condition(ioport_condition::condition_t condition, const char *tag, ioport_value mask, ioport_value value)
+{
+ ioport_condition &target = (m_cursetting != NULL) ? m_cursetting->condition() : m_curfield->condition();
+ target.set(condition, tag, mask, value);
+}
+
+
+//-------------------------------------------------
+// onoff_alloc - allocate an on/off DIP switch
+//-------------------------------------------------
+
+void ioport_configurer::onoff_alloc(const char *name, ioport_value defval, ioport_value mask, const char *diplocation)
+{
+ // allocate a field normally
+ field_alloc(IPT_DIPSWITCH, defval, mask, name);
+
+ // special case service mode
+ if (name == DEF_STR(Service_Mode))
+ {
+ field_set_toggle();
+ m_curfield->m_seq[SEQ_TYPE_STANDARD].set(KEYCODE_F2);
+ }
+
+ // expand the diplocation
+ if (diplocation != NULL)
+ field_set_diplocation(diplocation);
+
+ // allocate settings
+ setting_alloc(defval & mask, DEF_STR(Off));
+ setting_alloc(~defval & mask, DEF_STR(On));
+ // clear cursettings set by setting_alloc
+ m_cursetting = NULL;
+}
+
+
+
+/***************************************************************************
+ MISCELLANEOUS
+***************************************************************************/
+
+//-------------------------------------------------
+// find - look up information about a particular
+// character
+//-------------------------------------------------
+
+const char_info *char_info::find(unicode_char target)
+{
+ // perform a simple binary search to find the proper alternate
+ int low = 0;
+ int high = ARRAY_LENGTH(charinfo);
+ while (high > low)
+ {
+ int middle = (high + low) / 2;
+ unicode_char ch = charinfo[middle].ch;
+ if (ch < target)
+ low = middle + 1;
+ else if (ch > target)
+ high = middle;
+ else
+ return &charinfo[middle];
+ }
+ return NULL;
+}
+
+
+//-------------------------------------------------
+// dynamic_field - constructor
+//-------------------------------------------------
+
+dynamic_field::dynamic_field(ioport_field &field)
+ : m_next(NULL),
+ m_field(field),
+ m_shift(0),
+ m_oldval(field.defvalue())
+{
+ // fill in the data
+ for (ioport_value mask = field.mask(); !(mask & 1); mask >>= 1)
+ m_shift++;
+ m_oldval >>= m_shift;
+}
+
+
+//-------------------------------------------------
+// read - read the updated value and merge it
+// into the target
+//-------------------------------------------------
+
+void dynamic_field::read(ioport_value &result)
+{
+ // skip if not enabled
+ if (!m_field.enabled())
+ return;
+
+ // call the callback to read a new value
+ ioport_value newval = m_field.m_read(m_field, m_field.m_read_param);
+ m_oldval = newval;
+
+ // merge in the bits (don't invert yet, as all digitals are inverted together)
+ result = (result & ~m_field.mask()) | ((newval << m_shift) & m_field.mask());
+}
+
+
+//-------------------------------------------------
+// write - track a change to a value and call
+// the write callback if there's something new
+//-------------------------------------------------
+
+void dynamic_field::write(ioport_value newval)
+{
+ // skip if not enabled
+ if (!m_field.enabled())
+ return;
+
+ // if the bits have changed, call the handler
+ newval = (newval & m_field.mask()) >> m_shift;
+ if (m_oldval != newval)
+ {
+ m_field.m_write(m_field, m_field.m_write_param, m_oldval, newval);
+ m_oldval = newval;
+ }
+}
+
+
+//-------------------------------------------------
+// analog_field - constructor
+//-------------------------------------------------
+
+analog_field::analog_field(ioport_field &field)
+ : m_next(NULL),
+ m_field(field),
+ m_shift(0),
+ m_adjdefvalue(field.defvalue() & field.mask()),
+ m_adjmin(field.minval() & field.mask()),
+ m_adjmax(field.maxval() & field.mask()),
+ m_sensitivity(field.sensitivity()),
+ m_reverse(field.analog_reverse()),
+ m_delta(field.delta()),
+ m_centerdelta(field.centerdelta()),
+ m_accum(0),
+ m_previous(0),
+ m_previousanalog(0),
+ m_minimum(INPUT_ABSOLUTE_MIN),
+ m_maximum(INPUT_ABSOLUTE_MAX),
+ m_center(0),
+ m_reverse_val(0),
+ m_scalepos(0),
+ m_scaleneg(0),
+ m_keyscalepos(0),
+ m_keyscaleneg(0),
+ m_positionalscale(0),
+ m_absolute(false),
+ m_wraps(false),
+ m_autocenter(false),
+ m_single_scale(false),
+ m_interpolate(false),
+ m_lastdigital(false)
+{
+ // compute the shift amount and number of bits
+ for (ioport_value mask = field.mask(); !(mask & 1); mask >>= 1)
+ m_shift++;
+
+ // initialize core data
+ m_adjdefvalue >>= m_shift;
+ m_adjmin >>= m_shift;
+ m_adjmax >>= m_shift;
+
+ // set basic parameters based on the configured type
+ switch (field.type())
+ {
+ // paddles and analog joysticks are absolute and autocenter
+ case IPT_AD_STICK_X:
+ case IPT_AD_STICK_Y:
+ case IPT_AD_STICK_Z:
+ case IPT_PADDLE:
+ case IPT_PADDLE_V:
+ m_absolute = true;
+ m_autocenter = true;
+ m_interpolate = !field.analog_reset();
+ break;
+
+ // pedals start at and autocenter to the min range
+ case IPT_PEDAL:
+ case IPT_PEDAL2:
+ case IPT_PEDAL3:
+ m_center = INPUT_ABSOLUTE_MIN;
+ m_accum = apply_inverse_sensitivity(m_center);
+ m_absolute = true;
+ m_autocenter = true;
+ m_interpolate = !field.analog_reset();
+ break;
+
+ // lightguns are absolute as well, but don't autocenter and don't interpolate their values
+ case IPT_LIGHTGUN_X:
+ case IPT_LIGHTGUN_Y:
+ m_absolute = true;
+ m_autocenter = false;
+ m_interpolate = false;
+ break;
+
+ // positional devices are absolute, but can also wrap like relative devices
+ // set each position to be 512 units
+ case IPT_POSITIONAL:
+ case IPT_POSITIONAL_V:
+ m_positionalscale = compute_scale(field.maxval(), INPUT_ABSOLUTE_MAX - INPUT_ABSOLUTE_MIN);
+ m_adjmin = 0;
+ m_adjmax = field.maxval() - 1;
+ m_wraps = field.analog_wraps();
+ m_autocenter = !m_wraps;
+ break;
+
+ // dials, mice and trackballs are relative devices
+ // these have fixed "min" and "max" values based on how many bits are in the port
+ // in addition, we set the wrap around min/max values to 512 * the min/max values
+ // this takes into account the mapping that one mouse unit ~= 512 analog units
+ case IPT_DIAL:
+ case IPT_DIAL_V:
+ case IPT_TRACKBALL_X:
+ case IPT_TRACKBALL_Y:
+ case IPT_MOUSE_X:
+ case IPT_MOUSE_Y:
+ m_absolute = false;
+ m_wraps = true;
+ m_interpolate = !field.analog_reset();
+ break;
+
+ default:
+ fatalerror("Unknown analog port type -- don't know if it is absolute or not");
+ break;
+ }
+
+ // further processing for absolute controls
+ if (m_absolute)
+ {
+ // if the default value is pegged at the min or max, use a single scale value for the whole axis
+ m_single_scale = (m_adjdefvalue == m_adjmin) || (m_adjdefvalue == m_adjmax);
+
+ // if not "single scale", compute separate scales for each side of the default
+ if (!m_single_scale)
+ {
+ // unsigned
+ m_scalepos = compute_scale(m_adjmax - m_adjdefvalue, INPUT_ABSOLUTE_MAX - 0);
+ m_scaleneg = compute_scale(m_adjdefvalue - m_adjmin, 0 - INPUT_ABSOLUTE_MIN);
+
+ if (m_adjmin > m_adjmax)
+ m_scaleneg = -m_scaleneg;
+
+ // reverse point is at center
+ m_reverse_val = 0;
+ }
+ else
+ {
+ // single axis that increases from default
+ m_scalepos = compute_scale(m_adjmax - m_adjmin, INPUT_ABSOLUTE_MAX - INPUT_ABSOLUTE_MIN);
+
+ // move from default
+ if (m_adjdefvalue == m_adjmax)
+ m_scalepos = -m_scalepos;
+
+ // make the scaling the same for easier coding when we need to scale
+ m_scaleneg = m_scalepos;
+
+ // reverse point is at max
+ m_reverse_val = m_maximum;
+ }
+ }
+
+ // relative and positional controls all map directly with a 512x scale factor
+ else
+ {
+ // The relative code is set up to allow specifing PORT_MINMAX and default values.
+ // The validity checks are purposely set up to not allow you to use anything other
+ // a default of 0 and PORT_MINMAX(0,mask). This is in case the need arises to use
+ // this feature in the future. Keeping the code in does not hurt anything.
+ if (m_adjmin > m_adjmax)
+ // adjust for signed
+ m_adjmin = -m_adjmin;
+
+ if (m_wraps)
+ m_adjmax++;
+
+ m_minimum = (m_adjmin - m_adjdefvalue) * INPUT_RELATIVE_PER_PIXEL;
+ m_maximum = (m_adjmax - m_adjdefvalue) * INPUT_RELATIVE_PER_PIXEL;
+
+ // make the scaling the same for easier coding when we need to scale
+ m_scaleneg = m_scalepos = compute_scale(1, INPUT_RELATIVE_PER_PIXEL);
+
+ if (m_field.analog_reset())
+ // delta values reverse from center
+ m_reverse_val = 0;
+ else
+ {
+ // positional controls reverse from their max range
+ m_reverse_val = m_maximum + m_minimum;
+
+ // relative controls reverse from 1 past their max range
+ if (m_wraps)
+ m_reverse_val -= INPUT_RELATIVE_PER_PIXEL;
+ }
+ }
+
+ // compute scale for keypresses
+ m_keyscalepos = recip_scale(m_scalepos);
+ m_keyscaleneg = recip_scale(m_scaleneg);
+}
+
+
+//-------------------------------------------------
+// apply_min_max - clamp the given input value to
+// the appropriate min/max for the analog control
+//-------------------------------------------------
+
+inline INT32 analog_field::apply_min_max(INT32 value) const
+{
+ // take the analog minimum and maximum values and apply the inverse of the
+ // sensitivity so that we can clamp against them before applying sensitivity
+ INT32 adjmin = apply_inverse_sensitivity(m_minimum);
+ INT32 adjmax = apply_inverse_sensitivity(m_maximum);
+
+ // for absolute devices, clamp to the bounds absolutely
+ if (!m_wraps)
+ {
+ if (value > adjmax)
+ value = adjmax;
+ else if (value < adjmin)
+ value = adjmin;
+ }
+
+ // for relative devices, wrap around when we go past the edge
+ else
+ {
+ INT32 range = adjmax - adjmin;
+ // rolls to other end when 1 position past end.
+ value = (value - adjmin) % range;
+ if (value < 0)
+ value += range;
+ value += adjmin;
+ }
+
+ return value;
+}
+
+
+//-------------------------------------------------
+// apply_sensitivity - apply a sensitivity
+// adjustment for a current value
+//-------------------------------------------------
+
+inline INT32 analog_field::apply_sensitivity(INT32 value) const
+{
+ return INT32((INT64(value) * m_sensitivity) / 100.0 + 0.5);
+}
+
+
+//-------------------------------------------------
+// apply_inverse_sensitivity - reverse-apply the
+// sensitivity adjustment for a current value
+//-------------------------------------------------
+
+inline INT32 analog_field::apply_inverse_sensitivity(INT32 value) const
+{
+ return INT32((INT64(value) * 100) / m_sensitivity);
+}
+
+
+//-------------------------------------------------
+// apply_settings - return the value of an
+// analog input
+//-------------------------------------------------
+
+INT32 analog_field::apply_settings(INT32 value) const
+{
+ // apply the min/max and then the sensitivity
+ value = apply_min_max(value);
+ value = apply_sensitivity(value);
+
+ // apply reversal if needed
+ if (m_reverse)
+ value = m_reverse_val - value;
+ else if (m_single_scale)
+ // it's a pedal or the default value is equal to min/max
+ // so we need to adjust the center to the minimum
+ value -= INPUT_ABSOLUTE_MIN;
+
+ // map differently for positive and negative values
+ if (value >= 0)
+ value = apply_scale(value, m_scalepos);
+ else
+ value = apply_scale(value, m_scaleneg);
+ value += m_adjdefvalue;
+
+ return value;
+}
+
+
+//-------------------------------------------------
+// frame_update - update the internals of a
+// single analog field periodically
+//-------------------------------------------------
+
+void analog_field::frame_update(running_machine &machine)
+{
+ // clamp the previous value to the min/max range and remember it
+ m_previous = m_accum = apply_min_max(m_accum);
+
+ // get the new raw analog value and its type
+ input_item_class itemclass;
+ INT32 rawvalue = machine.input().seq_axis_value(m_field.seq(SEQ_TYPE_STANDARD), itemclass);
+
+ // if we got an absolute input, it overrides everything else
+ if (itemclass == ITEM_CLASS_ABSOLUTE)
+ {
+ if (m_previousanalog != rawvalue)
+ {
+ // only update if analog value changed
+ m_previousanalog = rawvalue;
+
+ // apply the inverse of the sensitivity to the raw value so that
+ // it will still cover the full min->max range requested after
+ // we apply the sensitivity adjustment
+ if (m_absolute || m_field.analog_reset())
+ {
+ // if port is absolute, then just return the absolute data supplied
+ m_accum = apply_inverse_sensitivity(rawvalue);
+ }
+ else if (m_positionalscale != 0)
+ {
+ // if port is positional, we will take the full analog control and divide it
+ // into positions, that way as the control is moved full scale,
+ // it moves through all the positions
+ rawvalue = apply_scale(rawvalue - INPUT_ABSOLUTE_MIN, m_positionalscale) * INPUT_RELATIVE_PER_PIXEL + m_minimum;
+
+ // clamp the high value so it does not roll over
+ rawvalue = MIN(rawvalue, m_maximum);
+ m_accum = apply_inverse_sensitivity(rawvalue);
+ }
+ else
+ // if port is relative, we use the value to simulate the speed of relative movement
+ // sensitivity adjustment is allowed for this mode
+ m_accum += rawvalue;
+
+ m_lastdigital = false;
+ // do not bother with other control types if the analog data is changing
+ return;
+ }
+ else
+ {
+ // we still have to update fake relative from joystick control
+ if (!m_absolute && m_positionalscale == 0)
+ m_accum += rawvalue;
+ }
+ }
+
+ // if we got it from a relative device, use that as the starting delta
+ // also note that the last input was not a digital one
+ INT32 delta = 0;
+ if (itemclass == ITEM_CLASS_RELATIVE && rawvalue != 0)
+ {
+ delta = rawvalue;
+ m_lastdigital = false;
+ }
+
+ INT64 keyscale = (m_accum >= 0) ? m_keyscalepos : m_keyscaleneg;
+
+ // if the decrement code sequence is pressed, add the key delta to
+ // the accumulated delta; also note that the last input was a digital one
+ bool keypressed = false;
+ if (machine.input().seq_pressed(m_field.seq(SEQ_TYPE_DECREMENT)))
+ {
+ keypressed = true;
+ if (m_delta != 0)
+ delta -= apply_scale(m_delta, keyscale);
+ else if (!m_lastdigital)
+ // decrement only once when first pressed
+ delta -= apply_scale(1, keyscale);
+ m_lastdigital = true;
+ }
+
+ // same for the increment code sequence
+ if (machine.input().seq_pressed(m_field.seq(SEQ_TYPE_INCREMENT)))
+ {
+ keypressed = true;
+ if (m_delta)
+ delta += apply_scale(m_delta, keyscale);
+ else if (!m_lastdigital)
+ // increment only once when first pressed
+ delta += apply_scale(1, keyscale);
+ m_lastdigital = true;
+ }
+
+ // if resetting is requested, clear the accumulated position to 0 before
+ // applying the deltas so that we only return this frame's delta
+ // note that centering only works for relative controls
+ // no need to check if absolute here because it is checked by the validity tests
+ if (m_field.analog_reset())
+ m_accum = 0;
+
+ // apply the delta to the accumulated value
+ m_accum += delta;
+
+ // if our last movement was due to a digital input, and if this control
+ // type autocenters, and if neither the increment nor the decrement seq
+ // was pressed, apply autocentering
+ if (m_autocenter)
+ {
+ INT32 center = apply_inverse_sensitivity(m_center);
+ if (m_lastdigital && !keypressed)
+ {
+ // autocenter from positive values
+ if (m_accum >= center)
+ {
+ m_accum -= apply_scale(m_centerdelta, m_keyscalepos);
+ if (m_accum < center)
+ {
+ m_accum = center;
+ m_lastdigital = false;
+ }
+ }
+
+ // autocenter from negative values
+ else
+ {
+ m_accum += apply_scale(m_centerdelta, m_keyscaleneg);
+ if (m_accum > center)
+ {
+ m_accum = center;
+ m_lastdigital = false;
+ }
+ }
+ }
+ }
+ else if (!keypressed)
+ m_lastdigital = false;
+}
+
+
+//-------------------------------------------------
+// read - read the current value and insert into
+// the provided ioport_value
+//-------------------------------------------------
+
+void analog_field::read(ioport_value &result)
+{
+ // do nothing if we're not enabled
+ if (!m_field.enabled())
+ return;
+
+ // start with the raw value
+ INT32 value = m_accum;
+
+ // interpolate if appropriate and if time has passed since the last update
+ if (m_interpolate)
+ value = manager().frame_interpolate(m_previous, m_accum);
+
+ // apply standard analog settings
+ value = apply_settings(value);
+
+ // remap the value if needed
+ if (m_field.remap_table() != NULL)
+ value = m_field.remap_table()[value];
+
+ // invert bits if needed
+ if (m_field.analog_invert())
+ value = ~value;
+
+ // insert into the port
+ result = (result & ~m_field.mask()) | ((value << m_shift) & m_field.mask());
+}
+
+
+//-------------------------------------------------
+// crosshair_read - read a value for crosshairs,
+// scaled between 0 and 1
+//-------------------------------------------------
+
+float analog_field::crosshair_read()
+{
+ INT32 rawvalue = apply_settings(m_accum) & (m_field.mask() >> m_shift);
+ return float(rawvalue - m_adjmin) / float(m_adjmax - m_adjmin);
+}
+
+
+
+/***************************************************************************
+ TOKENIZATION HELPERS
+***************************************************************************/
+
+//-------------------------------------------------
+// token_to_input_type - convert a string token
+// to an input field type and player
+//-------------------------------------------------
+
+ioport_type ioport_manager::token_to_input_type(const char *string, int &player) const
+{
+ // check for our failsafe case first
+ int ipnum;
+ if (sscanf(string, "TYPE_OTHER(%d,%d)", &ipnum, &player) == 2)
+ return ioport_type(ipnum);
+
+ // find the token in the list
+ for (input_type_entry *entry = m_typelist.first(); entry != NULL; entry = entry->next())
+ if (entry->token() != NULL && !strcmp(entry->token(), string))
+ {
+ player = entry->player();
+ return entry->type();
+ }
+
+ // if we fail, return IPT_UNKNOWN
+ player = 0;
+ return IPT_UNKNOWN;
+}
+
+
+//-------------------------------------------------
+// input_type_to_token - convert an input field
+// type and player to a string token
+//-------------------------------------------------
+
+const char *ioport_manager::input_type_to_token(astring &string, ioport_type type, int player)
+{
+ // look up the port and return the token
+ input_type_entry *entry = m_type_to_entry[type][player];
+ if (entry != NULL)
+ return string.cpy(entry->token());
+
+ // if that fails, carry on
+ return string.format("TYPE_OTHER(%d,%d)", type, player);
+}
+
+
+//-------------------------------------------------
+// token_to_seq_type - convert a string to
+// a sequence type
+//-------------------------------------------------
+
+input_seq_type ioport_manager::token_to_seq_type(const char *string)
+{
+ // look up the string in the table of possible sequence types and return the index
+ for (int seqindex = 0; seqindex < ARRAY_LENGTH(seqtypestrings); seqindex++)
+ if (!mame_stricmp(string, seqtypestrings[seqindex]))
+ return input_seq_type(seqindex);
+ return SEQ_TYPE_INVALID;
+}
+
+
+
+//-------------------------------------------------
+// validate_natural_keyboard_statics -
+// validates natural keyboard static data
+//-------------------------------------------------
+
+/*
+int validate_natural_keyboard_statics(void)
+{
+ int i;
+ int error = FALSE;
+ unicode_char last_char = 0;
+ const char_info *ci;
+
+ // check to make sure that charinfo is in order
+ for (i = 0; i < ARRAY_LENGTH(charinfo); i++)
+ {
+ if (last_char >= charinfo[i].ch)
+ {
+ mame_printf_error("inputx: charinfo is out of order; 0x%08x should be higher than 0x%08x\n", charinfo[i].ch, last_char);
+ error = TRUE;
+ }
+ last_char = charinfo[i].ch;
+ }
+
+ // check to make sure that I can look up everything on alternate_charmap
+ for (i = 0; i < ARRAY_LENGTH(charinfo); i++)
+ {
+ ci = char_info::find(charinfo[i].ch);
+ if (ci != &charinfo[i])
+ {
+ mame_printf_error("ioport: expected char_info::find(0x%08x) to work properly\n", charinfo[i].ch);
+ error = TRUE;
+ }
+ }
+ return error;
+}
+*/
+
+