mame/src/emu/ioport.cpp
Vas Crabb 1615b8551a
util/ioprocs.cpp: Added wrappers for common patterns. (#11608)
emu/diimage.h: Removed fread overloads that allocate memory for output.

util/core_file.cpp: Changed output size of load to size_t.
2024-02-25 02:27:26 +11:00

4077 lines
120 KiB
C++

// license:BSD-3-Clause
// copyright-holders:Aaron Giles
/***************************************************************************
ioport.cpp
Input/output port handling.
***************************************************************************/
#include "emu.h"
#include "config.h"
#include "emuopts.h"
#include "fileio.h"
#include "inputdev.h"
#include "main.h"
#include "natkeyboard.h"
#include "profiler.h"
#include "ui/uimain.h"
#include "util/corestr.h"
#include "util/ioprocsfilter.h"
#include "util/language.h"
#include "util/multibyte.h"
#include "util/unicode.h"
#include "util/xmlfile.h"
#include "osdepend.h"
#include <algorithm>
#include <cctype>
#include <ctime>
namespace {
// 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
//**************************************************************************
// CONSTANTS
//**************************************************************************
const int SPACE_COUNT = 3;
//**************************************************************************
// INLINE FUNCTIONS
//**************************************************************************
//-------------------------------------------------
// compute_scale -- compute an 8.24 scale value
// from a numerator and a denominator
//-------------------------------------------------
inline s64 compute_scale(s32 num, s32 den)
{
return (s64(num) << 24) / den;
}
//-------------------------------------------------
// recip_scale -- compute an 8.24 reciprocal of
// an 8.24 scale value
//-------------------------------------------------
inline s64 recip_scale(s64 scale)
{
return (s64(1) << 48) / scale;
}
//-------------------------------------------------
// apply_scale -- apply an 8.24 scale value to
// a 32-bit value
//-------------------------------------------------
inline s32 apply_scale(s32 value, s64 scale)
{
return (s64(value) * scale) / (1 << 24);
}
//-------------------------------------------------
// compute_shift -- get shift required to right-
// align an I/O port field value
//-------------------------------------------------
inline u8 compute_shift(ioport_value mask)
{
u8 result = 0U;
while (mask && !BIT(mask, 0))
{
mask >>= 1;
++result;
}
return result;
}
//**************************************************************************
// COMMON SHARED STRINGS
//**************************************************************************
const struct
{
u32 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_5C_2C, "5 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_3C_5C, "3 Coins/5 Credits" },
{ 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" },
};
inline bool input_seq_good(running_machine &machine, input_seq const &seq)
{
if (INPUT_CODE_INVALID == seq[0])
return false;
else if (seq.empty())
return true;
else
return input_seq::end_code != machine.input().seq_clean(seq)[0];
}
std::string substitute_player(std::string_view name, u8 player)
{
using util::lang_translate;
std::string result;
while (!name.empty())
{
auto const found = name.find('%');
if ((std::string_view::npos == found) || (name.length() == found + 1))
{
result.append(name);
break;
}
switch (name[found + 1])
{
case '%':
result.append(name.substr(0, found + 1));
break;
case 'p':
result.append(name.substr(0, found));
result.append(util::string_format(_("input-name", "P%1$u"), player + 1));
break;
default:
result.append(name.substr(0, found + 2));
}
name.remove_prefix(found + 2);
}
return result;
}
// ======================> inp_header
// header at the front of INP files
class inp_header
{
public:
// parameters
static constexpr unsigned MAJVERSION = 3;
static constexpr unsigned MINVERSION = 0;
bool read(emu_file &f)
{
return f.read(m_data, sizeof(m_data)) == sizeof(m_data);
}
bool write(emu_file &f) const
{
return f.write(m_data, sizeof(m_data)) == sizeof(m_data);
}
bool check_magic() const
{
return 0 == std::memcmp(MAGIC, m_data + OFFS_MAGIC, OFFS_BASETIME - OFFS_MAGIC);
}
u64 get_basetime() const
{
return get_u64le(m_data + OFFS_BASETIME);
}
unsigned get_majversion() const
{
return m_data[OFFS_MAJVERSION];
}
unsigned get_minversion() const
{
return m_data[OFFS_MINVERSION];
}
std::string get_sysname() const
{
return get_string<OFFS_SYSNAME, OFFS_APPDESC>();
}
std::string get_appdesc() const
{
return get_string<OFFS_APPDESC, OFFS_END>();
}
void set_magic()
{
std::memcpy(m_data + OFFS_MAGIC, MAGIC, OFFS_BASETIME - OFFS_MAGIC);
}
void set_basetime(u64 time)
{
put_u64le(m_data + OFFS_BASETIME, time);
}
void set_version()
{
m_data[OFFS_MAJVERSION] = MAJVERSION;
m_data[OFFS_MINVERSION] = MINVERSION;
}
void set_sysname(std::string const &name)
{
set_string<OFFS_SYSNAME, OFFS_APPDESC>(name);
}
void set_appdesc(std::string const &desc)
{
set_string<OFFS_APPDESC, OFFS_END>(desc);
}
private:
template <std::size_t BEGIN, std::size_t END> void set_string(std::string const &str)
{
std::size_t const used = (std::min<std::size_t>)(str.size() + 1, END - BEGIN);
std::memcpy(m_data + BEGIN, str.c_str(), used);
if ((END - BEGIN) > used)
std::memset(m_data + BEGIN + used, 0, (END - BEGIN) - used);
}
template <std::size_t BEGIN, std::size_t END> std::string get_string() const
{
char const *const begin = reinterpret_cast<char const *>(m_data + BEGIN);
return std::string(begin, std::find(begin, reinterpret_cast<char const *>(m_data + END), '\0'));
}
static constexpr std::size_t OFFS_MAGIC = 0x00; // 0x08 bytes
static constexpr std::size_t OFFS_BASETIME = 0x08; // 0x08 bytes (little-endian binary integer)
static constexpr std::size_t OFFS_MAJVERSION = 0x10; // 0x01 bytes (binary integer)
static constexpr std::size_t OFFS_MINVERSION = 0x11; // 0x01 bytes (binary integer)
// 0x02 bytes reserved
static constexpr std::size_t OFFS_SYSNAME = 0x14; // 0x0c bytes (ASCII)
static constexpr std::size_t OFFS_APPDESC = 0x20; // 0x20 bytes (ASCII)
static constexpr std::size_t OFFS_END = 0x40;
static u8 const MAGIC[OFFS_BASETIME - OFFS_MAGIC];
u8 m_data[OFFS_END];
};
} // anonymous namespace
// XML attributes for the different types
const char *const ioport_manager::seqtypestrings[] = { "standard", "increment", "decrement" };
u8 const inp_header::MAGIC[inp_header::OFFS_BASETIME - inp_header::OFFS_MAGIC] = { 'M', 'A', 'M', 'E', 'I', 'N', 'P', 0 };
//**************************************************************************
// BUILT-IN CORE MAPPINGS
//**************************************************************************
#include "inpttype.ipp"
//**************************************************************************
// 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, std::string &errorbuf)
{
// no constructor, no list
ioport_constructor constructor = device.input_ports();
if (constructor == nullptr)
return;
// reset error buffer
errorbuf.clear();
// detokenize into the list
(*constructor)(device, *this, errorbuf);
// collapse fields and sort the list
for (auto &port : *this)
port.second->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) noexcept
: 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) noexcept
: 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;
}
//-------------------------------------------------
// name - gets the display name for the input
// type
//-------------------------------------------------
std::string input_type_entry::name() const
{
using util::lang_translate;
if (!m_name)
return std::string();
else if ((group() < IPG_PLAYER1) || (group() > IPG_PLAYER10))
return _("input-name", m_name);
else
return substitute_player(_("input-name", m_name), player());
}
//-------------------------------------------------
// replace_code - replace all instances of
// oldcodewith newcode in all sequences
//-------------------------------------------------
void input_type_entry::replace_code(input_code oldcode, input_code newcode) noexcept
{
for (input_seq &seq : m_seq)
seq.replace(oldcode, newcode);
}
//-------------------------------------------------
// configure_osd - set the token and name of an
// OSD entry
//-------------------------------------------------
void input_type_entry::configure_osd(const char *token, const char *name) noexcept
{
assert(m_type >= IPT_OSD_1 && m_type <= IPT_OSD_16);
m_token = token;
m_name = name;
}
//-------------------------------------------------
// restore_default_seq - restores the sequence
// from the default
//-------------------------------------------------
void input_type_entry::restore_default_seq() noexcept
{
m_seq = m_defseq;
}
//**************************************************************************
// 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)
{
}
//-------------------------------------------------
// set_axis - configure a single axis of a
// digital joystick
//-------------------------------------------------
digital_joystick::direction_t digital_joystick::add_axis(ioport_field &field)
{
direction_t direction = direction_t((field.type() - (IPT_DIGITAL_JOYSTICK_FIRST + 1)) % 4);
m_field[direction].emplace_front(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 = nullptr;
for (direction_t direction = JOYDIR_UP; direction < JOYDIR_COUNT; ++direction)
for (const std::reference_wrapper<ioport_field> &i : m_field[direction])
{
machine = &i.get().machine();
if (machine->input().seq_pressed(i.get().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
// 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. Let's pick the horizontal axis.
//
if ((m_current4way & (UP_BIT | DOWN_BIT)) &&
(m_current4way & (LEFT_BIT | RIGHT_BIT)))
{
m_current4way &= ~(UP_BIT | DOWN_BIT);
}
}
}
//**************************************************************************
// I/O PORT CONDITION
//**************************************************************************
//-------------------------------------------------
// eval - evaluate condition
//-------------------------------------------------
bool ioport_condition::eval() 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 const condvalue = m_port->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;
}
//-------------------------------------------------
// initialize - create the live state
//-------------------------------------------------
void ioport_condition::initialize(device_t &device)
{
if (m_tag != nullptr)
m_port = device.ioport(m_tag);
}
//**************************************************************************
// I/O PORT SETTING
//**************************************************************************
//-------------------------------------------------
// ioport_setting - constructor
//-------------------------------------------------
ioport_setting::ioport_setting(ioport_field &field, ioport_value _value, const char *_name)
: m_field(field),
m_value(_value),
m_name(_name)
{
}
//**************************************************************************
// I/O PORT DIP LOCATION
//**************************************************************************
//-------------------------------------------------
// ioport_diplocation - constructor
//-------------------------------------------------
ioport_diplocation::ioport_diplocation(const char *name, u8 swnum, bool invert)
: 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(nullptr),
m_port(port),
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(port.device()),
m_write(port.device()),
m_write_param(0),
m_digital_value(false),
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_crosshair_mapper(port.device()),
m_full_turn_count(0),
m_remap_table(nullptr),
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();
for (int i = 0; i < std::size(m_chars); i++)
std::fill(std::begin(m_chars[i]), std::end(m_chars[i]), char32_t(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 != nullptr)
{
const char *fulltag = port.tag();
for ( ; def->tag != nullptr; def++)
if (device().subtag(def->tag) == fulltag && def->mask == m_mask)
m_defvalue = def->defvalue & m_mask;
}
m_flags |= FIELD_FLAG_TOGGLE;
}
}
//-------------------------------------------------
// ~ioport_field - destructor
//-------------------------------------------------
ioport_field::~ioport_field()
{
}
//-------------------------------------------------
// set_value - programmatically set field value
//-------------------------------------------------
void ioport_field::set_value(ioport_value value)
{
if (is_analog())
live().analog->set_value(s32(value));
else
m_digital_value = value != 0;
}
//-------------------------------------------------
// clear_value - clear programmatic override
//-------------------------------------------------
void ioport_field::clear_value()
{
if (is_analog())
live().analog->clear_value();
else
m_digital_value = false;
}
//-------------------------------------------------
// name - return the field name for a given input
// field (this must never return nullptr)
//-------------------------------------------------
std::string ioport_field::name() const
{
using util::lang_translate;
// if we have an overridden name, use that
if (m_live && !m_live->name.empty())
return m_live->name;
// if no specific name, use the generic name for the type
if (!m_name)
return manager().type_name(m_type, m_player);
// return name for non-controller fields as-is
ioport_group const group = manager().type_group(m_type, m_player);
if ((group < IPG_PLAYER1) || (group > IPG_PLAYER10))
return m_name;
// substitute the player number in if necessary
return substitute_player(m_name, m_player);
}
//-------------------------------------------------
// seq - return the live input sequence for the
// given input field
//-------------------------------------------------
const input_seq &ioport_field::seq(input_seq_type seqtype) const noexcept
{
// if the sequence is not the special default code, return it
if (m_live && !m_live->seq[seqtype].is_default())
return m_live->seq[seqtype];
// otherwise return the default sequence
return defseq(seqtype);
}
//-------------------------------------------------
// defseq - return the default input sequence for
// the given input field
//-------------------------------------------------
const input_seq &ioport_field::defseq(input_seq_type seqtype) const noexcept
{
// 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];
}
//-------------------------------------------------
// set_defseq - dynamically alter the default
// input sequence for the given input field
//-------------------------------------------------
void ioport_field::set_defseq(input_seq_type seqtype, const input_seq &newseq)
{
// set the new sequence
m_seq[seqtype] = newseq;
}
//-------------------------------------------------
// type_class - return the type class for this
// field
//-------------------------------------------------
ioport_type_class ioport_field::type_class() const noexcept
{
// inputs associated with specific players
ioport_group const group = manager().type_group(m_type, m_player);
if ((group >= IPG_PLAYER1) && (group <= IPG_PLAYER10))
return INPUT_CLASS_CONTROLLER;
// keys (names derived from character codes)
if (m_type == IPT_KEYPAD || m_type == IPT_KEYBOARD)
return INPUT_CLASS_KEYBOARD;
// configuration settings (specific names required)
if (m_type == IPT_CONFIG || m_type == IPT_ADJUSTER)
return INPUT_CLASS_CONFIG;
// DIP switches (specific names required)
if (m_type == IPT_DIPSWITCH)
return INPUT_CLASS_DIPSWITCH;
// miscellaneous non-player inputs (named and user-mappable)
if (group == IPG_OTHER || (group == IPG_INVALID && m_name != nullptr))
return INPUT_CLASS_MISC;
// internal inputs (these may be anonymous)
return INPUT_CLASS_INTERNAL;
}
//-------------------------------------------------
// keyboard_codes - accesses a particular keyboard
// code list
//-------------------------------------------------
std::vector<char32_t> ioport_field::keyboard_codes(int which) const
{
if (which >= std::size(m_chars))
throw emu_fatalerror("Tried to access keyboard_code with out-of-range index %d\n", which);
std::vector<char32_t> result;
for (int i = 0; i < std::size(m_chars[which]) && m_chars[which][i] != 0; i++)
result.push_back(m_chars[which][i]);
return result;
}
//-------------------------------------------------
// key_name - returns the name of a specific key
//-------------------------------------------------
std::string ioport_field::key_name(int which) const
{
std::vector<char32_t> codes = keyboard_codes(which);
char32_t ch = codes.empty() ? 0 : codes[0];
// attempt to get the string from the character info table
switch (ch)
{
case 8: return "Backspace";
case 9: return "Tab";
case 12: return "Clear";
case 13: return "Enter";
case 27: return "Esc";
case 32: return "Space";
case UCHAR_SHIFT_1: return "Shift";
case UCHAR_SHIFT_2: return "Ctrl";
case UCHAR_MAMEKEY(ESC): return "Esc";
case UCHAR_MAMEKEY(INSERT): return "Insert";
case UCHAR_MAMEKEY(DEL): return "Delete";
case UCHAR_MAMEKEY(HOME): return "Home";
case UCHAR_MAMEKEY(END): return "End";
case UCHAR_MAMEKEY(PGUP): return "Page Up";
case UCHAR_MAMEKEY(PGDN): return "Page Down";
case UCHAR_MAMEKEY(LEFT): return "Cursor Left";
case UCHAR_MAMEKEY(RIGHT): return "Cursor Right";
case UCHAR_MAMEKEY(UP): return "Cursor Up";
case UCHAR_MAMEKEY(DOWN): return "Cursor Down";
case UCHAR_MAMEKEY(SLASH_PAD): return "Keypad /";
case UCHAR_MAMEKEY(ASTERISK): return "Keypad *";
case UCHAR_MAMEKEY(MINUS_PAD): return "Keypad -";
case UCHAR_MAMEKEY(PLUS_PAD): return "Keypad +";
case UCHAR_MAMEKEY(DEL_PAD): return "Keypad .";
case UCHAR_MAMEKEY(ENTER_PAD): return "Keypad Enter";
case UCHAR_MAMEKEY(BS_PAD): return "Keypad Backspace";
case UCHAR_MAMEKEY(TAB_PAD): return "Keypad Tab";
case UCHAR_MAMEKEY(00_PAD): return "Keypad 00";
case UCHAR_MAMEKEY(000_PAD): return "Keypad 000";
case UCHAR_MAMEKEY(COMMA_PAD): return "Keypad ,";
case UCHAR_MAMEKEY(EQUALS_PAD): return "Keypad =";
case UCHAR_MAMEKEY(PRTSCR): return "Print Screen";
case UCHAR_MAMEKEY(PAUSE): return "Pause";
case UCHAR_MAMEKEY(LSHIFT): return "Left Shift";
case UCHAR_MAMEKEY(RSHIFT): return "Right Shift";
case UCHAR_MAMEKEY(LCONTROL): return "Left Ctrl";
case UCHAR_MAMEKEY(RCONTROL): return "Right Ctrl";
case UCHAR_MAMEKEY(LALT): return "Left Alt";
case UCHAR_MAMEKEY(RALT): return "Right Alt";
case UCHAR_MAMEKEY(SCRLOCK): return "Scroll Lock";
case UCHAR_MAMEKEY(NUMLOCK): return "Num Lock";
case UCHAR_MAMEKEY(CAPSLOCK): return "Caps Lock";
case UCHAR_MAMEKEY(LWIN): return "Left Win";
case UCHAR_MAMEKEY(RWIN): return "Right Win";
case UCHAR_MAMEKEY(MENU): return "Menu";
case UCHAR_MAMEKEY(CANCEL): return "Break";
default: break;
}
// handle function keys
if (ch >= UCHAR_MAMEKEY(F1) && ch <= UCHAR_MAMEKEY(F20))
return util::string_format("F%d", ch - UCHAR_MAMEKEY(F1) + 1);
// handle 0-9 on numeric keypad
if (ch >= UCHAR_MAMEKEY(0_PAD) && ch <= UCHAR_MAMEKEY(9_PAD))
return util::string_format("Keypad %d", ch - UCHAR_MAMEKEY(0_PAD));
// if that doesn't work, convert to UTF-8
if (ch > 0x7F || isprint(ch))
return utf8_from_uchar(ch);
// otherwise, opt for question marks
return "???";
}
//-------------------------------------------------
// get_user_settings - return the current
// settings for the given input field
//-------------------------------------------------
void ioport_field::get_user_settings(user_settings &settings) const
{
// zap the entire structure
settings = user_settings();
// copy the basics
for (input_seq_type seqtype = SEQ_TYPE_STANDARD; seqtype < SEQ_TYPE_TOTAL; ++seqtype)
{
settings.seq[seqtype] = seq(seqtype);
if (m_live)
settings.cfg[seqtype] = m_live->cfg[seqtype];
}
// if there's a list of settings or we're an adjuster, copy the current value
if (!m_settinglist.empty() || m_type == IPT_ADJUSTER)
settings.value = m_live->value;
if (m_live->analog != nullptr)
{
// if there's analog data, extract the analog settings
settings.sensitivity = m_live->analog->sensitivity();
settings.delta = m_live->analog->delta();
settings.centerdelta = m_live->analog->centerdelta();
settings.reverse = m_live->analog->reverse();
}
else
{
// non-analog settings
settings.toggle = m_live->toggle;
}
}
//-------------------------------------------------
// 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)
{
if (settings.seq[seqtype].is_default())
m_live->seq[seqtype].set_default();
else
m_live->seq[seqtype] = settings.seq[seqtype];
m_live->cfg[seqtype] = settings.cfg[seqtype];
}
// if there's a list of settings or we're an adjuster, copy the current value
if (!m_settinglist.empty() || m_type == IPT_ADJUSTER)
m_live->value = settings.value;
if (m_live->analog)
{
// if there's analog data, extract the analog settings
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;
}
else
{
// non-analog settings
m_live->toggle = settings.toggle;
}
}
//-------------------------------------------------
// 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(!m_settinglist.empty());
// scan the list of settings looking for a match on the current value
for (ioport_setting const &setting : m_settinglist)
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(!m_settinglist.empty());
// scan the list of settings looking for a match on the current value
for (ioport_setting const &setting : m_settinglist)
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(!m_settinglist.empty());
// scan the list of settings looking for a match on the current value
auto prevsetting = m_settinglist.end();
bool found_match = false;
for (auto setting = m_settinglist.begin(); m_settinglist.end() != setting; ++setting)
{
if (setting->enabled())
{
if (setting->value() == m_live->value)
{
found_match = true;
if (m_settinglist.end() != prevsetting)
break;
}
prevsetting = setting;
}
}
// if we didn't find a matching value, select the first
if (!found_match)
{
prevsetting = m_settinglist.begin();
while ((m_settinglist.end() != prevsetting) && !prevsetting->enabled())
++prevsetting;
}
// update the value to the previous one
if (m_settinglist.end() != prevsetting)
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(!m_settinglist.empty());
// scan the list of settings looking for a match on the current value
bool found = false;
for (ioport_setting const &setting : m_settinglist)
{
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(!m_settinglist.empty());
// scan the list of settings looking for a match on the current value
auto setting = m_settinglist.begin();
while ((m_settinglist.end() != setting) && (!setting->enabled() || (setting->value() != m_live->value)))
++setting;
// if we found one, scan forward for the next valid one
auto nextsetting = setting;
if (m_settinglist.end() != nextsetting)
{
++nextsetting;
while ((m_settinglist.end() != nextsetting) && !nextsetting->enabled())
++nextsetting;
}
// if we hit the end, search from the beginning
if (m_settinglist.end() == nextsetting)
{
nextsetting = m_settinglist.begin();
while ((m_settinglist.end() != nextsetting) && !nextsetting->enabled())
++nextsetting;
}
// update the value to the previous one
if (m_settinglist.end() != nextsetting)
m_live->value = nextsetting->value();
}
//-------------------------------------------------
// frame_update_digital - get the state of a
// digital field
//-------------------------------------------------
void ioport_field::frame_update(ioport_value &result)
{
// skip if not enabled
if (!enabled())
return;
// handle analog inputs first
if (m_live->analog != nullptr)
{
m_live->analog->frame_update(machine());
return;
}
// if UI is active, ignore digital inputs
if (machine().ui().is_menu_active())
return;
// if user input is locked out here, bail
if (m_live->lockout)
{
// use just the digital value
if (m_digital_value)
result |= m_mask;
return;
}
// if the state changed, look for switch down/switch up
bool curstate = m_digital_value || machine().input().seq_pressed(seq());
bool changed = false;
if (curstate != m_live->last)
{
m_live->last = curstate;
changed = true;
}
// 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)
{
// impulse 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 (m_live->toggle)
{
if (m_settinglist.empty())
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 (m_live->toggle)
curstate = false;
// additional logic to restrict digital joysticks
if (curstate && !m_digital_value && m_live->joystick != nullptr && m_way != 16 && !machine().options().joystick_contradictory())
{
u8 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 && machine().bookkeeping().coin_lockout_get_state(m_type - IPT_COIN1))
{
bool verbose = machine().options().verbose();
#ifdef MAME_DEBUG
verbose = true;
#endif
if (machine().options().coin_lockout())
{
if (verbose)
machine().ui().popup_time(3, "Coinlock disabled %s.", name());
curstate = false;
}
else
if (verbose)
machine().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_read - compute the crosshair
// position
//-------------------------------------------------
float ioport_field::crosshair_read() const
{
float value = m_live->analog->crosshair_read();
// apply the scale and offset
if (m_crosshair_scale < 0)
value = -(1.0f - 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(value);
return value;
}
//-------------------------------------------------
// expand_diplocation - expand a string-based
// DIP location into a linked list of
// descriptions
//-------------------------------------------------
void ioport_field::expand_diplocation(const char *location, std::string &errorbuf)
{
// if nothing present, bail
if (!location)
return;
m_diploclist.clear();
// parse the string
std::string name; // Don't move this variable inside the loop, lastname's lifetime depends on it being outside
const char *lastname = nullptr;
const char *curentry = location;
int entries = 0;
while (*curentry != 0)
{
// find the end of this entry
const char *comma = strchr(curentry, ',');
if (comma == nullptr)
comma = curentry + strlen(curentry);
// extract it to tempbuf
std::string tempstr(curentry, comma - curentry);
// first extract the switch name if present
const char *number = tempstr.c_str();
const char *colon = strchr(tempstr.c_str(), ':');
if (colon != nullptr)
{
// allocate and copy the name if it is present
lastname = name.assign(number, colon - number).c_str();
number = colon + 1;
}
else
{
// otherwise, just copy the last name
if (lastname == nullptr)
{
errorbuf.append(string_format("Switch location '%s' missing switch name!\n", location));
lastname = (char *)"UNK";
}
name.assign(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.append(string_format("Switch location '%s' has invalid format!\n", location));
// allocate a new entry
m_diploclist.emplace_back(name.c_str(), 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.append(string_format("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.resolve();
m_write.resolve();
m_crosshair_mapper.resolve();
// allocate live state
m_live = std::make_unique<ioport_field_live>(*this, analog);
m_condition.initialize(device());
for (ioport_setting &setting : m_settinglist)
setting.condition().initialize(setting.device());
}
//**************************************************************************
// I/O PORT FIELD LIVE
//**************************************************************************
//-------------------------------------------------
// ioport_field_live - constructor
//-------------------------------------------------
ioport_field_live::ioport_field_live(ioport_field &field, analog_field *analog)
: analog(analog),
joystick(nullptr),
value(field.defvalue()),
impulse(0),
last(0),
toggle(field.toggle()),
joydir(digital_joystick::JOYDIR_COUNT),
lockout(false)
{
// 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->add_axis(field);
}
// Name keyboard key names
if (field.type_class() == INPUT_CLASS_KEYBOARD && field.specific_name() == nullptr)
{
// loop through each character on the field
for (int which = 0; which < (1 << (UCHAR_SHIFT_END - UCHAR_SHIFT_BEGIN + 1)); which++)
{
std::vector<char32_t> const codes = field.keyboard_codes(which);
if (codes.empty())
break;
name.append(string_format("%-*s ", std::max(SPACE_COUNT - 1, 0), field.key_name(which)));
}
// special case
if (name.empty())
name.assign("Unnamed Key");
else
{
// trim extra spaces
auto pos = name.find_last_not_of(' ');
assert(pos < name.size());
name.erase(pos + 1);
}
}
}
//**************************************************************************
// I/O PORT
//**************************************************************************
//-------------------------------------------------
// ioport_port - constructor
//-------------------------------------------------
ioport_port::ioport_port(device_t &owner, const char *tag)
: m_next(nullptr),
m_device(owner),
m_tag(tag),
m_modcount(0),
m_active(0)
{
}
//-------------------------------------------------
// ~ioport_port - destructor
//-------------------------------------------------
ioport_port::~ioport_port()
{
}
//-------------------------------------------------
// 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) const
{
// if we got the port, look for the field
for (ioport_field &field : fields())
if ((field.mask() & mask) != 0 && field.enabled())
return &field;
return nullptr;
}
//-------------------------------------------------
// read - return the value of an I/O port
//-------------------------------------------------
ioport_value ioport_port::read()
{
if (!manager().safe_to_read())
throw emu_fatalerror("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)
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)
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)
if (dynfield.field().type() == IPT_OUTPUT)
dynfield.write(m_live->outputvalue ^ dynfield.field().defvalue());
}
//-------------------------------------------------
// frame_update - once/frame update
//-------------------------------------------------
void ioport_port::frame_update()
{
// 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 : m_fieldlist)
field.frame_update(m_live->digital);
}
//-------------------------------------------------
// collapse_fields - remove any fields that are
// wholly overlapped by other fields
//-------------------------------------------------
void ioport_port::collapse_fields(std::string &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 != nullptr)
{
// 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, std::string &errorbuf)
{
// verify against the disallowed bits, but only if we are condition-free
if (newfield.condition().none())
{
if ((newfield.mask() & disallowedbits) != 0)
errorbuf.append(string_format("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 = m_fieldlist.first(); field != nullptr; field = nextfield)
{
nextfield = field->next();
if ((field->mask() & newfield.mask()) &&
(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 (!field->mask() || (INPUT_PORT_OVERRIDE_FULLY_NUKES_PREVIOUS && (field->type() != IPT_UNUSED) && (field->type() != IPT_UNKNOWN)))
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 = m_fieldlist.first(); field != nullptr; 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 = std::make_unique<ioport_port_live>(*this);
}
//-------------------------------------------------
// update_defvalue - force an update to the input
// port values based on current conditions
//-------------------------------------------------
void ioport_port::update_defvalue(bool flush_defaults)
{
// only clear on the first pass
if (flush_defaults)
m_live->defvalue = 0;
// recompute the default value for the entire port
for (ioport_field &field : m_fieldlist)
if (field.enabled())
m_live->defvalue = (m_live->defvalue & ~field.mask()) | (field.live().value & field.mask());
}
//**************************************************************************
// 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.fields())
{
// allocate analog state if it's analog
analog_field *analog = nullptr;
if (field.is_analog())
analog = &analoglist.emplace_back(field);
// allocate a dynamic field for reading
if (field.has_dynamic_read())
readlist.emplace_back(field);
// allocate a dynamic field for writing
if (field.has_dynamic_write())
writelist.emplace_back(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_last_frame_time(attotime::zero)
, m_last_delta_nsec(0)
, m_playback_accumulated_speed(0)
, m_playback_accumulated_frames(0)
, m_deselected_card_config()
, m_applied_device_defaults(false)
{
for (auto &entries : m_type_to_entry)
std::fill(std::begin(entries), std::end(entries), nullptr);
}
//-------------------------------------------------
// 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(&ioport_manager::exit, this));
machine().add_notifier(MACHINE_NOTIFY_FRAME, machine_notify_delegate(&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_enumerator iter(machine().root_device());
for (device_t &device : iter)
{
std::string errors;
m_portlist.append(device, errors);
if (!errors.empty())
osd_printf_error("Input port errors:\n%s", errors);
}
// renumber player numbers for controller ports
int player_offset = 0;
for (device_t &device : iter)
{
int players = 0;
for (auto &port : m_portlist)
{
if (&port.second->device() == &device)
{
for (ioport_field &field : port.second->fields())
{
if (field.type_class() == INPUT_CLASS_CONTROLLER)
{
if (players < field.player() + 1)
players = field.player() + 1;
field.set_player(field.player() + player_offset);
}
}
}
}
player_offset += players;
}
// allocate live structures to mirror the configuration
for (auto &port : m_portlist)
port.second->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 }, 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 }, OPTION_LIGHTGUN_DEVICE, "lightgun");
init_autoselect_devices({ IPT_POSITIONAL, IPT_POSITIONAL_V }, OPTION_POSITIONAL_DEVICE, "positional");
init_autoselect_devices({ IPT_DIAL, IPT_DIAL_V }, OPTION_DIAL_DEVICE, "dial");
init_autoselect_devices({ IPT_TRACKBALL_X, IPT_TRACKBALL_Y }, OPTION_TRACKBALL_DEVICE, "trackball");
init_autoselect_devices({ IPT_MOUSE_X, IPT_MOUSE_Y }, OPTION_MOUSE_DEVICE, "mouse");
// look for 4-way diagonal 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 (auto &port : m_portlist)
for (ioport_field const &field : port.second->fields())
if (field.live().joystick != nullptr && field.rotated())
{
input_class_joystick &devclass = downcast<input_class_joystick &>(machine().input().device_class(DEVICE_CLASS_JOYSTICK));
devclass.set_global_joystick_map(input_class_joystick::map_4way_diagonal);
break;
}
// register callbacks for when we load configurations
machine().configuration().config_register(
"input",
configuration_manager::load_delegate(&ioport_manager::load_config, this),
configuration_manager::save_delegate(&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
emplace_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 : m_typelist)
{
// first copy all the OSD-updated sequences into our current state
curtype.restore_default_seq();
// 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(std::initializer_list<ioport_type> types, std::string_view option, std::string_view ananame)
{
static std::pair<char const *, char const *> const CLASS_OPTIONS[] = {
{ "mouse", OPTION_MOUSE },
{ "joystick", OPTION_JOYSTICK },
{ "lightgun", OPTION_LIGHTGUN } };
// if nothing specified, ignore the option
auto const autooption = machine().options().get_entry(option);
char const *const autoclass = autooption->value();
if (!autoclass || !*autoclass || !std::strcmp(autoclass, "none"))
return;
// if the device class is enabled anyway or disabled at a higher priority level, do nothing
auto const classname = std::find_if(
std::begin(CLASS_OPTIONS),
std::end(CLASS_OPTIONS),
[&autoclass] (auto const &x) { return !std::strcmp(autoclass, x.first); });
if (std::end(CLASS_OPTIONS) != classname)
{
if (machine().options().bool_value(classname->second))
return;
auto const classoption = machine().options().get_entry(classname->second);
if (classoption->priority() > autooption->priority())
{
osd_printf_verbose("Input: Won't autoenable %s in presence of a %s as it's disabled at a higher priority\n", autoclass, ananame);
return;
}
}
// find matching device class
input_class *autoenable_class = nullptr;
for (input_device_class devclass = DEVICE_CLASS_FIRST_VALID; devclass <= DEVICE_CLASS_LAST_VALID; ++devclass)
{
if (!std::strcmp(autoclass, machine().input().device_class(devclass).name()))
{
autoenable_class = &machine().input().device_class(devclass);
break;
}
}
if (!autoenable_class)
{
osd_printf_error("Invalid %s value %s; reverting to keyboard\n", option, autoclass);
autoenable_class = &machine().input().device_class(DEVICE_CLASS_KEYBOARD);
}
// nothing to do if the class is already enabled
if (autoenable_class->enabled())
return;
// scan the port list
for (auto &port : m_portlist)
{
for (ioport_field const &field : port.second->fields())
{
// if this port type is in use, apply the autoselect criteria
if (std::find(std::begin(types), std::end(types), field.type()) != std::end(types))
{
osd_printf_verbose("Input: Autoenabling %s due to presence of a %s\n", autoenable_class->name(), ananame);
autoenable_class->enable();
return;
}
}
}
}
//-------------------------------------------------
// 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();
}
//-------------------------------------------------
// ~ioport_manager - destructor
//-------------------------------------------------
ioport_manager::~ioport_manager()
{
}
//-------------------------------------------------
// type_name - return the name for the given
// type/player
//-------------------------------------------------
std::string ioport_manager::type_name(ioport_type type, u8 player) const
{
using util::lang_translate;
// if we have a machine, use the live state and quick lookup
input_type_entry const *const entry = m_type_to_entry[type][player];
if (entry)
{
std::string name = entry->name();
if (!name.empty())
return name;
}
// if we find nothing, return a default string (not a null pointer)
return _("input-name", "???");
}
//-------------------------------------------------
// type_group - return the group for the given
// type/player
//-------------------------------------------------
ioport_group ioport_manager::type_group(ioport_type type, int player) const noexcept
{
input_type_entry *entry = m_type_to_entry[type][player];
if (entry != nullptr)
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) const noexcept
{
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 != nullptr)
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) noexcept
{
input_type_entry *const entry = m_type_to_entry[type][player];
if (entry)
{
if (newseq.is_default())
{
entry->set_seq(seqtype, entry->defseq(seqtype));
entry->set_cfg(seqtype, "");
}
else
{
entry->set_seq(seqtype, newseq);
if (!newseq.length())
entry->set_cfg(seqtype, "NONE");
else
entry->set_cfg(seqtype, machine().input().seq_to_tokens(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) const noexcept
{
for (auto &port : m_portlist)
for (ioport_field const &field : port.second->fields())
if (field.type_class() == inputclass)
return true;
return false;
}
//-------------------------------------------------
// count_players - counts the number of active
// players
//-------------------------------------------------
int ioport_manager::count_players() const noexcept
{
int max_player = 0;
for (auto &port : m_portlist)
for (ioport_field const &field : port.second->fields())
if (field.type_class() == INPUT_CLASS_CONTROLLER && max_player <= field.player() + 1)
max_player = field.player() + 1;
return max_player;
}
//-------------------------------------------------
// 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)
if (joystick.player() == player && joystick.number() == number)
return joystick;
// create a new one
return m_joystick_list.emplace_back(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()
{
auto profile = 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)
joystick.frame_update();
// compute default values for all the ports
// two passes to catch conditionals properly
for (auto &port : m_portlist)
port.second->update_defvalue(true);
for (auto &port : m_portlist)
port.second->update_defvalue(false);
// loop over all input ports
for (auto &port : m_portlist)
{
port.second->frame_update();
// handle playback/record
playback_port(*port.second.get());
record_port(*port.second.get());
// call device line write handlers
ioport_value newvalue = port.second->read();
for (dynamic_field &dynfield : port.second->live().writelist)
if (dynfield.field().type() != IPT_OUTPUT)
dynfield.write(newvalue);
}
}
//-------------------------------------------------
// frame_interpolate - interpolate between two
// values based on the time between frames
//-------------------------------------------------
s32 ioport_manager::frame_interpolate(s32 oldval, s32 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 + (s64(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(config_type cfg_type, config_level cfg_level, util::xml::data_node const *parentnode)
{
// make sure device defaults get applied at some point
if ((cfg_type > config_type::CONTROLLER) && !m_applied_device_defaults)
{
apply_device_defaults();
// after applying controller config, push that to the backup, as it's what we'll diff against
for (input_type_entry &entry : m_typelist)
for (input_seq_type seqtype = SEQ_TYPE_STANDARD; seqtype < SEQ_TYPE_TOTAL; ++seqtype)
entry.defseq(seqtype) = entry.seq(seqtype);
}
// in the completion phase, we finish the initialization with the final ports
if (cfg_type == config_type::FINAL)
{
m_safe_to_read = true;
frame_update();
}
// early exit if no data to parse
if (!parentnode)
return;
// load device map table for controller configs only
if (cfg_type == config_type::CONTROLLER)
{
// iterate over device remapping entries
input_manager::devicemap_table devicemap;
for (util::xml::data_node const *mapdevice_node = parentnode->get_child("mapdevice"); mapdevice_node != nullptr; mapdevice_node = mapdevice_node->get_next_sibling("mapdevice"))
{
char const *const devicename = mapdevice_node->get_attribute_string("device", nullptr);
char const *const controllername = mapdevice_node->get_attribute_string("controller", nullptr);
if (devicename && controllername)
devicemap.emplace(devicename, controllername);
}
// we can't rearrange controllers after applying device-supplied defaults
if (!m_applied_device_defaults)
{
// map device to controller if we have a device map
if (!devicemap.empty())
machine().input().map_device_to_controller(devicemap);
// add extra default assignments for input devices
apply_device_defaults();
}
else if (!devicemap.empty())
{
osd_printf_warning("Controller configuration: Only <mapdevice> elements from the first applicable <system> element are applied\n");
}
// iterate over any input code remapping nodes
load_remap_table(*parentnode);
}
// iterate over all the port nodes
for (util::xml::data_node const *portnode = parentnode->get_child("port"); portnode; portnode = portnode->get_next_sibling("port"))
{
// get the basic port info from the attributes
int player;
int type = token_to_input_type(portnode->get_attribute_string("type", ""), player);
// initialize sequences to invalid defaults
std::pair<input_seq, char const *> newseq[SEQ_TYPE_TOTAL];
for (auto &seq : newseq)
{
seq.first.set(INPUT_CODE_INVALID);
seq.second = "";
}
// loop over new sequences
for (util::xml::data_node const *seqnode = portnode->get_child("newseq"); seqnode; seqnode = seqnode->get_next_sibling("newseq"))
{
// with a valid type, parse out the new sequence
input_seq_type seqtype = token_to_seq_type(seqnode->get_attribute_string("type", ""));
if ((seqtype != -1) && seqnode->get_value())
{
if (!strcmp(seqnode->get_value(), "NONE"))
newseq[seqtype].first.reset();
else
machine().input().seq_from_tokens(newseq[seqtype].first, seqnode->get_value());
if (config_type::CONTROLLER != cfg_type)
newseq[seqtype].second = seqnode->get_value();
}
}
// load into the appropriate place for the config type/level
if (config_type::SYSTEM == cfg_type)
load_system_config(*portnode, type, player, newseq);
else if ((config_type::CONTROLLER == cfg_type) && (config_level::DEFAULT != cfg_level))
load_controller_config(*portnode, type, player, newseq);
else
load_default_config(type, player, newseq);
}
if (cfg_type == config_type::CONTROLLER)
{
// after applying the controller config, push that back into the backup, since that is what we will diff against
for (input_type_entry &entry : m_typelist)
for (input_seq_type seqtype = SEQ_TYPE_STANDARD; seqtype < SEQ_TYPE_TOTAL; ++seqtype)
entry.defseq(seqtype) = entry.seq(seqtype);
}
else if (cfg_type == config_type::SYSTEM)
{
// load keyboard enable/disable state
std::vector<bool> kbd_enable_set;
bool keyboard_enabled = false, missing_enabled = false;
natural_keyboard &natkbd = machine().natkeyboard();
for (util::xml::data_node const *kbdnode = parentnode->get_child("keyboard"); kbdnode; kbdnode = kbdnode->get_next_sibling("keyboard"))
{
char const *const tag = kbdnode->get_attribute_string("tag", nullptr);
int const enabled = kbdnode->get_attribute_int("enabled", -1);
if (tag && (0 <= enabled))
{
size_t i;
for (i = 0; natkbd.keyboard_count() > i; ++i)
{
if (!strcmp(natkbd.keyboard_device(i).tag(), tag))
{
if (kbd_enable_set.empty())
kbd_enable_set.resize(natkbd.keyboard_count(), false);
kbd_enable_set[i] = true;
if (enabled)
{
if (!natkbd.keyboard_is_keypad(i))
keyboard_enabled = true;
natkbd.enable_keyboard(i);
}
else
{
natkbd.disable_keyboard(i);
}
break;
}
}
missing_enabled = missing_enabled || (enabled && (natkbd.keyboard_count() <= i));
}
}
// if keyboard enable configuration was loaded, patch it up for principle of least surprise
if (!kbd_enable_set.empty())
{
for (size_t i = 0; natkbd.keyboard_count() > i; ++i)
{
if (!natkbd.keyboard_is_keypad(i))
{
if (!keyboard_enabled && missing_enabled)
{
natkbd.enable_keyboard(i);
keyboard_enabled = true;
}
else if (!kbd_enable_set[i])
{
if (keyboard_enabled)
natkbd.disable_keyboard(i);
else
natkbd.enable_keyboard(i);
keyboard_enabled = true;
}
}
}
}
}
}
//-------------------------------------------------
// load_remap_table - extract and apply the
// global remapping table
//-------------------------------------------------
void ioport_manager::load_remap_table(util::xml::data_node const &parentnode)
{
// count items first so we can allocate
int count = 0;
for (util::xml::data_node const *remapnode = parentnode.get_child("remap"); remapnode != nullptr; remapnode = remapnode->get_next_sibling("remap"))
count++;
// if we have some, deal with them
if (count > 0)
{
// allocate tables
std::vector<input_code> oldtable(count);
std::vector<input_code> newtable(count);
// build up the remap table
count = 0;
for (util::xml::data_node const *remapnode = parentnode.get_child("remap"); remapnode != nullptr; remapnode = remapnode->get_next_sibling("remap"))
{
input_code origcode = machine().input().code_from_token(remapnode->get_attribute_string("origcode", ""));
input_code newcode = machine().input().code_from_token(remapnode->get_attribute_string("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)
entry.replace_code(oldtable[remapnum], newtable[remapnum]);
}
}
//-------------------------------------------------
// load_default_config - apply input settings
// to defaults for all systems
//-------------------------------------------------
bool ioport_manager::load_default_config(
int type,
int player,
const std::pair<input_seq, char const *> (&newseq)[SEQ_TYPE_TOTAL])
{
// find a matching port in the list
for (input_type_entry &entry : m_typelist)
{
if (entry.type() == type && entry.player() == player)
{
for (input_seq_type seqtype = SEQ_TYPE_STANDARD; seqtype < SEQ_TYPE_TOTAL; ++seqtype)
{
if (input_seq_good(machine(), newseq[seqtype].first))
entry.set_seq(seqtype, newseq[seqtype].first);
entry.set_cfg(seqtype, newseq[seqtype].second);
}
return true;
}
}
return false;
}
//-------------------------------------------------
// load_controller_config - apply controler
// profile settings to defaults
//-------------------------------------------------
bool ioport_manager::load_controller_config(
util::xml::data_node const &portnode,
int type,
int player,
const std::pair<input_seq, char const *> (&newseq)[SEQ_TYPE_TOTAL])
{
// without a tag, apply to the defaults for all systems
char const *const tag = portnode.get_attribute_string("tag", nullptr);
if (!tag)
return load_default_config(type, player, newseq);
// ensure the port actually exists
auto const port(m_portlist.find(tag));
if (m_portlist.end() == port)
return false;
ioport_value const mask = portnode.get_attribute_int("mask", 0);
if (!mask)
return false;
// find the matching field
ioport_value const defvalue = portnode.get_attribute_int("defvalue", 0);
bool matched = false;
for (ioport_field &field : port->second->fields())
{
// find the matching mask and default value
if (field.type() == type && field.player() == player &&
field.mask() == mask && (field.defvalue() & mask) == (defvalue & mask))
{
// if a sequence was specified, override the developer-specified default for the field
for (input_seq_type seqtype = SEQ_TYPE_STANDARD; seqtype < SEQ_TYPE_TOTAL; ++seqtype)
{
if (input_seq_good(machine(), newseq[seqtype].first))
{
field.live().seq[seqtype] = newseq[seqtype].first;
field.set_defseq(seqtype, newseq[seqtype].first);
}
}
// fetch configurable attributes
if (!field.live().analog)
{
// for non-analog fields
// can't practically set value here
// fetch yes/no for toggle setting
char const *const togstring = portnode.get_attribute_string("toggle", nullptr);
if (togstring && !strcmp(togstring, "yes"))
{
field.live().toggle = true;
field.m_flags |= ioport_field::FIELD_FLAG_TOGGLE;
}
else if (togstring && !strcmp(togstring, "no"))
{
field.live().toggle = false;
field.m_flags &= ~ioport_field::FIELD_FLAG_TOGGLE;
}
}
else
{
// for analog fields
#if 0 // changing this stuff causes issues because of the way it's tied up with the analog_field object
// get base attributes
field.live().analog->m_delta = field.m_delta = portnode.get_attribute_int("keydelta", field.delta());
field.live().analog->m_centerdelta = field.m_centerdelta = portnode.get_attribute_int("centerdelta", field.centerdelta());
field.live().analog->m_sensitivity = field.m_sensitivity = portnode.get_attribute_int("sensitivity", field.sensitivity());
// fetch yes/no for reverse setting
char const *const revstring = portnode.get_attribute_string("reverse", nullptr);
if (revstring && !strcmp(revstring, "yes"))
{
field.live().analog->m_reverse = true;
field.m_flags |= ioport_field::ANALOG_FLAG_REVERSE;
}
else if (revstring && !strcmp(revstring, "no"))
{
field.live().analog->m_reverse = false;
field.m_flags &= ~ioport_field::ANALOG_FLAG_REVERSE;
}
#endif
}
// only break out of the loop for unconditional inputs
if (field.condition().condition() == ioport_condition::ALWAYS)
return true;
else
matched = true;
}
}
// return whether any field matched
return matched;
}
//-------------------------------------------------
// load_system_config - apply saved input
// configuration for the current system
//-------------------------------------------------
void ioport_manager::load_system_config(
util::xml::data_node const &portnode,
int type,
int player,
const std::pair<input_seq, char const *> (&newseq)[SEQ_TYPE_TOTAL])
{
// system-specific configuration should always apply by port/field
char const *const tag = portnode.get_attribute_string("tag", nullptr);
ioport_value const mask = portnode.get_attribute_int("mask", 0);
ioport_value const defvalue = portnode.get_attribute_int("defvalue", 0);
if (!tag || !mask)
return;
// find the port we want
auto const port(m_portlist.find(tag));
if (m_portlist.end() != port)
{
for (ioport_field &field : port->second->fields())
{
// find the matching mask and default value
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 (input_seq_good(machine(), newseq[seqtype].first))
field.live().seq[seqtype] = newseq[seqtype].first;
field.live().cfg[seqtype] = newseq[seqtype].second;
}
// fetch configurable attributes
if (!field.live().analog)
{
// for non-analog fields
// fetch the value
field.live().value = portnode.get_attribute_int("value", field.defvalue());
// fetch yes/no for toggle setting
char const *const togstring = portnode.get_attribute_string("toggle", nullptr);
if (togstring && !strcmp(togstring, "yes"))
field.live().toggle = true;
else if (togstring && !strcmp(togstring, "no"))
field.live().toggle = false;
}
else
{
// for analog fields
// get base attributes
field.live().analog->m_delta = portnode.get_attribute_int("keydelta", field.delta());
field.live().analog->m_centerdelta = portnode.get_attribute_int("centerdelta", field.centerdelta());
field.live().analog->m_sensitivity = portnode.get_attribute_int("sensitivity", field.sensitivity());
// fetch yes/no for reverse setting
char const *const revstring = portnode.get_attribute_string("reverse", nullptr);
if (revstring && !strcmp(revstring, "yes"))
field.live().analog->m_reverse = true;
else if (revstring && !strcmp(revstring, "no"))
field.live().analog->m_reverse = false;
}
// only break out of the loop for unconditional inputs
if (field.condition().condition() == ioport_condition::ALWAYS)
break;
}
}
}
else
{
// see if this belongs to a slot card that isn't inserted
std::string_view parent_tag(tag);
auto pos(parent_tag.rfind(':'));
if (pos && (std::string_view::npos != pos))
{
parent_tag = parent_tag.substr(0, pos);
if (!machine().root_device().subdevice(parent_tag))
{
for (pos = parent_tag.rfind(':'); pos && (std::string_view::npos != pos); pos = parent_tag.rfind(':'))
{
std::string_view const child_tag(parent_tag.substr(pos + 1));
parent_tag = parent_tag.substr(0, pos);
device_t const *const parent_device(machine().root_device().subdevice(parent_tag));
if (parent_device)
{
device_slot_interface const *slot;
if (parent_device->interface(slot) && (slot->option_list().find(std::string(child_tag)) != slot->option_list().end()))
{
if (!m_deselected_card_config)
m_deselected_card_config = util::xml::file::create();
portnode.copy_into(*m_deselected_card_config);
}
break;
}
}
}
}
}
}
//-------------------------------------------------
// apply_device_defaults - add default assignments
// supplied by input devices
//-------------------------------------------------
void ioport_manager::apply_device_defaults()
{
// make sure this only happens once
assert(!m_applied_device_defaults);
m_applied_device_defaults = true;
// TODO: come up with a way to deal with non-multi device classes here?
for (input_device_class classno = DEVICE_CLASS_FIRST_VALID; DEVICE_CLASS_LAST_VALID >= classno; ++classno)
{
input_class &devclass = machine().input().device_class(classno);
for (int devnum = 0; devclass.maxindex() >= devnum; ++devnum)
{
// make sure device exists
input_device const *const device = devclass.device(devnum);
if (!device)
continue;
// iterate over default assignments
for (auto [porttype, seqtype, seq] : device->default_assignments())
{
assert(!seq.empty());
assert(seq.is_valid());
assert(!seq.is_default());
// only apply UI assignments for first device in a class
if ((IPT_UI_FIRST < porttype) && (IPT_UI_LAST > porttype) && (device->devindex() != 0))
continue;
// limit to maximum player count
if (device->devindex() >= MAX_PLAYERS)
continue;
// find a matching port in the list
auto const found = std::find_if(
m_typelist.begin(),
m_typelist.end(),
[type = porttype, device] (input_type_entry const &entry)
{
return (entry.type() == type) && (entry.player() == device->devindex());
});
if (m_typelist.end() == found)
continue;
// start with the current setting
input_seq remapped(found->seq(seqtype));
if (!remapped.empty())
remapped += input_seq::or_code;
// append adjusting the device index
for (int i = 0, len = seq.length(); i < len; ++i)
{
input_code code = seq[i];
if (!code.internal())
{
assert(code.device_class() == classno);
assert(code.device_index() == 0);
assert(code.item_id() >= ITEM_ID_FIRST_VALID);
assert(code.item_id() <= ITEM_ID_ABSOLUTE_MAXIMUM);
code.set_device_index(device->devindex());
}
remapped += code;
}
// apply to the entry
found->set_seq(seqtype, remapped);
}
}
}
}
//**************************************************************************
// SETTINGS SAVE
//**************************************************************************
//-------------------------------------------------
// save_config - config callback for saving input
// port configuration
//-------------------------------------------------
void ioport_manager::save_config(config_type cfg_type, util::xml::data_node *parentnode)
{
// if no parentnode, ignore
if (!parentnode)
return;
// default ports save differently
if (cfg_type == config_type::DEFAULT)
save_default_inputs(*parentnode);
else if (cfg_type == config_type::SYSTEM)
save_game_inputs(*parentnode);
}
//-------------------------------------------------
// 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(util::xml::data_node &parentnode)
{
// iterate over ports
for (input_type_entry &entry : m_typelist)
{
// 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.cfg(seqtype).empty())
break;
// if so, we need to add a node
if (seqtype < SEQ_TYPE_TOTAL)
{
// add a new port node
util::xml::data_node *const portnode = parentnode.add_child("port", nullptr);
if (portnode)
{
// add the port information and attributes
portnode->set_attribute("type", input_type_to_token(entry.type(), entry.player()).c_str());
// add only the sequences that have changed from the defaults
for (input_seq_type type = SEQ_TYPE_STANDARD; type < SEQ_TYPE_TOTAL; ++type)
{
if (!entry.cfg(type).empty())
{
util::xml::data_node *const seqnode = portnode->add_child("newseq", entry.cfg(type).c_str());
if (seqnode)
seqnode->set_attribute("type", seqtypestrings[type]);
}
}
}
}
}
}
}
//-------------------------------------------------
// save_game_inputs - add nodes for any game
// mappings that have changed
//-------------------------------------------------
void ioport_manager::save_game_inputs(util::xml::data_node &parentnode)
{
// save keyboard enable/disable state
natural_keyboard &natkbd = machine().natkeyboard();
for (size_t i = 0; natkbd.keyboard_count() > i; ++i)
{
util::xml::data_node *const kbdnode = parentnode.add_child("keyboard", nullptr);
kbdnode->set_attribute("tag", natkbd.keyboard_device(i).tag());
kbdnode->set_attribute_int("enabled", natkbd.keyboard_enabled(i));
}
// iterate over ports
for (auto &port : m_portlist)
for (ioport_field const &field : port.second->fields())
if (save_this_input_field_type(field.type()) && field.enabled())
{
// determine if we changed
bool changed = false;
for (input_seq_type seqtype = SEQ_TYPE_STANDARD; (seqtype < SEQ_TYPE_TOTAL) && !changed; ++seqtype)
changed = !field.live().cfg[seqtype].empty();
if (!field.is_analog())
{
// non-analog changes
changed = changed || ((field.live().value & field.mask()) != (field.defvalue() & field.mask()));
changed = changed || (field.live().toggle != field.toggle());
}
else
{
// analog changes
changed = changed || (field.live().analog->m_delta != field.delta());
changed = changed || (field.live().analog->m_centerdelta != field.centerdelta());
changed = changed || (field.live().analog->m_sensitivity != field.sensitivity());
changed = changed || (field.live().analog->m_reverse != field.analog_reverse());
}
// if we did change, add a new node
if (changed)
{
// add a new port node
util::xml::data_node *const portnode = parentnode.add_child("port", nullptr);
if (portnode)
{
// add the identifying information and attributes
portnode->set_attribute("tag", port.second->tag());
portnode->set_attribute("type", input_type_to_token(field.type(), field.player()).c_str());
portnode->set_attribute_int("mask", field.mask());
portnode->set_attribute_int("defvalue", field.defvalue() & field.mask());
// add sequences if changed
for (input_seq_type seqtype = SEQ_TYPE_STANDARD; seqtype < SEQ_TYPE_TOTAL; ++seqtype)
{
if (!field.live().cfg[seqtype].empty())
{
util::xml::data_node *const seqnode = portnode->add_child("newseq", field.live().cfg[seqtype].c_str());
if (seqnode)
seqnode->set_attribute("type", seqtypestrings[seqtype]);
}
}
if (!field.is_analog())
{
// write out non-analog changes
if ((field.live().value & field.mask()) != (field.defvalue() & field.mask()))
portnode->set_attribute_int("value", field.live().value & field.mask());
if (field.live().toggle != field.toggle())
portnode->set_attribute("toggle", field.live().toggle ? "yes" : "no");
}
else
{
// write out analog changes
if (field.live().analog->m_delta != field.delta())
portnode->set_attribute_int("keydelta", field.live().analog->m_delta);
if (field.live().analog->m_centerdelta != field.centerdelta())
portnode->set_attribute_int("centerdelta", field.live().analog->m_centerdelta);
if (field.live().analog->m_sensitivity != field.sensitivity())
portnode->set_attribute_int("sensitivity", field.live().analog->m_sensitivity);
if (field.live().analog->m_reverse != field.analog_reverse())
portnode->set_attribute("reverse", field.live().analog->m_reverse ? "yes" : "no");
}
}
}
}
// preserve configuration for deselected slot cards
if (m_deselected_card_config)
{
for (util::xml::data_node const *node = m_deselected_card_config->get_first_child(); node; node = node->get_next_sibling())
node->copy_into(parentnode);
}
}
//**************************************************************************
// INPUT PLAYBACK
//**************************************************************************
//-------------------------------------------------
// playback_read - read a value from the playback
// file
//-------------------------------------------------
template<typename Type>
Type ioport_manager::playback_read(Type &result)
{
// protect against nullptr handles if previous reads fail
if (!m_playback_stream)
return result = Type(0);
// read the value; if we fail, end playback
auto const [err, actual] = read(*m_playback_stream, &result, sizeof(result));
if (err)
{
playback_end("Read error");
return result = Type(0);
}
else if (sizeof(result) != actual)
{
playback_end("End of file");
return result = Type(0);
}
// normalize byte order
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)
{
u8 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
m_playback_file = std::make_unique<emu_file>(machine().options().input_directory(), OPEN_FLAG_READ);
std::error_condition const filerr = m_playback_file->open(filename);
// return an explicit error if file isn't found in given path
if (filerr == std::errc::no_such_file_or_directory)
fatalerror("Input file %s not found\n",filename);
// TODO: bail out any other error laconically for now
if (filerr)
fatalerror("Failed to open file %s for playback (%s:%d %s)\n", filename, filerr.category().name(), filerr.value(), filerr.message());
// read the header and verify that it is a modern version; if not, print an error
inp_header header;
if (!header.read(*m_playback_file))
fatalerror("Input file is corrupt or invalid (missing header)\n");
if (!header.check_magic())
fatalerror("Input file invalid or in an older, unsupported format\n");
if (header.get_majversion() != inp_header::MAJVERSION)
fatalerror("Input file format version mismatch\n");
// output info to console
osd_printf_info("Input file: %s\n", filename);
osd_printf_info("INP version %u.%u\n", header.get_majversion(), header.get_minversion());
time_t basetime = header.get_basetime();
osd_printf_info("Created %s\n", ctime(&basetime));
osd_printf_info("Recorded using %s\n", header.get_appdesc());
// verify the header against the current game
std::string const sysname = header.get_sysname();
if (sysname != machine().system().name)
osd_printf_info("Input file is for machine '%s', not for current machine '%s'\n", sysname, machine().system().name);
// enable compression
m_playback_stream = util::zlib_read(*m_playback_file, 16386);
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_stream)
{
// close the file
m_playback_stream.reset();
m_playback_file.reset();
// pop a message
if (message != nullptr)
machine().popmessage("Playback Ended\nReason: %s", message);
// display speed stats
if (m_playback_accumulated_speed > 0)
m_playback_accumulated_speed /= m_playback_accumulated_frames;
osd_printf_info("Total playback frames: %d\n", u32(m_playback_accumulated_frames));
osd_printf_info("Average recorded speed: %d%%\n", u32((m_playback_accumulated_speed * 200 + 1) >> 21));
// close the program at the end of inp file playback
if (machine().options().exit_after_playback())
{
osd_printf_info("Exiting MAME now...\n");
machine().schedule_exit();
}
}
}
//-------------------------------------------------
// playback_frame - start of frame callback for
// playback
//-------------------------------------------------
void ioport_manager::playback_frame(const attotime &curtime)
{
// if playing back, fetch the information and verify
if (m_playback_stream)
{
// first the absolute time
seconds_t seconds_temp;
attoseconds_t attoseconds_temp;
playback_read(seconds_temp);
playback_read(attoseconds_temp);
attotime readtime(seconds_temp, attoseconds_temp);
if (readtime != curtime)
playback_end("Out of sync");
// then the speed
u32 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_stream)
{
// 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)
{
// 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 nullptr handles if previous reads fail
if (!m_record_stream)
return;
// normalize byte order
if (sizeof(value) == 8)
value = little_endianize_int64(value);
else if (sizeof(value) == 4)
value = little_endianize_int32(value);
else if (sizeof(value) == 2)
value = little_endianize_int16(value);
// write the value; if we fail, end recording
if (write(*m_record_stream, &value, sizeof(value)).first)
record_end("Write error");
}
template<>
void ioport_manager::record_write<bool>(bool value)
{
u8 byte = u8(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
m_record_file = std::make_unique<emu_file>(machine().options().input_directory(), OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS);
std::error_condition const filerr = m_record_file->open(filename);
if (filerr)
throw emu_fatalerror("ioport_manager::record_init: Failed to open file for recording (%s:%d %s)", filerr.category().name(), filerr.value(), filerr.message());
// get the base time
system_time systime;
machine().base_datetime(systime);
// fill in the header
inp_header header;
header.set_magic();
header.set_basetime(systime.time);
header.set_version();
header.set_sysname(machine().system().name);
header.set_appdesc(util::string_format("%s %s", emulator_info::get_appname(), emulator_info::get_build_version()));
// write it
header.write(*m_record_file);
// enable compression
m_record_stream = util::zlib_write(*m_record_file, 6, 16384);
}
//-------------------------------------------------
// record_end - end INP recording
//-------------------------------------------------
void ioport_manager::record_end(const char *message)
{
// only applies if we have a live file
if (m_record_stream)
{
// close the file
m_record_stream.reset(); // TODO: check for errors flushing the last compressed block before doing this
m_record_file.reset();
// pop a message
if (message != nullptr)
machine().popmessage("Recording Ended\nReason: %s", message);
}
}
//-------------------------------------------------
// record_frame - start of frame callback for
// recording
//-------------------------------------------------
void ioport_manager::record_frame(const attotime &curtime)
{
// if recording, record information about the current frame
if (m_record_stream)
{
// first the absolute time
record_write(curtime.seconds());
record_write(curtime.attoseconds());
// then the current speed
record_write(u32(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_stream)
{
// 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)
{
// 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, std::string &errorbuf)
: m_owner(owner),
m_portlist(portlist),
m_errorbuf(errorbuf),
m_curport(nullptr),
m_curfield(nullptr),
m_cursetting(nullptr)
{
}
//-------------------------------------------------
// 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 == nullptr)
return nullptr;
// if the index is greater than the count, assume it to be a pointer
if (uintptr_t(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
for (int index = 0; index < std::size(input_port_default_strings); index++)
if (input_port_default_strings[index].id == uintptr_t(string))
return input_port_default_strings[index].string;
return "(Unknown Default)";
#else
return input_port_default_strings[uintptr_t(string)-1].string;
#endif
}
//-------------------------------------------------
// port_alloc - allocate a new port
//-------------------------------------------------
ioport_configurer& ioport_configurer::port_alloc(const char *tag)
{
// create the full tag
std::string fulltag = m_owner.subtag(tag);
// add it to the list, and reset current field/setting
if (m_portlist.count(fulltag) != 0) throw tag_add_exception(fulltag.c_str());
m_portlist.emplace(std::make_pair(fulltag, std::make_unique<ioport_port>(m_owner, fulltag.c_str())));
m_curport = m_portlist.find(fulltag)->second.get();
m_curfield = nullptr;
m_cursetting = nullptr;
return *this;
}
//-------------------------------------------------
// port_modify - find an existing port and
// modify it
//-------------------------------------------------
ioport_configurer& ioport_configurer::port_modify(const char *tag)
{
// create the full tag
std::string fulltag = m_owner.subtag(tag);
// find the existing port
m_curport = m_portlist.find(fulltag)->second.get();
if (m_curport == nullptr)
throw emu_fatalerror("Requested to modify nonexistent port '%s'", fulltag);
// bump the modification count, and reset current field/setting
m_curport->m_modcount++;
m_curfield = nullptr;
m_cursetting = nullptr;
return *this;
}
//-------------------------------------------------
// field_alloc - allocate a new field
//-------------------------------------------------
ioport_configurer& 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 == nullptr)
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(*new ioport_field(*m_curport, type, defval, mask, string_from_token(name)));
// reset the current setting
m_cursetting = nullptr;
return *this;
}
//-------------------------------------------------
// field_add_char - add a character to a field
//-------------------------------------------------
ioport_configurer& ioport_configurer::field_add_char(std::initializer_list<char32_t> charlist)
{
for (int index = 0; index < std::size(m_curfield->m_chars); index++)
if (m_curfield->m_chars[index][0] == 0)
{
const size_t char_count = std::size(m_curfield->m_chars[index]);
assert(charlist.size() > 0 && charlist.size() <= char_count);
for (size_t i = 0; i < char_count; i++)
m_curfield->m_chars[index][i] = i < charlist.size() ? *(charlist.begin() + i) : 0;
return *this;
}
std::ostringstream s;
bool is_first = true;
for (char32_t ch : charlist)
{
util::stream_format(s, "%s%d", is_first ? "" : ",", (int)ch);
is_first = false;
}
throw emu_fatalerror("PORT_CHAR(%s) could not be added - maximum amount exceeded\n", s.str());
}
//-------------------------------------------------
// field_add_code - add a character to a field
//-------------------------------------------------
ioport_configurer& ioport_configurer::field_add_code(input_seq_type which, input_code code)
{
m_curfield->m_seq[which] |= code;
return *this;
}
//-------------------------------------------------
// setting_alloc - allocate a new setting
//-------------------------------------------------
ioport_configurer& ioport_configurer::setting_alloc(ioport_value value, const char *name)
{
// make sure we have a field
if (!m_curfield)
throw emu_fatalerror("setting_alloc called with no active field (value=%X name=%s)\n", value, name);
// append a new setting
m_cursetting = &m_curfield->m_settinglist.emplace_back(*m_curfield, value & m_curfield->mask(), string_from_token(name));
return *this;
}
//-------------------------------------------------
// set_condition - set the condition for either
// the current setting or field
//-------------------------------------------------
ioport_configurer& ioport_configurer::set_condition(ioport_condition::condition_t condition, const char *tag, ioport_value mask, ioport_value value)
{
ioport_condition &target = m_cursetting ? m_cursetting->condition() : m_curfield->condition();
target.set(condition, tag, mask, value);
return *this;
}
//-------------------------------------------------
// onoff_alloc - allocate an on/off DIP switch
//-------------------------------------------------
ioport_configurer& 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);
// expand the diplocation
if (diplocation != nullptr)
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 = nullptr;
return *this;
}
/***************************************************************************
MISCELLANEOUS
***************************************************************************/
//-------------------------------------------------
// dynamic_field - constructor
//-------------------------------------------------
dynamic_field::dynamic_field(ioport_field &field)
: 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())
{
// call the callback to read a new value
ioport_value newval = m_field.m_read();
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())
{
// 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_field(field)
, m_shift(compute_shift(field.mask()))
, m_adjdefvalue((field.defvalue() & field.mask()) >> m_shift)
, m_adjmin((field.minval() & field.mask()) >> m_shift)
, m_adjmax((field.maxval() & field.mask()) >> m_shift)
, m_adjoverride((field.defvalue() & field.mask()) >> m_shift)
, 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(osd::input_device::ABSOLUTE_MIN)
, m_maximum(osd::input_device::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)
, m_use_adjoverride(false)
{
// 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 = osd::input_device::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(), osd::input_device::ABSOLUTE_MAX - osd::input_device::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\n");
}
if (m_absolute)
{
// further processing for absolute controls
// 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, potentially passing through zero
m_scalepos = compute_scale(
(m_adjmax - m_adjdefvalue) & (field.mask() >> m_shift),
osd::input_device::ABSOLUTE_MAX);
m_scaleneg = compute_scale(
(m_adjdefvalue - m_adjmin) & (field.mask() >> m_shift),
-osd::input_device::ABSOLUTE_MIN);
// reverse point is at center
m_reverse_val = 0;
}
else
{
// single axis that increases from default
m_scalepos = compute_scale(m_adjmax - m_adjmin, osd::input_device::ABSOLUTE_MAX - osd::input_device::ABSOLUTE_MIN);
// 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;
}
}
else
{
// relative and positional controls all map directly with a 512x scale factor
// The relative code is set up to allow specifying 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) * osd::input_device::RELATIVE_PER_PIXEL;
m_maximum = (m_adjmax - m_adjdefvalue) * osd::input_device::RELATIVE_PER_PIXEL;
// make the scaling the same for easier coding when we need to scale
m_scaleneg = m_scalepos = compute_scale(1, osd::input_device::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)
{
// FIXME: positional needs -1, using osd::input_device::RELATIVE_PER_PIXEL skips a position (and reads outside the table array)
if (field.type() == IPT_POSITIONAL || field.type() == IPT_POSITIONAL_V)
m_reverse_val--;
else
m_reverse_val -= osd::input_device::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 s32 analog_field::apply_min_max(s32 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
s32 adjmin = apply_inverse_sensitivity(m_minimum);
s32 adjmax = apply_inverse_sensitivity(m_maximum);
// clamp to the bounds absolutely
if (value > adjmax)
value = adjmax;
else if (value < adjmin)
value = adjmin;
return value;
}
//-------------------------------------------------
// apply_sensitivity - apply a sensitivity
// adjustment for a current value
//-------------------------------------------------
inline s32 analog_field::apply_sensitivity(s32 value) const
{
return lround((s64(value) * m_sensitivity) / 100.0);
}
//-------------------------------------------------
// apply_inverse_sensitivity - reverse-apply the
// sensitivity adjustment for a current value
//-------------------------------------------------
inline s32 analog_field::apply_inverse_sensitivity(s32 value) const
{
return s32((s64(value) * 100) / m_sensitivity);
}
//-------------------------------------------------
// apply_settings - return the value of an
// analog input
//-------------------------------------------------
s32 analog_field::apply_settings(s32 value) const
{
// apply the min/max and then the sensitivity
if (!m_wraps)
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 -= osd::input_device::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;
// for relative devices, wrap around when we go past the edge
// (this is done last to prevent rounding errors)
if (m_wraps)
{
s32 range = m_adjmax - m_adjmin;
// rolls to other end when 1 position past end.
value = (value - m_adjmin) % range;
if (value < 0)
value += range;
value += m_adjmin;
}
return value;
}
//-------------------------------------------------
// set_value - override the value that will be
// read from the field
//-------------------------------------------------
void analog_field::set_value(s32 value)
{
m_use_adjoverride = true;
m_adjoverride = std::clamp(value, m_adjmin, m_adjmax);
}
//-------------------------------------------------
// clear_value - clear programmatic override
//-------------------------------------------------
void analog_field::clear_value()
{
m_use_adjoverride = false;
m_adjoverride = m_adjdefvalue;
}
//-------------------------------------------------
// 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
if (!m_wraps)
m_accum = apply_min_max(m_accum);
// remember the previous value in case we need to interpolate
m_previous = m_accum;
// get the new raw analog value and its type
input_item_class itemclass;
s32 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_absolute && !m_positionalscale)
{
// if port is relative, we use the value to simulate the speed of relative movement
// sensitivity adjustment is allowed for this mode
if (rawvalue)
{
if (m_field.analog_reset())
m_accum = rawvalue / 8;
else
m_accum += rawvalue / 8;
// do not bother with other control types if the analog data is changing
m_lastdigital = false;
return;
}
}
else 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
{
assert(m_positionalscale); // only way to get here due to previous if
// 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 - osd::input_device::ABSOLUTE_MIN, m_positionalscale) * osd::input_device::RELATIVE_PER_PIXEL + m_minimum;
// clamp the high value so it does not roll over
rawvalue = std::min(rawvalue, m_maximum);
m_accum = apply_inverse_sensitivity(rawvalue);
}
// do not bother with other control types if the analog data is changing
m_lastdigital = false;
return;
}
}
// 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
s32 delta = 0;
if (itemclass == ITEM_CLASS_RELATIVE && rawvalue)
{
delta = rawvalue;
m_lastdigital = false;
}
s64 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)
{
s32 center = apply_inverse_sensitivity(m_center);
if (m_lastdigital && !keypressed)
{
if (m_accum >= center)
{
// autocenter from positive values
m_accum -= apply_scale(m_centerdelta, m_keyscalepos);
if (m_accum < center)
{
m_accum = center;
m_lastdigital = false;
}
}
else
{
// autocenter from negative values
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;
// if set programmatically, only use the override value
if (m_use_adjoverride)
{
result = m_adjoverride;
return;
}
// start with the raw value
s32 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() != nullptr)
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()
{
s32 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 (const input_type_entry &entry : m_typelist)
if (entry.token() != nullptr && !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
//-------------------------------------------------
std::string ioport_manager::input_type_to_token(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 != nullptr)
return std::string(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 < std::size(seqtypestrings); seqindex++)
if (!core_stricmp(string, seqtypestrings[seqindex]))
return input_seq_type(seqindex);
return SEQ_TYPE_INVALID;
}