summaryrefslogtreecommitdiffstats
path: root/docs/release/src/frontend/mame
diff options
context:
space:
mode:
Diffstat (limited to 'docs/release/src/frontend/mame')
-rw-r--r--docs/release/src/frontend/mame/audit.cpp643
-rw-r--r--docs/release/src/frontend/mame/audit.h182
-rw-r--r--docs/release/src/frontend/mame/info.cpp1882
-rw-r--r--docs/release/src/frontend/mame/mameopts.cpp210
-rw-r--r--docs/release/src/frontend/mame/mameopts.h64
5 files changed, 2981 insertions, 0 deletions
diff --git a/docs/release/src/frontend/mame/audit.cpp b/docs/release/src/frontend/mame/audit.cpp
new file mode 100644
index 00000000000..cbc6a67c30b
--- /dev/null
+++ b/docs/release/src/frontend/mame/audit.cpp
@@ -0,0 +1,643 @@
+// license:BSD-3-Clause
+// copyright-holders:Aaron Giles
+/***************************************************************************
+
+ audit.cpp
+
+ ROM set auditing functions.
+
+***************************************************************************/
+
+#include "emu.h"
+#include "emuopts.h"
+#include "audit.h"
+#include "chd.h"
+#include "drivenum.h"
+#include "romload.h"
+#include "sound/samples.h"
+#include "softlist_dev.h"
+
+
+//**************************************************************************
+// CORE FUNCTIONS
+//**************************************************************************
+
+//-------------------------------------------------
+// media_auditor - constructor
+//-------------------------------------------------
+
+media_auditor::media_auditor(const driver_enumerator &enumerator)
+ : m_enumerator(enumerator)
+ , m_validation(AUDIT_VALIDATE_FULL)
+ , m_searchpath(nullptr)
+{
+}
+
+
+//-------------------------------------------------
+// audit_media - audit the media described by the
+// currently-enumerated driver
+//-------------------------------------------------
+
+media_auditor::summary media_auditor::audit_media(const char *validation)
+{
+ // start fresh
+ m_record_list.clear();
+
+ // store validation for later
+ m_validation = validation;
+
+// temporary hack until romload is update: get the driver path and support it for
+// all searches
+const char *driverpath = m_enumerator.config()->root_device().searchpath();
+
+ std::size_t found = 0;
+ std::size_t required = 0;
+ std::size_t shared_found = 0;
+ std::size_t shared_required = 0;
+
+ // iterate over devices and regions
+ for (device_t &device : device_iterator(m_enumerator.config()->root_device()))
+ {
+ // determine the search path for this source and iterate through the regions
+ m_searchpath = device.searchpath();
+
+ // now iterate over regions and ROMs within
+ for (const rom_entry *region = rom_first_region(device); region; region = rom_next_region(region))
+ {
+// temporary hack: add the driver path & region name
+std::string combinedpath = util::string_format("%s;%s", device.searchpath(), driverpath);
+if (device.shortname())
+ combinedpath.append(";").append(device.shortname());
+m_searchpath = combinedpath.c_str();
+
+ for (const rom_entry *rom = rom_first_file(region); rom; rom = rom_next_file(rom))
+ {
+ char const *const name(ROM_GETNAME(rom));
+ util::hash_collection const hashes(ROM_GETHASHDATA(rom));
+ device_t *const shared_device(find_shared_device(device, name, hashes, ROM_GETLENGTH(rom)));
+
+ // count the number of files with hashes
+ if (!hashes.flag(util::hash_collection::FLAG_NO_DUMP) && !ROM_ISOPTIONAL(rom))
+ {
+ required++;
+ if (shared_device)
+ shared_required++;
+ }
+
+ audit_record *record = nullptr;
+ if (ROMREGION_ISROMDATA(region))
+ record = &audit_one_rom(rom);
+ else if (ROMREGION_ISDISKDATA(region))
+ record = &audit_one_disk(rom, nullptr);
+
+ if (record)
+ {
+ // count the number of files that are found.
+ if ((record->status() == audit_status::GOOD) || ((record->status() == audit_status::FOUND_INVALID) && !find_shared_device(device, name, record->actual_hashes(), record->actual_length())))
+ {
+ found++;
+ if (shared_device)
+ shared_found++;
+ }
+
+ record->set_shared_device(shared_device);
+ }
+ }
+ }
+ }
+
+ // if we only find files that are in the parent & either the set has no unique files or the parent is not found, then assume we don't have the set at all
+ if ((found == shared_found) && (required > 0) && ((required != shared_required) || (shared_found == 0)))
+ {
+ m_record_list.clear();
+ return NOTFOUND;
+ }
+
+ // return a summary
+ return summarize(m_enumerator.driver().name);
+}
+
+
+//-------------------------------------------------
+// audit_device - audit the device
+//-------------------------------------------------
+
+media_auditor::summary media_auditor::audit_device(device_t &device, const char *validation)
+{
+ // start fresh
+ m_record_list.clear();
+
+ // store validation for later
+ m_validation = validation;
+ m_searchpath = device.shortname();
+
+ std::size_t found = 0;
+ std::size_t required = 0;
+
+ audit_regions(rom_first_region(device), nullptr, found, required);
+
+ if ((found == 0) && (required > 0))
+ {
+ m_record_list.clear();
+ return NOTFOUND;
+ }
+
+ // return a summary
+ return summarize(device.shortname());
+}
+
+
+//-------------------------------------------------
+// audit_software
+//-------------------------------------------------
+media_auditor::summary media_auditor::audit_software(const std::string &list_name, const software_info *swinfo, const char *validation)
+{
+ // start fresh
+ m_record_list.clear();
+
+ // store validation for later
+ m_validation = validation;
+
+ std::string combinedpath(util::string_format("%s;%s%s%s", swinfo->shortname(), list_name, PATH_SEPARATOR, swinfo->shortname()));
+ std::string locationtag(util::string_format("%s%%%s%%", list_name, swinfo->shortname()));
+ if (!swinfo->parentname().empty())
+ {
+ locationtag.append(swinfo->parentname());
+ combinedpath.append(util::string_format(";%s;%s%s%s", swinfo->parentname(), list_name, PATH_SEPARATOR, swinfo->parentname()));
+ }
+ m_searchpath = combinedpath.c_str();
+
+ std::size_t found = 0;
+ std::size_t required = 0;
+
+ // now iterate over software parts
+ for (const software_part &part : swinfo->parts())
+ audit_regions(part.romdata().data(), locationtag.c_str(), found, required);
+
+ if ((found == 0) && (required > 0))
+ {
+ m_record_list.clear();
+ return NOTFOUND;
+ }
+
+ // return a summary
+ return summarize(list_name.c_str());
+}
+
+
+//-------------------------------------------------
+// audit_samples - validate the samples for the
+// currently-enumerated driver
+//-------------------------------------------------
+
+media_auditor::summary media_auditor::audit_samples()
+{
+ // start fresh
+ m_record_list.clear();
+
+ std::size_t required = 0;
+ std::size_t found = 0;
+
+ // iterate over sample entries
+ for (samples_device &device : samples_device_iterator(m_enumerator.config()->root_device()))
+ {
+ // by default we just search using the driver name
+ std::string searchpath(m_enumerator.driver().name);
+
+ // add the alternate path if present
+ samples_iterator iter(device);
+ if (iter.altbasename() != nullptr)
+ searchpath.append(";").append(iter.altbasename());
+
+ // iterate over samples in this entry
+ for (const char *samplename = iter.first(); samplename; samplename = iter.next())
+ {
+ required++;
+
+ // create a new record
+ audit_record &record = *m_record_list.emplace(m_record_list.end(), samplename, media_type::SAMPLE);
+
+ // look for the files
+ emu_file file(m_enumerator.options().sample_path(), OPEN_FLAG_READ | OPEN_FLAG_NO_PRELOAD);
+ path_iterator path(searchpath.c_str());
+ std::string curpath;
+ while (path.next(curpath, samplename))
+ {
+ // attempt to access the file (.flac) or (.wav)
+ osd_file::error filerr = file.open(curpath.c_str(), ".flac");
+ if (filerr != osd_file::error::NONE)
+ filerr = file.open(curpath.c_str(), ".wav");
+
+ if (filerr == osd_file::error::NONE)
+ {
+ record.set_status(audit_status::GOOD, audit_substatus::GOOD);
+ found++;
+ }
+ else
+ {
+ record.set_status(audit_status::NOT_FOUND, audit_substatus::NOT_FOUND);
+ }
+ }
+ }
+ }
+
+ if ((found == 0) && (required > 0))
+ {
+ m_record_list.clear();
+ return NOTFOUND;
+ }
+
+ // return a summary
+ return summarize(m_enumerator.driver().name);
+}
+
+
+//-------------------------------------------------
+// summary - generate a summary, with an optional
+// string format
+//-------------------------------------------------
+
+media_auditor::summary media_auditor::summarize(const char *name, std::ostream *output) const
+{
+ if (m_record_list.empty())
+ return NONE_NEEDED;
+
+ // loop over records
+ summary overall_status = CORRECT;
+ for (audit_record const &record : m_record_list)
+ {
+ // skip anything that's fine
+ if (record.substatus() == audit_substatus::GOOD)
+ continue;
+
+ // output the game name, file name, and length (if applicable)
+ if (output)
+ {
+ if (name)
+ util::stream_format(*output, "%-12s: %s", name, record.name());
+ else
+ util::stream_format(*output, "%s", record.name());
+ if (record.expected_length() > 0)
+ util::stream_format(*output, " (%d bytes)", record.expected_length());
+ *output << " - ";
+ }
+
+ // use the substatus for finer details
+ summary best_new_status = INCORRECT;
+ switch (record.substatus())
+ {
+ case audit_substatus::GOOD_NEEDS_REDUMP:
+ if (output) *output << "NEEDS REDUMP\n";
+ best_new_status = BEST_AVAILABLE;
+ break;
+
+ case audit_substatus::FOUND_NODUMP:
+ if (output) *output << "NO GOOD DUMP KNOWN\n";
+ best_new_status = BEST_AVAILABLE;
+ break;
+
+ case audit_substatus::FOUND_BAD_CHECKSUM:
+ if (output)
+ {
+ util::stream_format(*output, "INCORRECT CHECKSUM:\n");
+ util::stream_format(*output, "EXPECTED: %s\n", record.expected_hashes().macro_string());
+ util::stream_format(*output, " FOUND: %s\n", record.actual_hashes().macro_string());
+ }
+ break;
+
+ case audit_substatus::FOUND_WRONG_LENGTH:
+ if (output) util::stream_format(*output, "INCORRECT LENGTH: %d bytes\n", record.actual_length());
+ break;
+
+ case audit_substatus::NOT_FOUND:
+ if (output)
+ {
+ device_t *const shared_device = record.shared_device();
+ if (shared_device)
+ util::stream_format(*output, "NOT FOUND (%s)\n", shared_device->shortname());
+ else
+ util::stream_format(*output, "NOT FOUND\n");
+ }
+ break;
+
+ case audit_substatus::NOT_FOUND_NODUMP:
+ if (output) *output << "NOT FOUND - NO GOOD DUMP KNOWN\n";
+ best_new_status = BEST_AVAILABLE;
+ break;
+
+ case audit_substatus::NOT_FOUND_OPTIONAL:
+ if (output) *output << "NOT FOUND BUT OPTIONAL\n";
+ best_new_status = BEST_AVAILABLE;
+ break;
+
+ default:
+ assert(false);
+ }
+
+ // downgrade the overall status if necessary
+ overall_status = (std::max)(overall_status, best_new_status);
+ }
+ return overall_status;
+}
+
+
+//-------------------------------------------------
+// audit_regions - validate/count for regions
+//-------------------------------------------------
+
+void media_auditor::audit_regions(const rom_entry *region, const char *locationtag, std::size_t &found, std::size_t &required)
+{
+ // now iterate over regions
+ for ( ; region; region = rom_next_region(region))
+ {
+ // now iterate over rom definitions
+ for (const rom_entry *rom = rom_first_file(region); rom; rom = rom_next_file(rom))
+ {
+ util::hash_collection const hashes(ROM_GETHASHDATA(rom));
+
+ // count the number of files with hashes
+ if (!hashes.flag(util::hash_collection::FLAG_NO_DUMP) && !ROM_ISOPTIONAL(rom))
+ required++;
+
+ audit_record const *record = nullptr;
+ if (ROMREGION_ISROMDATA(region))
+ record = &audit_one_rom(rom);
+ else if (ROMREGION_ISDISKDATA(region))
+ record = &audit_one_disk(rom, locationtag);
+
+ // count the number of files that are found.
+ if (record && ((record->status() == audit_status::GOOD) || (record->status() == audit_status::FOUND_INVALID)))
+ found++;
+ }
+ }
+}
+
+
+//-------------------------------------------------
+// audit_one_rom - validate a single ROM entry
+//-------------------------------------------------
+
+media_auditor::audit_record &media_auditor::audit_one_rom(const rom_entry *rom)
+{
+ // allocate and append a new record
+ audit_record &record = *m_record_list.emplace(m_record_list.end(), *rom, media_type::ROM);
+
+ // see if we have a CRC and extract it if so
+ uint32_t crc = 0;
+ bool const has_crc = record.expected_hashes().crc(crc);
+
+ // find the file and checksum it, getting the file length along the way
+ emu_file file(m_enumerator.options().media_path(), OPEN_FLAG_READ | OPEN_FLAG_NO_PRELOAD);
+ file.set_restrict_to_mediapath(true);
+ path_iterator path(m_searchpath);
+ std::string curpath;
+ while (path.next(curpath, record.name()))
+ {
+ // open the file if we can
+ osd_file::error filerr;
+ if (has_crc)
+ filerr = file.open(curpath.c_str(), crc);
+ else
+ filerr = file.open(curpath.c_str());
+
+ // if it worked, get the actual length and hashes, then stop
+ if (filerr == osd_file::error::NONE)
+ {
+ record.set_actual(file.hashes(m_validation), file.size());
+ break;
+ }
+ }
+
+ // compute the final status
+ compute_status(record, rom, record.actual_length() != 0);
+ return record;
+}
+
+
+//-------------------------------------------------
+// audit_one_disk - validate a single disk entry
+//-------------------------------------------------
+
+media_auditor::audit_record &media_auditor::audit_one_disk(const rom_entry *rom, const char *locationtag)
+{
+ // allocate and append a new record
+ audit_record &record = *m_record_list.emplace(m_record_list.end(), *rom, media_type::DISK);
+
+ // open the disk
+ chd_file source;
+ chd_error err = chd_error(open_disk_image(m_enumerator.options(), &m_enumerator.driver(), rom, source, locationtag));
+
+ // if we succeeded, get the hashes
+ if (err == CHDERR_NONE)
+ {
+ util::hash_collection hashes;
+
+ // if there's a SHA1 hash, add them to the output hash
+ if (source.sha1() != util::sha1_t::null)
+ hashes.add_sha1(source.sha1());
+
+ // update the actual values
+ record.set_actual(hashes);
+ }
+
+ // compute the final status
+ compute_status(record, rom, err == CHDERR_NONE);
+ return record;
+}
+
+
+//-------------------------------------------------
+// compute_status - compute a detailed status
+// based on the information we have
+//-------------------------------------------------
+
+void media_auditor::compute_status(audit_record &record, const rom_entry *rom, bool found)
+{
+ // if not found, provide more details
+ if (!found)
+ {
+ if (record.expected_hashes().flag(util::hash_collection::FLAG_NO_DUMP))
+ record.set_status(audit_status::NOT_FOUND, audit_substatus::NOT_FOUND_NODUMP);
+ else if (ROM_ISOPTIONAL(rom))
+ record.set_status(audit_status::NOT_FOUND, audit_substatus::NOT_FOUND_OPTIONAL);
+ else
+ record.set_status(audit_status::NOT_FOUND, audit_substatus::NOT_FOUND);
+ }
+ else
+ {
+ if (record.expected_length() != record.actual_length())
+ record.set_status(audit_status::FOUND_INVALID, audit_substatus::FOUND_WRONG_LENGTH);
+ else if (record.expected_hashes().flag(util::hash_collection::FLAG_NO_DUMP))
+ record.set_status(audit_status::GOOD, audit_substatus::FOUND_NODUMP);
+ else if (record.expected_hashes() != record.actual_hashes())
+ record.set_status(audit_status::FOUND_INVALID, audit_substatus::FOUND_BAD_CHECKSUM);
+ else if (record.expected_hashes().flag(util::hash_collection::FLAG_BAD_DUMP))
+ record.set_status(audit_status::GOOD, audit_substatus::GOOD_NEEDS_REDUMP);
+ else
+ record.set_status(audit_status::GOOD, audit_substatus::GOOD);
+ }
+}
+
+
+//-------------------------------------------------
+// find_shared_device - return the source that
+// shares a media entry with the same hashes
+//-------------------------------------------------
+
+device_t *media_auditor::find_shared_device(device_t &device, const char *name, const util::hash_collection &romhashes, uint64_t romlength)
+{
+ bool const dumped = !romhashes.flag(util::hash_collection::FLAG_NO_DUMP);
+
+ // special case for non-root devices
+ device_t *highest_device = nullptr;
+ if (device.owner())
+ {
+ for (const rom_entry *region = rom_first_region(device); region; region = rom_next_region(region))
+ {
+ for (const rom_entry *rom = rom_first_file(region); rom; rom = rom_next_file(rom))
+ {
+ if (ROM_GETLENGTH(rom) == romlength)
+ {
+ util::hash_collection hashes(ROM_GETHASHDATA(rom));
+ if ((dumped && hashes == romhashes) || (!dumped && ROM_GETNAME(rom) == name))
+ highest_device = &device;
+ }
+ }
+ }
+ }
+ else
+ {
+ // iterate up the parent chain
+ for (auto drvindex = m_enumerator.find(m_enumerator.driver().parent); drvindex >= 0; drvindex = m_enumerator.find(m_enumerator.driver(drvindex).parent))
+ {
+ for (device_t &scandevice : device_iterator(m_enumerator.config(drvindex)->root_device()))
+ {
+ for (const rom_entry *region = rom_first_region(scandevice); region; region = rom_next_region(region))
+ {
+ for (const rom_entry *rom = rom_first_file(region); rom; rom = rom_next_file(rom))
+ {
+ if (ROM_GETLENGTH(rom) == romlength)
+ {
+ util::hash_collection hashes(ROM_GETHASHDATA(rom));
+ if ((dumped && hashes == romhashes) || (!dumped && ROM_GETNAME(rom) == name))
+ highest_device = &scandevice;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return highest_device;
+}
+
+
+//-------------------------------------------------
+// audit_record - constructor
+//-------------------------------------------------
+
+media_auditor::audit_record::audit_record(const rom_entry &media, media_type type)
+ : m_type(type)
+ , m_status(audit_status::UNVERIFIED)
+ , m_substatus(audit_substatus::UNVERIFIED)
+ , m_name(ROM_GETNAME(&media))
+ , m_explength(rom_file_size(&media))
+ , m_length(0)
+ , m_exphashes()
+ , m_hashes()
+ , m_shared_device(nullptr)
+{
+ m_exphashes.from_internal_string(ROM_GETHASHDATA(&media));
+}
+
+media_auditor::audit_record::audit_record(const char *name, media_type type)
+ : m_type(type)
+ , m_status(audit_status::UNVERIFIED)
+ , m_substatus(audit_substatus::UNVERIFIED)
+ , m_name(name)
+ , m_explength(0)
+ , m_length(0)
+ , m_exphashes()
+ , m_hashes()
+ , m_shared_device(nullptr)
+{
+}
+
+
+
+// MESSUI - only report problems that the user can fix
+media_auditor::summary media_auditor::winui_summarize(const char *name, std::string *output)
+{
+ if (m_record_list.empty())
+ return NONE_NEEDED;
+
+ // loop over records
+ summary overall_status = CORRECT;
+ for (audit_record const &record : m_record_list)
+ {
+ summary best_new_status = INCORRECT;
+
+ // skip anything that's fine
+ if ( (record.substatus() == audit_substatus::GOOD)
+ || (record.substatus() == audit_substatus::GOOD_NEEDS_REDUMP)
+ || (record.substatus() == audit_substatus::NOT_FOUND_NODUMP)
+ || (record.substatus() == audit_substatus::FOUND_NODUMP)
+ )
+ continue;
+
+ // output the game name, file name, and length (if applicable)
+ //if (output)
+ {
+ output->append(string_format("%-12s: %s", name, record.name()));
+ if (record.expected_length() > 0)
+ output->append(string_format(" (%d bytes)", record.expected_length()));
+ output->append(" - ");
+ }
+
+ // use the substatus for finer details
+ switch (record.substatus())
+ {
+ case audit_substatus::FOUND_NODUMP:
+ if (output) output->append("NO GOOD DUMP KNOWN\n");
+ best_new_status = BEST_AVAILABLE;
+ break;
+
+ case audit_substatus::FOUND_BAD_CHECKSUM:
+ if (output)
+ {
+ output->append("INCORRECT CHECKSUM:\n");
+ output->append(string_format("EXPECTED: %s\n", record.expected_hashes().macro_string().c_str()));
+ output->append(string_format(" FOUND: %s\n", record.actual_hashes().macro_string().c_str()));
+ }
+ break;
+
+ case audit_substatus::FOUND_WRONG_LENGTH:
+ if (output) output->append(string_format("INCORRECT LENGTH: %d bytes\n", record.actual_length()));
+ break;
+
+ case audit_substatus::NOT_FOUND:
+ if (output)
+ {
+ device_t *shared_device = record.shared_device();
+ if (shared_device == NULL)
+ output->append("NOT FOUND\n");
+ else
+ output->append(string_format("NOT FOUND (%s)\n", shared_device->shortname()));
+ }
+ break;
+
+ case audit_substatus::NOT_FOUND_OPTIONAL:
+ if (output) output->append("NOT FOUND BUT OPTIONAL\n");
+ best_new_status = BEST_AVAILABLE;
+ break;
+
+ default:
+ break;
+ }
+
+ // downgrade the overall status if necessary
+ overall_status = (std::max)(overall_status, best_new_status);
+ }
+ return overall_status;
+}
diff --git a/docs/release/src/frontend/mame/audit.h b/docs/release/src/frontend/mame/audit.h
new file mode 100644
index 00000000000..c017651842f
--- /dev/null
+++ b/docs/release/src/frontend/mame/audit.h
@@ -0,0 +1,182 @@
+// license:BSD-3-Clause
+// copyright-holders:Aaron Giles
+/***************************************************************************
+
+ audit.h
+
+ ROM, disk, and sample auditing functions.
+
+***************************************************************************/
+
+#pragma once
+
+#ifndef MAME_FRONTEND_AUDIT_H
+#define MAME_FRONTEND_AUDIT_H
+
+#include "hash.h"
+
+#include <iosfwd>
+#include <list>
+#include <utility>
+
+
+
+//**************************************************************************
+// CONSTANTS
+//**************************************************************************
+
+// hashes to use for validation
+#define AUDIT_VALIDATE_FAST "R" /* CRC only */
+#define AUDIT_VALIDATE_FULL "RS" /* CRC + SHA1 */
+
+
+
+//**************************************************************************
+// TYPE DEFINITIONS
+//**************************************************************************
+
+
+// forward declarations
+class driver_enumerator;
+
+
+
+// ======================> media_auditor
+
+// class which manages auditing of items
+class media_auditor
+{
+public:
+ enum class media_type
+ {
+ ROM = 0,
+ DISK,
+ SAMPLE
+ };
+
+ // status values
+ enum class audit_status
+ {
+ GOOD = 0,
+ FOUND_INVALID,
+ NOT_FOUND,
+ UNVERIFIED = 100
+ };
+
+ // substatus values
+ enum class audit_substatus
+ {
+ GOOD = 0,
+ GOOD_NEEDS_REDUMP,
+ FOUND_NODUMP,
+ FOUND_BAD_CHECKSUM,
+ FOUND_WRONG_LENGTH,
+ NOT_FOUND,
+ NOT_FOUND_NODUMP,
+ NOT_FOUND_OPTIONAL,
+ UNVERIFIED = 100
+ };
+
+ // summary values
+ enum summary
+ {
+ CORRECT = 0,
+ NONE_NEEDED,
+ BEST_AVAILABLE,
+ INCORRECT,
+ NOTFOUND
+ };
+
+ // holds the result of auditing a single item
+ class audit_record
+ {
+ public:
+ // media types
+ // construction/destruction
+ audit_record(const rom_entry &media, media_type type);
+ audit_record(const char *name, media_type type);
+ audit_record(const audit_record &) = default;
+ audit_record(audit_record &&) = default;
+ audit_record &operator=(const audit_record &) = default;
+ audit_record &operator=(audit_record &&) = default;
+
+ // getters
+ media_type type() const { return m_type; }
+ audit_status status() const { return m_status; }
+ audit_substatus substatus() const { return m_substatus; }
+ const char *name() const { return m_name; }
+ uint64_t expected_length() const { return m_explength; }
+ uint64_t actual_length() const { return m_length; }
+ const util::hash_collection &expected_hashes() const { return m_exphashes; }
+ const util::hash_collection &actual_hashes() const { return m_hashes; }
+ device_t *shared_device() const { return m_shared_device; }
+
+ // setters
+ void set_status(audit_status status, audit_substatus substatus)
+ {
+ m_status = status;
+ m_substatus = substatus;
+ }
+
+ void set_actual(const util::hash_collection &hashes, uint64_t length = 0)
+ {
+ m_hashes = hashes;
+ m_length = length;
+ }
+
+ void set_actual(util::hash_collection &&hashes, uint64_t length = 0)
+ {
+ m_hashes = std::move(hashes);
+ m_length = length;
+ }
+
+ void set_shared_device(device_t *shared_device)
+ {
+ m_shared_device = shared_device;
+ }
+
+ private:
+ // internal state
+ media_type m_type; // type of item that was audited
+ audit_status m_status; // status of audit on this item
+ audit_substatus m_substatus; // finer-detail status
+ const char * m_name; // name of item
+ uint64_t m_explength; // expected length of item
+ uint64_t m_length; // actual length of item
+ util::hash_collection m_exphashes; // expected hash data
+ util::hash_collection m_hashes; // actual hash information
+ device_t * m_shared_device; // device that shares the rom
+ };
+ using record_list = std::list<audit_record>;
+
+ // construction/destruction
+ media_auditor(const driver_enumerator &enumerator);
+
+ // getters
+ const record_list &records() const { return m_record_list; }
+
+ // audit operations
+ summary audit_media(const char *validation = AUDIT_VALIDATE_FULL);
+ summary audit_device(device_t &device, const char *validation = AUDIT_VALIDATE_FULL);
+ summary audit_software(const std::string &list_name, const software_info *swinfo, const char *validation = AUDIT_VALIDATE_FULL);
+ summary audit_samples();
+ summary summarize(const char *name, std::ostream *output = nullptr) const;
+ summary winui_summarize(const char *name, std::string *output = nullptr); //WINUI - only report problems that the user can fix
+
+private:
+ // internal helpers
+ void audit_regions(const rom_entry *region, const char *locationtag, std::size_t &found, std::size_t &required);
+ audit_record &audit_one_rom(const rom_entry *rom);
+ audit_record &audit_one_disk(const rom_entry *rom, const char *locationtag);
+ void compute_status(audit_record &record, const rom_entry *rom, bool found);
+ device_t *find_shared_device(device_t &device, const char *name, const util::hash_collection &romhashes, uint64_t romlength);
+
+ // internal state
+ record_list m_record_list;
+ const driver_enumerator & m_enumerator;
+ const char * m_validation;
+ const char * m_searchpath;
+};
+
+
+#endif // MAME_FRONTEND_AUDIT_H
diff --git a/docs/release/src/frontend/mame/info.cpp b/docs/release/src/frontend/mame/info.cpp
new file mode 100644
index 00000000000..91669110961
--- /dev/null
+++ b/docs/release/src/frontend/mame/info.cpp
@@ -0,0 +1,1882 @@
+// license:BSD-3-Clause
+// copyright-holders:Aaron Giles,Paul Priest
+/***************************************************************************
+
+ info.c
+
+ Dumps the MAME internal data as an XML file.
+
+***************************************************************************/
+
+#include "emu.h"
+
+#include "info.h"
+#include "mameopts.h"
+
+#include "machine/ram.h"
+#include "sound/samples.h"
+
+#include "config.h"
+#include "drivenum.h"
+#include "romload.h"
+#include "screen.h"
+#include "softlist_dev.h"
+#include "speaker.h"
+
+#include "xmlfile.h"
+
+#include <ctype.h>
+#include <cstring>
+#include <map>
+
+
+#define XML_ROOT "mame"
+#define XML_TOP "machine"
+
+
+//**************************************************************************
+// GLOBAL VARIABLES
+//**************************************************************************
+
+// DTD string describing the data
+const char info_xml_creator::s_dtd_string[] =
+"<!DOCTYPE __XML_ROOT__ [\n"
+"<!ELEMENT __XML_ROOT__ (__XML_TOP__+)>\n"
+"\t<!ATTLIST __XML_ROOT__ build CDATA #IMPLIED>\n"
+"\t<!ATTLIST __XML_ROOT__ debug (yes|no) \"no\">\n"
+"\t<!ATTLIST __XML_ROOT__ mameconfig CDATA #REQUIRED>\n"
+"\t<!ELEMENT __XML_TOP__ (description, year?, manufacturer?, biosset*, rom*, disk*, device_ref*, sample*, chip*, display*, sound?, input?, dipswitch*, configuration*, port*, adjuster*, driver?, feature*, device*, slot*, softwarelist*, ramoption*)>\n"
+"\t\t<!ATTLIST __XML_TOP__ name CDATA #REQUIRED>\n"
+"\t\t<!ATTLIST __XML_TOP__ sourcefile CDATA #IMPLIED>\n"
+"\t\t<!ATTLIST __XML_TOP__ isbios (yes|no) \"no\">\n"
+"\t\t<!ATTLIST __XML_TOP__ isdevice (yes|no) \"no\">\n"
+"\t\t<!ATTLIST __XML_TOP__ ismechanical (yes|no) \"no\">\n"
+"\t\t<!ATTLIST __XML_TOP__ runnable (yes|no) \"yes\">\n"
+"\t\t<!ATTLIST __XML_TOP__ cloneof CDATA #IMPLIED>\n"
+"\t\t<!ATTLIST __XML_TOP__ romof CDATA #IMPLIED>\n"
+"\t\t<!ATTLIST __XML_TOP__ sampleof CDATA #IMPLIED>\n"
+"\t\t<!ELEMENT description (#PCDATA)>\n"
+"\t\t<!ELEMENT year (#PCDATA)>\n"
+"\t\t<!ELEMENT manufacturer (#PCDATA)>\n"
+"\t\t<!ELEMENT biosset EMPTY>\n"
+"\t\t\t<!ATTLIST biosset name CDATA #REQUIRED>\n"
+"\t\t\t<!ATTLIST biosset description CDATA #REQUIRED>\n"
+"\t\t\t<!ATTLIST biosset default (yes|no) \"no\">\n"
+"\t\t<!ELEMENT rom EMPTY>\n"
+"\t\t\t<!ATTLIST rom name CDATA #REQUIRED>\n"
+"\t\t\t<!ATTLIST rom bios CDATA #IMPLIED>\n"
+"\t\t\t<!ATTLIST rom size CDATA #REQUIRED>\n"
+"\t\t\t<!ATTLIST rom crc CDATA #IMPLIED>\n"
+"\t\t\t<!ATTLIST rom sha1 CDATA #IMPLIED>\n"
+"\t\t\t<!ATTLIST rom merge CDATA #IMPLIED>\n"
+"\t\t\t<!ATTLIST rom region CDATA #IMPLIED>\n"
+"\t\t\t<!ATTLIST rom offset CDATA #IMPLIED>\n"
+"\t\t\t<!ATTLIST rom status (baddump|nodump|good) \"good\">\n"
+"\t\t\t<!ATTLIST rom optional (yes|no) \"no\">\n"
+"\t\t<!ELEMENT disk EMPTY>\n"
+"\t\t\t<!ATTLIST disk name CDATA #REQUIRED>\n"
+"\t\t\t<!ATTLIST disk sha1 CDATA #IMPLIED>\n"
+"\t\t\t<!ATTLIST disk merge CDATA #IMPLIED>\n"
+"\t\t\t<!ATTLIST disk region CDATA #IMPLIED>\n"
+"\t\t\t<!ATTLIST disk index CDATA #IMPLIED>\n"
+"\t\t\t<!ATTLIST disk writable (yes|no) \"no\">\n"
+"\t\t\t<!ATTLIST disk status (baddump|nodump|good) \"good\">\n"
+"\t\t\t<!ATTLIST disk optional (yes|no) \"no\">\n"
+"\t\t<!ELEMENT device_ref EMPTY>\n"
+"\t\t\t<!ATTLIST device_ref name CDATA #REQUIRED>\n"
+"\t\t<!ELEMENT sample EMPTY>\n"
+"\t\t\t<!ATTLIST sample name CDATA #REQUIRED>\n"
+"\t\t<!ELEMENT chip EMPTY>\n"
+"\t\t\t<!ATTLIST chip name CDATA #REQUIRED>\n"
+"\t\t\t<!ATTLIST chip tag CDATA #IMPLIED>\n"
+"\t\t\t<!ATTLIST chip type (cpu|audio) #REQUIRED>\n"
+"\t\t\t<!ATTLIST chip clock CDATA #IMPLIED>\n"
+"\t\t<!ELEMENT display EMPTY>\n"
+"\t\t\t<!ATTLIST display tag CDATA #IMPLIED>\n"
+"\t\t\t<!ATTLIST display type (raster|vector|lcd|svg|unknown) #REQUIRED>\n"
+"\t\t\t<!ATTLIST display rotate (0|90|180|270) #IMPLIED>\n"
+"\t\t\t<!ATTLIST display flipx (yes|no) \"no\">\n"
+"\t\t\t<!ATTLIST display width CDATA #IMPLIED>\n"
+"\t\t\t<!ATTLIST display height CDATA #IMPLIED>\n"
+"\t\t\t<!ATTLIST display refresh CDATA #REQUIRED>\n"
+"\t\t\t<!ATTLIST display pixclock CDATA #IMPLIED>\n"
+"\t\t\t<!ATTLIST display htotal CDATA #IMPLIED>\n"
+"\t\t\t<!ATTLIST display hbend CDATA #IMPLIED>\n"
+"\t\t\t<!ATTLIST display hbstart CDATA #IMPLIED>\n"
+"\t\t\t<!ATTLIST display vtotal CDATA #IMPLIED>\n"
+"\t\t\t<!ATTLIST display vbend CDATA #IMPLIED>\n"
+"\t\t\t<!ATTLIST display vbstart CDATA #IMPLIED>\n"
+"\t\t<!ELEMENT sound EMPTY>\n"
+"\t\t\t<!ATTLIST sound channels CDATA #REQUIRED>\n"
+"\t\t<!ELEMENT condition EMPTY>\n"
+"\t\t\t<!ATTLIST condition tag CDATA #REQUIRED>\n"
+"\t\t\t<!ATTLIST condition mask CDATA #REQUIRED>\n"
+"\t\t\t<!ATTLIST condition relation (eq|ne|gt|le|lt|ge) #REQUIRED>\n"
+"\t\t\t<!ATTLIST condition value CDATA #REQUIRED>\n"
+"\t\t<!ELEMENT input (control*)>\n"
+"\t\t\t<!ATTLIST input service (yes|no) \"no\">\n"
+"\t\t\t<!ATTLIST input tilt (yes|no) \"no\">\n"
+"\t\t\t<!ATTLIST input players CDATA #REQUIRED>\n"
+"\t\t\t<!ATTLIST input coins CDATA #IMPLIED>\n"
+"\t\t\t<!ELEMENT control EMPTY>\n"
+"\t\t\t\t<!ATTLIST control type CDATA #REQUIRED>\n"
+"\t\t\t\t<!ATTLIST control player CDATA #IMPLIED>\n"
+"\t\t\t\t<!ATTLIST control buttons CDATA #IMPLIED>\n"
+"\t\t\t\t<!ATTLIST control reqbuttons CDATA #IMPLIED>\n"
+"\t\t\t\t<!ATTLIST control minimum CDATA #IMPLIED>\n"
+"\t\t\t\t<!ATTLIST control maximum CDATA #IMPLIED>\n"
+"\t\t\t\t<!ATTLIST control sensitivity CDATA #IMPLIED>\n"
+"\t\t\t\t<!ATTLIST control keydelta CDATA #IMPLIED>\n"
+"\t\t\t\t<!ATTLIST control reverse (yes|no) \"no\">\n"
+"\t\t\t\t<!ATTLIST control ways CDATA #IMPLIED>\n"
+"\t\t\t\t<!ATTLIST control ways2 CDATA #IMPLIED>\n"
+"\t\t\t\t<!ATTLIST control ways3 CDATA #IMPLIED>\n"
+"\t\t<!ELEMENT dipswitch (condition?, diplocation*, dipvalue*)>\n"
+"\t\t\t<!ATTLIST dipswitch name CDATA #REQUIRED>\n"
+"\t\t\t<!ATTLIST dipswitch tag CDATA #REQUIRED>\n"
+"\t\t\t<!ATTLIST dipswitch mask CDATA #REQUIRED>\n"
+"\t\t\t<!ELEMENT condition EMPTY>\n"
+"\t\t\t<!ATTLIST condition tag CDATA #REQUIRED>\n"
+"\t\t\t<!ATTLIST condition mask CDATA #REQUIRED>\n"
+"\t\t\t<!ATTLIST condition relation (eq|ne|gt|le|lt|ge) #REQUIRED>\n"
+"\t\t\t<!ATTLIST condition value CDATA #REQUIRED>\n"
+"\t\t\t<!ELEMENT diplocation EMPTY>\n"
+"\t\t\t\t<!ATTLIST diplocation name CDATA #REQUIRED>\n"
+"\t\t\t\t<!ATTLIST diplocation number CDATA #REQUIRED>\n"
+"\t\t\t\t<!ATTLIST diplocation inverted (yes|no) \"no\">\n"
+"\t\t\t<!ELEMENT dipvalue (condition?)>\n"
+"\t\t\t\t<!ATTLIST dipvalue name CDATA #REQUIRED>\n"
+"\t\t\t\t<!ATTLIST dipvalue value CDATA #REQUIRED>\n"
+"\t\t\t\t<!ATTLIST dipvalue default (yes|no) \"no\">\n"
+"\t\t<!ELEMENT configuration (condition?, conflocation*, confsetting*)>\n"
+"\t\t\t<!ATTLIST configuration name CDATA #REQUIRED>\n"
+"\t\t\t<!ATTLIST configuration tag CDATA #REQUIRED>\n"
+"\t\t\t<!ATTLIST configuration mask CDATA #REQUIRED>\n"
+"\t\t\t<!ELEMENT conflocation EMPTY>\n"
+"\t\t\t\t<!ATTLIST conflocation name CDATA #REQUIRED>\n"
+"\t\t\t\t<!ATTLIST conflocation number CDATA #REQUIRED>\n"
+"\t\t\t\t<!ATTLIST conflocation inverted (yes|no) \"no\">\n"
+"\t\t\t<!ELEMENT confsetting (condition?)>\n"
+"\t\t\t\t<!ATTLIST confsetting name CDATA #REQUIRED>\n"
+"\t\t\t\t<!ATTLIST confsetting value CDATA #REQUIRED>\n"
+"\t\t\t\t<!ATTLIST confsetting default (yes|no) \"no\">\n"
+"\t\t<!ELEMENT port (analog*)>\n"
+"\t\t\t<!ATTLIST port tag CDATA #REQUIRED>\n"
+"\t\t\t<!ELEMENT analog EMPTY>\n"
+"\t\t\t\t<!ATTLIST analog mask CDATA #REQUIRED>\n"
+"\t\t<!ELEMENT adjuster (condition?)>\n"
+"\t\t\t<!ATTLIST adjuster name CDATA #REQUIRED>\n"
+"\t\t\t<!ATTLIST adjuster default CDATA #REQUIRED>\n"
+"\t\t<!ELEMENT driver EMPTY>\n"
+"\t\t\t<!ATTLIST driver status (good|imperfect|preliminary) #REQUIRED>\n"
+"\t\t\t<!ATTLIST driver emulation (good|imperfect|preliminary) #REQUIRED>\n"
+"\t\t\t<!ATTLIST driver cocktail (good|imperfect|preliminary) #IMPLIED>\n"
+"\t\t\t<!ATTLIST driver savestate (supported|unsupported) #REQUIRED>\n"
+"\t\t<!ELEMENT feature EMPTY>\n"
+"\t\t\t<!ATTLIST feature type (protection|timing|graphics|palette|sound|capture|camera|microphone|controls|keyboard|mouse|media|disk|printer|tape|punch|drum|rom|comms|lan|wan) #REQUIRED>\n"
+"\t\t\t<!ATTLIST feature status (unemulated|imperfect) #IMPLIED>\n"
+"\t\t\t<!ATTLIST feature overall (unemulated|imperfect) #IMPLIED>\n"
+"\t\t<!ELEMENT device (instance?, extension*)>\n"
+"\t\t\t<!ATTLIST device type CDATA #REQUIRED>\n"
+"\t\t\t<!ATTLIST device tag CDATA #IMPLIED>\n"
+"\t\t\t<!ATTLIST device fixed_image CDATA #IMPLIED>\n"
+"\t\t\t<!ATTLIST device mandatory CDATA #IMPLIED>\n"
+"\t\t\t<!ATTLIST device interface CDATA #IMPLIED>\n"
+"\t\t\t<!ELEMENT instance EMPTY>\n"
+"\t\t\t\t<!ATTLIST instance name CDATA #REQUIRED>\n"
+"\t\t\t\t<!ATTLIST instance briefname CDATA #REQUIRED>\n"
+"\t\t\t<!ELEMENT extension EMPTY>\n"
+"\t\t\t\t<!ATTLIST extension name CDATA #REQUIRED>\n"
+"\t\t<!ELEMENT slot (slotoption*)>\n"
+"\t\t\t<!ATTLIST slot name CDATA #REQUIRED>\n"
+"\t\t\t<!ELEMENT slotoption EMPTY>\n"
+"\t\t\t\t<!ATTLIST slotoption name CDATA #REQUIRED>\n"
+"\t\t\t\t<!ATTLIST slotoption devname CDATA #REQUIRED>\n"
+"\t\t\t\t<!ATTLIST slotoption default (yes|no) \"no\">\n"
+"\t\t<!ELEMENT softwarelist EMPTY>\n"
+"\t\t\t<!ATTLIST softwarelist name CDATA #REQUIRED>\n"
+"\t\t\t<!ATTLIST softwarelist status (original|compatible) #REQUIRED>\n"
+"\t\t\t<!ATTLIST softwarelist filter CDATA #IMPLIED>\n"
+"\t\t<!ELEMENT ramoption (#PCDATA)>\n"
+"\t\t\t<!ATTLIST ramoption name CDATA #REQUIRED>\n"
+"\t\t\t<!ATTLIST ramoption default CDATA #IMPLIED>\n"
+"]>";
+
+
+//**************************************************************************
+// INFO XML CREATOR
+//**************************************************************************
+
+//-------------------------------------------------
+// info_xml_creator - constructor
+//-------------------------------------------------
+
+info_xml_creator::info_xml_creator(emu_options const &options, bool dtd)
+ : m_output(nullptr)
+ , m_dtd(dtd)
+{
+}
+
+
+//-------------------------------------------------
+// output_mame_xml - print the XML information
+// for all known games
+//-------------------------------------------------
+
+void info_xml_creator::output(FILE *out, std::vector<std::string> const &patterns)
+{
+ m_output = out;
+
+ std::unique_ptr<device_type_set> devfilter(patterns.empty() ? nullptr : new device_type_set);
+
+ // track which patterns match machines
+ driver_enumerator drivlist(m_lookup_options);
+ std::vector<bool> matched(patterns.size(), false);
+ size_t exact_matches = 0;
+ auto const included = [&patterns, &matched, &exact_matches] (char const *const name) -> bool
+ {
+ if (patterns.empty())
+ return true;
+
+ bool result = false;
+ auto it = matched.begin();
+ for (std::string const &pat : patterns)
+ {
+ if (!core_strwildcmp(pat.c_str(), name))
+ {
+ result = true;
+ if (!*it)
+ {
+ *it = true;
+ if (!core_iswildstr(pat.c_str()))
+ ++exact_matches;
+ }
+ }
+ ++it;
+ }
+ return result;
+ };
+
+ // iterate through the drivers, outputting one at a time
+ bool first = true;
+ while (drivlist.next())
+ {
+ if (included(drivlist.driver().name))
+ {
+ if (first)
+ {
+ output_header();
+ first = false;
+ }
+ output_one(drivlist, devfilter.get());
+
+ // stop looking if we found everything specified
+ if (!patterns.empty() && exact_matches == patterns.size())
+ break;
+ }
+ }
+
+ // iterate through the device types if not everything matches a driver
+ if (!patterns.empty() && exact_matches != patterns.size())
+ {
+ for (device_type type : registered_device_types)
+ {
+ if (included(type.shortname()))
+ {
+ devfilter->insert(&type);
+ if (exact_matches == patterns.size())
+ break;
+ }
+ }
+ }
+
+ // output devices (both devices with roms and slot devices)
+ if (!devfilter || !devfilter->empty())
+ {
+ if (first)
+ {
+ output_header();
+ first = false;
+ }
+ output_devices(devfilter.get());
+ }
+
+ if (!first)
+ output_footer();
+
+ // throw an error if there were unmatched patterns
+ auto it = matched.begin();
+ for (std::string const &pat : patterns)
+ {
+ if (!*it)
+ throw emu_fatalerror(EMU_ERR_NO_SUCH_GAME, "No matching machines found for '%s'", pat.c_str());
+
+ ++it;
+ }
+}
+
+
+//-------------------------------------------------
+// output_mame_xml - print the XML information
+// for a subset of games
+//-------------------------------------------------
+
+void info_xml_creator::output(FILE *out, driver_enumerator &drivlist, bool nodevices)
+{
+ m_output = out;
+
+ device_type_set devfilter;
+
+ output_header();
+
+ // iterate through the drivers, outputting one at a time
+ while (drivlist.next())
+ output_one(drivlist, &devfilter);
+
+ // output devices (both devices with roms and slot devices)
+ if (!nodevices)
+ output_devices(&devfilter);
+
+ output_footer();
+}
+
+
+//-------------------------------------------------
+// output_header - print the XML DTD and open
+// the root element
+//-------------------------------------------------
+
+void info_xml_creator::output_header()
+{
+ if (m_dtd)
+ {
+ // output the DTD
+ fprintf(m_output, "<?xml version=\"1.0\"?>\n");
+ std::string dtd(s_dtd_string);
+ strreplace(dtd, "__XML_ROOT__", XML_ROOT);
+ strreplace(dtd, "__XML_TOP__", XML_TOP);
+
+ fprintf(m_output, "%s\n\n", dtd.c_str());
+ }
+
+ // top-level tag
+ fprintf(m_output, "<%s build=\"%s\" debug=\""
+#ifdef MAME_DEBUG
+ "yes"
+#else
+ "no"
+#endif
+ "\" mameconfig=\"%d\">\n",
+ XML_ROOT,
+ util::xml::normalize_string(emulator_info::get_build_version()),
+ CONFIG_VERSION);
+}
+
+
+//-------------------------------------------------
+// output_header - close the root element
+//-------------------------------------------------
+
+void info_xml_creator::output_footer()
+{
+ // close the top level tag
+ fprintf(m_output, "</%s>\n", XML_ROOT);
+}
+
+
+//-------------------------------------------------
+// output_one - print the XML information
+// for one particular game driver
+//-------------------------------------------------
+
+void info_xml_creator::output_one(driver_enumerator &drivlist, device_type_set *devtypes)
+{
+ const game_driver &driver = drivlist.driver();
+ std::shared_ptr<machine_config> const config(drivlist.config());
+ device_iterator iter(config->root_device());
+
+ // allocate input ports and build overall emulation status
+ ioport_list portlist;
+ std::string errors;
+ device_t::feature_type overall_unemulated(driver.type.unemulated_features());
+ device_t::feature_type overall_imperfect(driver.type.imperfect_features());
+ for (device_t &device : iter)
+ {
+ portlist.append(device, errors);
+ overall_unemulated |= device.type().unemulated_features();
+ overall_imperfect |= device.type().imperfect_features();
+
+ if (devtypes && device.owner())
+ devtypes->insert(&device.type());
+ }
+
+ // renumber player numbers for controller ports
+ int player_offset = 0;
+ // but treat keyboard count separately from players' number
+ int kbd_offset = 0;
+ for (device_t &device : iter)
+ {
+ int nplayers = 0;
+ bool new_kbd = false;
+ for (auto &port : portlist)
+ if (&port.second->device() == &device)
+ for (ioport_field &field : port.second->fields())
+ if (field.type() >= IPT_START && field.type() < IPT_ANALOG_LAST)
+ {
+ if (field.type() == IPT_KEYBOARD)
+ {
+ if (!new_kbd)
+ new_kbd = true;
+ field.set_player(field.player() + kbd_offset);
+ }
+ else
+ {
+ nplayers = std::max(nplayers, field.player() + 1);
+ field.set_player(field.player() + player_offset);
+ }
+ }
+ player_offset += nplayers;
+ if (new_kbd) kbd_offset++;
+ }
+
+ // print the header and the machine name
+ fprintf(m_output, "\t<%s name=\"%s\"", XML_TOP, util::xml::normalize_string(driver.name));
+
+ // strip away any path information from the source_file and output it
+ const char *start = strrchr(driver.type.source(), '/');
+ if (!start)
+ start = strrchr(driver.type.source(), '\\');
+ start = start ? (start + 1) : driver.type.source();
+ fprintf(m_output, " sourcefile=\"%s\"", util::xml::normalize_string(start));
+
+ // append bios and runnable flags
+ if (driver.flags & machine_flags::IS_BIOS_ROOT)
+ fprintf(m_output, " isbios=\"yes\"");
+ if (driver.flags & machine_flags::MECHANICAL)
+ fprintf(m_output, " ismechanical=\"yes\"");
+
+ // display clone information
+ int clone_of = drivlist.find(driver.parent);
+ if (clone_of != -1 && !(drivlist.driver(clone_of).flags & machine_flags::IS_BIOS_ROOT))
+ fprintf(m_output, " cloneof=\"%s\"", util::xml::normalize_string(drivlist.driver(clone_of).name));
+ if (clone_of != -1)
+ fprintf(m_output, " romof=\"%s\"", util::xml::normalize_string(drivlist.driver(clone_of).name));
+
+ // display sample information and close the game tag
+ output_sampleof(config->root_device());
+ fprintf(m_output, ">\n");
+
+ // output game description
+ if (driver.type.fullname() != nullptr)
+ fprintf(m_output, "\t\t<description>%s</description>\n", util::xml::normalize_string(driver.type.fullname()));
+
+ // print the year only if is a number or another allowed character (? or +)
+ if (driver.year != nullptr && strspn(driver.year, "0123456789?+") == strlen(driver.year))
+ fprintf(m_output, "\t\t<year>%s</year>\n", util::xml::normalize_string(driver.year));
+
+ // print the manufacturer information
+ if (driver.manufacturer != nullptr)
+ fprintf(m_output, "\t\t<manufacturer>%s</manufacturer>\n", util::xml::normalize_string(driver.manufacturer));
+
+ // now print various additional information
+ output_bios(config->root_device());
+ output_rom(&drivlist, config->root_device());
+ output_device_refs(config->root_device());
+ output_sample(config->root_device());
+ output_chips(config->root_device(), "");
+ output_display(config->root_device(), &drivlist.driver().flags, "");
+ output_sound(config->root_device());
+ output_input(portlist);
+ output_switches(portlist, "", IPT_DIPSWITCH, "dipswitch", "diplocation", "dipvalue");
+ output_switches(portlist, "", IPT_CONFIG, "configuration", "conflocation", "confsetting");
+ output_ports(portlist);
+ output_adjusters(portlist);
+ output_driver(driver, overall_unemulated, overall_imperfect);
+ output_features(driver.type, overall_unemulated, overall_imperfect);
+ output_images(config->root_device(), "");
+ output_slots(*config, config->root_device(), "", devtypes);
+ output_software_list(config->root_device());
+ output_ramoptions(config->root_device());
+
+ // close the topmost tag
+ fprintf(m_output, "\t</%s>\n", XML_TOP);
+}
+
+
+//-------------------------------------------------
+// output_one_device - print the XML info for
+// a single device
+//-------------------------------------------------
+
+void info_xml_creator::output_one_device(machine_config &config, device_t &device, const char *devtag)
+{
+ bool has_speaker = false, has_input = false;
+ // check if the device adds speakers to the system
+ sound_interface_iterator snditer(device);
+ if (snditer.first() != nullptr)
+ has_speaker = true;
+
+ // generate input list and build overall emulation status
+ ioport_list portlist;
+ std::string errors;
+ device_t::feature_type overall_unemulated(device.type().unemulated_features());
+ device_t::feature_type overall_imperfect(device.type().imperfect_features());
+ for (device_t &dev : device_iterator(device))
+ {
+ portlist.append(dev, errors);
+ overall_unemulated |= dev.type().unemulated_features();
+ overall_imperfect |= dev.type().imperfect_features();
+ }
+
+ // check if the device adds player inputs (other than dsw and configs) to the system
+ for (auto &port : portlist)
+ for (ioport_field const &field : port.second->fields())
+ if (field.type() >= IPT_START1 && field.type() < IPT_UI_FIRST)
+ {
+ has_input = true;
+ break;
+ }
+
+ // start to output info
+ fprintf(m_output, "\t<%s name=\"%s\"", XML_TOP, util::xml::normalize_string(device.shortname()));
+ std::string src(device.source());
+ strreplace(src,"../", "");
+ fprintf(m_output, " sourcefile=\"%s\" isdevice=\"yes\" runnable=\"no\"", util::xml::normalize_string(src.c_str()));
+ output_sampleof(device);
+ fprintf(m_output, ">\n\t\t<description>%s</description>\n", util::xml::normalize_string(device.name()));
+
+ output_bios(device);
+ output_rom(nullptr, device);
+ output_device_refs(device);
+
+ if (device.type().type() != typeid(samples_device)) // ignore samples_device itself
+ output_sample(device);
+
+ output_chips(device, devtag);
+ output_display(device, nullptr, devtag);
+ if (has_speaker)
+ output_sound(device);
+ if (has_input)
+ output_input(portlist);
+ output_switches(portlist, devtag, IPT_DIPSWITCH, "dipswitch", "diplocation", "dipvalue");
+ output_switches(portlist, devtag, IPT_CONFIG, "configuration", "conflocation", "confsetting");
+ output_adjusters(portlist);
+ output_features(device.type(), overall_unemulated, overall_imperfect);
+ output_images(device, devtag);
+ output_slots(config, device, devtag, nullptr);
+ fprintf(m_output, "\t</%s>\n", XML_TOP);
+}
+
+
+//-------------------------------------------------
+// output_devices - print the XML info for
+// registered device types
+//-------------------------------------------------
+
+void info_xml_creator::output_devices(device_type_set const *filter)
+{
+ // get config for empty machine
+ machine_config config(GAME_NAME(___empty), m_lookup_options);
+
+ auto const action = [this, &config] (device_type type)
+ {
+ // add it at the root of the machine config
+ device_t *dev;
+ {
+ machine_config::token const tok(config.begin_configuration(config.root_device()));
+ dev = config.device_add("_tmp", type, 0);
+ }
+
+ // notify this device and all its subdevices that they are now configured
+ for (device_t &device : device_iterator(*dev))
+ if (!device.configured())
+ device.config_complete();
+
+ // print details and remove it
+ output_one_device(config, *dev, dev->tag());
+ machine_config::token const tok(config.begin_configuration(config.root_device()));
+ config.device_remove("_tmp");
+ };
+
+ // run through devices
+ if (filter)
+ {
+ for (std::add_pointer_t<device_type> type : *filter) action(*type);
+ }
+ else
+ {
+ for (device_type type : registered_device_types) action(type);
+ }
+}
+
+
+//------------------------------------------------
+// output_device_refs - when a machine uses a
+// subdevice, print a reference
+//-------------------------------------------------
+
+void info_xml_creator::output_device_refs(device_t &root)
+{
+ for (device_t &device : device_iterator(root))
+ if (&device != &root)
+ fprintf(m_output, "\t\t<device_ref name=\"%s\"/>\n", util::xml::normalize_string(device.shortname()));
+}
+
+
+//------------------------------------------------
+// output_sampleof - print the 'sampleof'
+// attribute, if appropriate
+//-------------------------------------------------
+
+void info_xml_creator::output_sampleof(device_t &device)
+{
+ // iterate over sample devices
+ for (samples_device &samples : samples_device_iterator(device))
+ {
+ samples_iterator sampiter(samples);
+ if (sampiter.altbasename() != nullptr)
+ {
+ fprintf(m_output, " sampleof=\"%s\"", util::xml::normalize_string(sampiter.altbasename()));
+
+ // must stop here, as there can only be one attribute of the same name
+ return;
+ }
+ }
+}
+
+
+//-------------------------------------------------
+// output_bios - print BIOS sets for a device
+//-------------------------------------------------
+
+void info_xml_creator::output_bios(device_t const &device)
+{
+ // first determine the default BIOS name
+ char const *defaultname(nullptr);
+ for (tiny_rom_entry const *rom = device.rom_region(); rom && !ROMENTRY_ISEND(rom); ++rom)
+ {
+ if (ROMENTRY_ISDEFAULT_BIOS(rom))
+ defaultname = rom->name;
+ }
+
+ // iterate over ROM entries and look for BIOSes
+ for (romload::system_bios const &bios : romload::entries(device.rom_region()).get_system_bioses())
+ {
+ // output extracted name and descriptions
+ fprintf(m_output, "\t\t<biosset");
+ fprintf(m_output, " name=\"%s\"", util::xml::normalize_string(bios.get_name()));
+ fprintf(m_output, " description=\"%s\"", util::xml::normalize_string(bios.get_description()));
+ if (defaultname && !std::strcmp(defaultname, bios.get_name()))
+ fprintf(m_output, " default=\"yes\"");
+ fprintf(m_output, "/>\n");
+ }
+}
+
+
+//-------------------------------------------------
+// output_rom - print the roms section of
+// the XML output
+//-------------------------------------------------
+
+void info_xml_creator::output_rom(driver_enumerator *drivlist, device_t &device)
+{
+ enum class type { BIOS, NORMAL, DISK };
+ std::map<u32, char const *> biosnames;
+ bool bios_scanned(false);
+ auto const get_biosname =
+ [&biosnames, &bios_scanned] (tiny_rom_entry const *rom) -> char const *
+ {
+ u32 const biosflags(ROM_GETBIOSFLAGS(rom));
+ std::map<u32, char const *>::const_iterator const found(biosnames.find(biosflags));
+ if (biosnames.end() != found)
+ return found->second;
+
+ char const *result(nullptr);
+ if (!bios_scanned)
+ {
+ for (++rom; !ROMENTRY_ISEND(rom); ++rom)
+ {
+ if (ROMENTRY_ISSYSTEM_BIOS(rom))
+ {
+ u32 const biosno(ROM_GETBIOSFLAGS(rom));
+ biosnames.emplace(biosno, rom->name);
+ if (biosflags == biosno)
+ result = rom->name;
+ }
+ }
+ bios_scanned = true;
+ }
+ return result;
+ };
+ auto const rom_file_size = // FIXME: need a common way to do this without the cost of allocating rom_entry
+ [] (tiny_rom_entry const *romp) -> u32
+ {
+ u32 maxlength = 0;
+
+ // loop until we run out of reloads
+ do
+ {
+ // loop until we run out of continues/ignores */
+ u32 curlength(ROM_GETLENGTH(romp++));
+ while (ROMENTRY_ISCONTINUE(romp) || ROMENTRY_ISIGNORE(romp))
+ curlength += ROM_GETLENGTH(romp++);
+
+ // track the maximum length
+ maxlength = (std::max)(maxlength, curlength);
+ }
+ while (ROMENTRY_ISRELOAD(romp));
+
+ return maxlength;
+ };
+
+ // iterate over 3 different ROM "types": BIOS, ROMs, DISKs
+ bool const do_merge_name = drivlist && dynamic_cast<driver_device *>(&device);
+ for (type pass : { type::BIOS, type::NORMAL, type::DISK })
+ {
+ tiny_rom_entry const *region(nullptr);
+ for (tiny_rom_entry const *rom = device.rom_region(); rom && !ROMENTRY_ISEND(rom); ++rom)
+ {
+ if (ROMENTRY_ISREGION(rom))
+ region = rom;
+ else if (ROMENTRY_ISSYSTEM_BIOS(rom))
+ biosnames.emplace(ROM_GETBIOSFLAGS(rom), rom->name);
+
+ if (!ROMENTRY_ISFILE(rom))
+ continue;
+
+ // only list disks on the disk pass
+ bool const is_disk = ROMREGION_ISDISKDATA(region);
+ if ((type::DISK == pass) != is_disk)
+ continue;
+
+ // BIOS ROMs only apply to bioses
+ // FIXME: disk images associated with a system BIOS will never be listed
+ u32 const biosno(ROM_GETBIOSFLAGS(rom));
+ if ((type::BIOS == pass) != bool(biosno))
+ continue;
+ char const *const bios_name((!is_disk && biosno) ? get_biosname(rom) : nullptr);
+
+ // if we have a valid ROM and we are a clone, see if we can find the parent ROM
+ util::hash_collection const hashes(rom->hashdata);
+ char const *const merge_name((do_merge_name && !hashes.flag(util::hash_collection::FLAG_NO_DUMP)) ? get_merge_name(*drivlist, hashes) : nullptr);
+
+ // opening tag
+ fprintf(m_output, is_disk ? "\t\t<disk" : "\t\t<rom");
+
+ // add name, merge, bios, and size tags */
+ char const *const name(rom->name);
+ if (name && name[0])
+ fprintf(m_output, " name=\"%s\"", util::xml::normalize_string(name));
+ if (merge_name)
+ fprintf(m_output, " merge=\"%s\"", util::xml::normalize_string(merge_name));
+ if (bios_name)
+ fprintf(m_output, " bios=\"%s\"", util::xml::normalize_string(bios_name));
+ if (!is_disk)
+ fprintf(m_output, " size=\"%u\"", rom_file_size(rom));
+
+ // dump checksum information only if there is a known dump
+ if (!hashes.flag(util::hash_collection::FLAG_NO_DUMP))
+ fprintf(m_output, " %s", hashes.attribute_string().c_str()); // iterate over hash function types and print m_output their values
+ else
+ fprintf(m_output, " status=\"nodump\"");
+
+ // append a region name
+ fprintf(m_output, " region=\"%s\"", region->name);
+
+ if (!is_disk)
+ {
+ // for non-disk entries, print offset
+ fprintf(m_output, " offset=\"%x\"", ROM_GETOFFSET(rom));
+ }
+ else
+ {
+ // for disk entries, add the disk index
+ fprintf(m_output, " index=\"%x\" writable=\"%s\"", DISK_GETINDEX(rom), DISK_ISREADONLY(rom) ? "no" : "yes");
+ }
+
+ // add optional flag
+ if (ROM_ISOPTIONAL(rom))
+ fprintf(m_output, " optional=\"yes\"");
+
+ fprintf(m_output, "/>\n");
+ }
+ bios_scanned = true;
+ }
+}
+
+
+//-------------------------------------------------
+// output_sample - print a list of all
+// samples referenced by a game_driver
+//-------------------------------------------------
+
+void info_xml_creator::output_sample(device_t &device)
+{
+ // iterate over sample devices
+ for (samples_device &samples : samples_device_iterator(device))
+ {
+ samples_iterator iter(samples);
+ std::unordered_set<std::string> already_printed;
+ for (const char *samplename = iter.first(); samplename != nullptr; samplename = iter.next())
+ {
+ // filter out duplicates
+ if (!already_printed.insert(samplename).second)
+ continue;
+
+ // output the sample name
+ fprintf(m_output, "\t\t<sample name=\"%s\"/>\n", util::xml::normalize_string(samplename));
+ }
+ }
+}
+
+
+/*-------------------------------------------------
+ output_chips - print a list of CPU and
+ sound chips used by a game
+-------------------------------------------------*/
+
+void info_xml_creator::output_chips(device_t &device, const char *root_tag)
+{
+ // iterate over executable devices
+ for (device_execute_interface &exec : execute_interface_iterator(device))
+ {
+ if (strcmp(exec.device().tag(), device.tag()))
+ {
+ std::string newtag(exec.device().tag()), oldtag(":");
+ newtag = newtag.substr(newtag.find(oldtag.append(root_tag)) + oldtag.length());
+
+ fprintf(m_output, "\t\t<chip");
+ fprintf(m_output, " type=\"cpu\"");
+ fprintf(m_output, " tag=\"%s\"", util::xml::normalize_string(newtag.c_str()));
+ fprintf(m_output, " name=\"%s\"", util::xml::normalize_string(exec.device().name()));
+ fprintf(m_output, " clock=\"%d\"", exec.device().clock());
+ fprintf(m_output, "/>\n");
+ }
+ }
+
+ // iterate over sound devices
+ for (device_sound_interface &sound : sound_interface_iterator(device))
+ {
+ if (strcmp(sound.device().tag(), device.tag()) != 0 && sound.issound())
+ {
+ std::string newtag(sound.device().tag()), oldtag(":");
+ newtag = newtag.substr(newtag.find(oldtag.append(root_tag)) + oldtag.length());
+
+ fprintf(m_output, "\t\t<chip");
+ fprintf(m_output, " type=\"audio\"");
+ fprintf(m_output, " tag=\"%s\"", util::xml::normalize_string(newtag.c_str()));
+ fprintf(m_output, " name=\"%s\"", util::xml::normalize_string(sound.device().name()));
+ if (sound.device().clock() != 0)
+ fprintf(m_output, " clock=\"%d\"", sound.device().clock());
+ fprintf(m_output, "/>\n");
+ }
+ }
+}
+
+
+//-------------------------------------------------
+// output_display - print a list of all the
+// displays
+//-------------------------------------------------
+
+void info_xml_creator::output_display(device_t &device, machine_flags::type const *flags, const char *root_tag)
+{
+ // iterate over screens
+ for (const screen_device &screendev : screen_device_iterator(device))
+ {
+ if (strcmp(screendev.tag(), device.tag()))
+ {
+ std::string newtag(screendev.tag()), oldtag(":");
+ newtag = newtag.substr(newtag.find(oldtag.append(root_tag)) + oldtag.length());
+
+ fprintf(m_output, "\t\t<display tag=\"%s\"", util::xml::normalize_string(newtag.c_str()));
+
+ switch (screendev.screen_type())
+ {
+ case SCREEN_TYPE_RASTER: fprintf(m_output, " type=\"raster\""); break;
+ case SCREEN_TYPE_VECTOR: fprintf(m_output, " type=\"vector\""); break;
+ case SCREEN_TYPE_LCD: fprintf(m_output, " type=\"lcd\""); break;
+ case SCREEN_TYPE_SVG: fprintf(m_output, " type=\"svg\""); break;
+ default: fprintf(m_output, " type=\"unknown\""); break;
+ }
+
+ // output the orientation as a string
+ switch (screendev.orientation())
+ {
+ case ORIENTATION_FLIP_X:
+ fprintf(m_output, " rotate=\"0\" flipx=\"yes\"");
+ break;
+ case ORIENTATION_FLIP_Y:
+ fprintf(m_output, " rotate=\"180\" flipx=\"yes\"");
+ break;
+ case ORIENTATION_FLIP_X|ORIENTATION_FLIP_Y:
+ fprintf(m_output, " rotate=\"180\"");
+ break;
+ case ORIENTATION_SWAP_XY:
+ fprintf(m_output, " rotate=\"90\" flipx=\"yes\"");
+ break;
+ case ORIENTATION_SWAP_XY|ORIENTATION_FLIP_X:
+ fprintf(m_output, " rotate=\"90\"");
+ break;
+ case ORIENTATION_SWAP_XY|ORIENTATION_FLIP_Y:
+ fprintf(m_output, " rotate=\"270\"");
+ break;
+ case ORIENTATION_SWAP_XY|ORIENTATION_FLIP_X|ORIENTATION_FLIP_Y:
+ fprintf(m_output, " rotate=\"270\" flipx=\"yes\"");
+ break;
+ default:
+ fprintf(m_output, " rotate=\"0\"");
+ break;
+ }
+
+ // output width and height only for games that are not vector
+ if (screendev.screen_type() != SCREEN_TYPE_VECTOR)
+ {
+ const rectangle &visarea = screendev.visible_area();
+ fprintf(m_output, " width=\"%d\"", visarea.width());
+ fprintf(m_output, " height=\"%d\"", visarea.height());
+ }
+
+ // output refresh rate
+ fprintf(m_output, " refresh=\"%f\"", ATTOSECONDS_TO_HZ(screendev.refresh_attoseconds()));
+
+ // output raw video parameters only for games that are not vector
+ // and had raw parameters specified
+ if (screendev.screen_type() != SCREEN_TYPE_VECTOR && !screendev.oldstyle_vblank_supplied())
+ {
+ int pixclock = screendev.width() * screendev.height() * ATTOSECONDS_TO_HZ(screendev.refresh_attoseconds());
+
+ fprintf(m_output, " pixclock=\"%d\"", pixclock);
+ fprintf(m_output, " htotal=\"%d\"", screendev.width());
+ fprintf(m_output, " hbend=\"%d\"", screendev.visible_area().min_x);
+ fprintf(m_output, " hbstart=\"%d\"", screendev.visible_area().max_x+1);
+ fprintf(m_output, " vtotal=\"%d\"", screendev.height());
+ fprintf(m_output, " vbend=\"%d\"", screendev.visible_area().min_y);
+ fprintf(m_output, " vbstart=\"%d\"", screendev.visible_area().max_y+1);
+ }
+ fprintf(m_output, " />\n");
+ }
+ }
+}
+
+
+//-------------------------------------------------
+// output_sound - print a list of all the
+// speakers
+//------------------------------------------------
+
+void info_xml_creator::output_sound(device_t &device)
+{
+ speaker_device_iterator spkiter(device);
+ int speakers = spkiter.count();
+
+ // if we have no sound, zero m_output the speaker count
+ sound_interface_iterator snditer(device);
+ if (snditer.first() == nullptr)
+ speakers = 0;
+
+ fprintf(m_output, "\t\t<sound channels=\"%d\"/>\n", speakers);
+}
+
+
+//-------------------------------------------------
+// output_ioport_condition - print condition
+// required to use I/O port field/setting
+//-------------------------------------------------
+
+void info_xml_creator::output_ioport_condition(const ioport_condition &condition, unsigned indent)
+{
+ for (unsigned i = 0; indent > i; ++i)
+ fprintf(m_output, "\t");
+
+ char const *rel(nullptr);
+ switch (condition.condition())
+ {
+ case ioport_condition::ALWAYS: throw false;
+ case ioport_condition::EQUALS: rel = "eq"; break;
+ case ioport_condition::NOTEQUALS: rel = "ne"; break;
+ case ioport_condition::GREATERTHAN: rel = "gt"; break;
+ case ioport_condition::NOTGREATERTHAN: rel = "le"; break;
+ case ioport_condition::LESSTHAN: rel = "lt"; break;
+ case ioport_condition::NOTLESSTHAN: rel = "ge"; break;
+ }
+
+ fprintf(m_output,"<condition tag=\"%s\" mask=\"%u\" relation=\"%s\" value=\"%u\"/>\n", util::xml::normalize_string(condition.tag()), condition.mask(), rel, condition.value());
+}
+
+//-------------------------------------------------
+// output_input - print a summary of a game's
+// input
+//-------------------------------------------------
+
+void info_xml_creator::output_input(const ioport_list &portlist)
+{
+ // enumerated list of control types
+ // NOTE: the order is chosen so that 'spare' button inputs are assigned to the
+ // most-likely-correct input device when info is output (you can think of it as
+ // a sort of likelihood order of having buttons)
+ enum
+ {
+ CTRL_DIGITAL_BUTTONS,
+ CTRL_DIGITAL_JOYSTICK,
+ CTRL_ANALOG_JOYSTICK,
+ CTRL_ANALOG_LIGHTGUN,
+ CTRL_ANALOG_DIAL,
+ CTRL_ANALOG_POSITIONAL,
+ CTRL_ANALOG_TRACKBALL,
+ CTRL_ANALOG_MOUSE,
+ CTRL_ANALOG_PADDLE,
+ CTRL_ANALOG_PEDAL,
+ CTRL_DIGITAL_KEYPAD,
+ CTRL_DIGITAL_KEYBOARD,
+ CTRL_DIGITAL_MAHJONG,
+ CTRL_DIGITAL_HANAFUDA,
+ CTRL_DIGITAL_GAMBLING,
+ CTRL_COUNT
+ };
+
+ enum
+ {
+ CTRL_P1,
+ CTRL_P2,
+ CTRL_P3,
+ CTRL_P4,
+ CTRL_P5,
+ CTRL_P6,
+ CTRL_P7,
+ CTRL_P8,
+ CTRL_P9,
+ CTRL_P10,
+ CTRL_PCOUNT
+ };
+
+ // directions
+ const uint8_t DIR_UP = 0x01;
+ const uint8_t DIR_DOWN = 0x02;
+ const uint8_t DIR_LEFT = 0x04;
+ const uint8_t DIR_RIGHT = 0x08;
+
+ // initialize the list of control types
+ struct
+ {
+ const char * type; // general type of input
+ int player; // player which the input belongs to
+ int nbuttons; // total number of buttons
+ int reqbuttons; // total number of non-optional buttons
+ int maxbuttons; // max index of buttons (using IPT_BUTTONn) [probably to be removed soonish]
+ int ways; // directions for joystick
+ bool analog; // is analog input?
+ uint8_t helper[3]; // for dual joysticks [possibly to be removed soonish]
+ int32_t min; // analog minimum value
+ int32_t max; // analog maximum value
+ int32_t sensitivity; // default analog sensitivity
+ int32_t keydelta; // default analog keydelta
+ bool reverse; // default analog reverse setting
+ } control_info[CTRL_COUNT * CTRL_PCOUNT];
+
+ memset(&control_info, 0, sizeof(control_info));
+
+ // tracking info as we iterate
+ int nplayer = 0;
+ int ncoin = 0;
+ bool service = false;
+ bool tilt = false;
+
+ // iterate over the ports
+ for (auto &port : portlist)
+ {
+ int ctrl_type = CTRL_DIGITAL_BUTTONS;
+ bool ctrl_analog = false;
+ for (ioport_field &field : port.second->fields())
+ {
+ // track the highest player number
+ if (nplayer < field.player() + 1)
+ nplayer = field.player() + 1;
+
+ // switch off of the type
+ switch (field.type())
+ {
+ // map joysticks
+ case IPT_JOYSTICK_UP:
+ ctrl_type = CTRL_DIGITAL_JOYSTICK;
+ control_info[field.player() * CTRL_COUNT + ctrl_type].type = "joy";
+ control_info[field.player() * CTRL_COUNT + ctrl_type].player = field.player() + 1;
+ control_info[field.player() * CTRL_COUNT + ctrl_type].ways = field.way();
+ control_info[field.player() * CTRL_COUNT + ctrl_type].helper[0] |= DIR_UP;
+ break;
+ case IPT_JOYSTICK_DOWN:
+ ctrl_type = CTRL_DIGITAL_JOYSTICK;
+ control_info[field.player() * CTRL_COUNT + ctrl_type].type = "joy";
+ control_info[field.player() * CTRL_COUNT + ctrl_type].player = field.player() + 1;
+ control_info[field.player() * CTRL_COUNT + ctrl_type].ways = field.way();
+ control_info[field.player() * CTRL_COUNT + ctrl_type].helper[0] |= DIR_DOWN;
+ break;
+ case IPT_JOYSTICK_LEFT:
+ ctrl_type = CTRL_DIGITAL_JOYSTICK;
+ control_info[field.player() * CTRL_COUNT + ctrl_type].type = "joy";
+ control_info[field.player() * CTRL_COUNT + ctrl_type].player = field.player() + 1;
+ control_info[field.player() * CTRL_COUNT + ctrl_type].ways = field.way();
+ control_info[field.player() * CTRL_COUNT + ctrl_type].helper[0] |= DIR_LEFT;
+ break;
+ case IPT_JOYSTICK_RIGHT:
+ ctrl_type = CTRL_DIGITAL_JOYSTICK;
+ control_info[field.player() * CTRL_COUNT + ctrl_type].type = "joy";
+ control_info[field.player() * CTRL_COUNT + ctrl_type].player = field.player() + 1;
+ control_info[field.player() * CTRL_COUNT + ctrl_type].ways = field.way();
+ control_info[field.player() * CTRL_COUNT + ctrl_type].helper[0] |= DIR_RIGHT;
+ break;
+
+ case IPT_JOYSTICKLEFT_UP:
+ ctrl_type = CTRL_DIGITAL_JOYSTICK;
+ control_info[field.player() * CTRL_COUNT + ctrl_type].type = "joy";
+ control_info[field.player() * CTRL_COUNT + ctrl_type].player = field.player() + 1;
+ control_info[field.player() * CTRL_COUNT + ctrl_type].ways = field.way();
+ control_info[field.player() * CTRL_COUNT + ctrl_type].helper[1] |= DIR_UP;
+ break;
+ case IPT_JOYSTICKLEFT_DOWN:
+ ctrl_type = CTRL_DIGITAL_JOYSTICK;
+ control_info[field.player() * CTRL_COUNT + ctrl_type].type = "joy";
+ control_info[field.player() * CTRL_COUNT + ctrl_type].player = field.player() + 1;
+ control_info[field.player() * CTRL_COUNT + ctrl_type].ways = field.way();
+ control_info[field.player() * CTRL_COUNT + ctrl_type].helper[1] |= DIR_DOWN;
+ break;
+ case IPT_JOYSTICKLEFT_LEFT:
+ ctrl_type = CTRL_DIGITAL_JOYSTICK;
+ control_info[field.player() * CTRL_COUNT + ctrl_type].type = "joy";
+ control_info[field.player() * CTRL_COUNT + ctrl_type].player = field.player() + 1;
+ control_info[field.player() * CTRL_COUNT + ctrl_type].ways = field.way();
+ control_info[field.player() * CTRL_COUNT + ctrl_type].helper[1] |= DIR_LEFT;
+ break;
+ case IPT_JOYSTICKLEFT_RIGHT:
+ ctrl_type = CTRL_DIGITAL_JOYSTICK;
+ control_info[field.player() * CTRL_COUNT + ctrl_type].type = "joy";
+ control_info[field.player() * CTRL_COUNT + ctrl_type].player = field.player() + 1;
+ control_info[field.player() * CTRL_COUNT + ctrl_type].ways = field.way();
+ control_info[field.player() * CTRL_COUNT + ctrl_type].helper[1] |= DIR_RIGHT;
+ break;
+
+ case IPT_JOYSTICKRIGHT_UP:
+ ctrl_type = CTRL_DIGITAL_JOYSTICK;
+ control_info[field.player() * CTRL_COUNT + ctrl_type].type = "joy";
+ control_info[field.player() * CTRL_COUNT + ctrl_type].player = field.player() + 1;
+ control_info[field.player() * CTRL_COUNT + ctrl_type].ways = field.way();
+ control_info[field.player() * CTRL_COUNT + ctrl_type].helper[2] |= DIR_UP;
+ break;
+ case IPT_JOYSTICKRIGHT_DOWN:
+ ctrl_type = CTRL_DIGITAL_JOYSTICK;
+ control_info[field.player() * CTRL_COUNT + ctrl_type].type = "joy";
+ control_info[field.player() * CTRL_COUNT + ctrl_type].player = field.player() + 1;
+ control_info[field.player() * CTRL_COUNT + ctrl_type].ways = field.way();
+ control_info[field.player() * CTRL_COUNT + ctrl_type].helper[2] |= DIR_DOWN;
+ break;
+ case IPT_JOYSTICKRIGHT_LEFT:
+ ctrl_type = CTRL_DIGITAL_JOYSTICK;
+ control_info[field.player() * CTRL_COUNT + ctrl_type].type = "joy";
+ control_info[field.player() * CTRL_COUNT + ctrl_type].player = field.player() + 1;
+ control_info[field.player() * CTRL_COUNT + ctrl_type].ways = field.way();
+ control_info[field.player() * CTRL_COUNT + ctrl_type].helper[2] |= DIR_LEFT;
+ break;
+ case IPT_JOYSTICKRIGHT_RIGHT:
+ ctrl_type = CTRL_DIGITAL_JOYSTICK;
+ control_info[field.player() * CTRL_COUNT + ctrl_type].type = "joy";
+ control_info[field.player() * CTRL_COUNT + ctrl_type].player = field.player() + 1;
+ control_info[field.player() * CTRL_COUNT + ctrl_type].ways = field.way();
+ control_info[field.player() * CTRL_COUNT + ctrl_type].helper[2] |= DIR_RIGHT;
+ break;
+
+ // map analog inputs
+ case IPT_AD_STICK_X:
+ case IPT_AD_STICK_Y:
+ case IPT_AD_STICK_Z:
+ ctrl_analog = true;
+ ctrl_type = CTRL_ANALOG_JOYSTICK;
+ control_info[field.player() * CTRL_COUNT + ctrl_type].type = "stick";
+ control_info[field.player() * CTRL_COUNT + ctrl_type].player = field.player() + 1;
+ control_info[field.player() * CTRL_COUNT + ctrl_type].analog = true;
+ break;
+
+ case IPT_PADDLE:
+ case IPT_PADDLE_V:
+ ctrl_analog = true;
+ ctrl_type = CTRL_ANALOG_PADDLE;
+ control_info[field.player() * CTRL_COUNT + ctrl_type].type = "paddle";
+ control_info[field.player() * CTRL_COUNT + ctrl_type].player = field.player() + 1;
+ control_info[field.player() * CTRL_COUNT + ctrl_type].analog = true;
+ break;
+
+ case IPT_PEDAL:
+ case IPT_PEDAL2:
+ case IPT_PEDAL3:
+ ctrl_analog = true;
+ ctrl_type = CTRL_ANALOG_PEDAL;
+ control_info[field.player() * CTRL_COUNT + ctrl_type].type = "pedal";
+ control_info[field.player() * CTRL_COUNT + ctrl_type].player = field.player() + 1;
+ control_info[field.player() * CTRL_COUNT + ctrl_type].analog = true;
+ break;
+
+ case IPT_LIGHTGUN_X:
+ case IPT_LIGHTGUN_Y:
+ ctrl_analog = true;
+ ctrl_type = CTRL_ANALOG_LIGHTGUN;
+ control_info[field.player() * CTRL_COUNT + ctrl_type].type = "lightgun";
+ control_info[field.player() * CTRL_COUNT + ctrl_type].player = field.player() + 1;
+ control_info[field.player() * CTRL_COUNT + ctrl_type].analog = true;
+ break;
+
+ case IPT_POSITIONAL:
+ case IPT_POSITIONAL_V:
+ ctrl_analog = true;
+ ctrl_type = CTRL_ANALOG_POSITIONAL;
+ control_info[field.player() * CTRL_COUNT + ctrl_type].type = "positional";
+ control_info[field.player() * CTRL_COUNT + ctrl_type].player = field.player() + 1;
+ control_info[field.player() * CTRL_COUNT + ctrl_type].analog = true;
+ break;
+
+ case IPT_DIAL:
+ case IPT_DIAL_V:
+ ctrl_analog = true;
+ ctrl_type = CTRL_ANALOG_DIAL;
+ control_info[field.player() * CTRL_COUNT + ctrl_type].type = "dial";
+ control_info[field.player() * CTRL_COUNT + ctrl_type].player = field.player() + 1;
+ control_info[field.player() * CTRL_COUNT + ctrl_type].analog = true;
+ break;
+
+ case IPT_TRACKBALL_X:
+ case IPT_TRACKBALL_Y:
+ ctrl_analog = true;
+ ctrl_type = CTRL_ANALOG_TRACKBALL;
+ control_info[field.player() * CTRL_COUNT + ctrl_type].type = "trackball";
+ control_info[field.player() * CTRL_COUNT + ctrl_type].player = field.player() + 1;
+ control_info[field.player() * CTRL_COUNT + ctrl_type].analog = true;
+ break;
+
+ case IPT_MOUSE_X:
+ case IPT_MOUSE_Y:
+ ctrl_analog = true;
+ ctrl_type = CTRL_ANALOG_MOUSE;
+ control_info[field.player() * CTRL_COUNT + ctrl_type].type = "mouse";
+ control_info[field.player() * CTRL_COUNT + ctrl_type].player = field.player() + 1;
+ control_info[field.player() * CTRL_COUNT + ctrl_type].analog = true;
+ break;
+
+ // map buttons
+ case IPT_BUTTON1:
+ case IPT_BUTTON2:
+ case IPT_BUTTON3:
+ case IPT_BUTTON4:
+ case IPT_BUTTON5:
+ case IPT_BUTTON6:
+ case IPT_BUTTON7:
+ case IPT_BUTTON8:
+ case IPT_BUTTON9:
+ case IPT_BUTTON10:
+ case IPT_BUTTON11:
+ case IPT_BUTTON12:
+ case IPT_BUTTON13:
+ case IPT_BUTTON14:
+ case IPT_BUTTON15:
+ case IPT_BUTTON16:
+ ctrl_analog = false;
+ if (control_info[field.player() * CTRL_COUNT + ctrl_type].type == nullptr)
+ {
+ control_info[field.player() * CTRL_COUNT + ctrl_type].type = "only_buttons";
+ control_info[field.player() * CTRL_COUNT + ctrl_type].player = field.player() + 1;
+ control_info[field.player() * CTRL_COUNT + ctrl_type].analog = false;
+ }
+ control_info[field.player() * CTRL_COUNT + ctrl_type].maxbuttons = std::max(control_info[field.player() * CTRL_COUNT + ctrl_type].maxbuttons, field.type() - IPT_BUTTON1 + 1);
+ control_info[field.player() * CTRL_COUNT + ctrl_type].nbuttons++;
+ if (!field.optional())
+ control_info[field.player() * CTRL_COUNT + ctrl_type].reqbuttons++;
+ break;
+
+ // track maximum coin index
+ case IPT_COIN1:
+ case IPT_COIN2:
+ case IPT_COIN3:
+ case IPT_COIN4:
+ case IPT_COIN5:
+ case IPT_COIN6:
+ case IPT_COIN7:
+ case IPT_COIN8:
+ case IPT_COIN9:
+ case IPT_COIN10:
+ case IPT_COIN11:
+ case IPT_COIN12:
+ ncoin = std::max(ncoin, field.type() - IPT_COIN1 + 1);
+ break;
+
+ // track presence of keypads and keyboards
+ case IPT_KEYPAD:
+ ctrl_type = CTRL_DIGITAL_KEYPAD;
+ control_info[field.player() * CTRL_COUNT + ctrl_type].type = "keypad";
+ control_info[field.player() * CTRL_COUNT + ctrl_type].player = field.player() + 1;
+ control_info[field.player() * CTRL_COUNT + ctrl_type].nbuttons++;
+ if (!field.optional())
+ control_info[field.player() * CTRL_COUNT + ctrl_type].reqbuttons++;
+ break;
+
+ case IPT_KEYBOARD:
+ ctrl_type = CTRL_DIGITAL_KEYBOARD;
+ control_info[field.player() * CTRL_COUNT + ctrl_type].type = "keyboard";
+ control_info[field.player() * CTRL_COUNT + ctrl_type].player = field.player() + 1;
+ control_info[field.player() * CTRL_COUNT + ctrl_type].nbuttons++;
+ if (!field.optional())
+ control_info[field.player() * CTRL_COUNT + ctrl_type].reqbuttons++;
+ break;
+
+ // additional types
+ case IPT_SERVICE:
+ service = true;
+ break;
+
+ case IPT_TILT:
+ tilt = true;
+ break;
+
+ default:
+ if (field.type() > IPT_MAHJONG_FIRST && field.type() < IPT_MAHJONG_LAST)
+ {
+ ctrl_type = CTRL_DIGITAL_MAHJONG;
+ control_info[field.player() * CTRL_COUNT + ctrl_type].type = "mahjong";
+ control_info[field.player() * CTRL_COUNT + ctrl_type].player = field.player() + 1;
+ control_info[field.player() * CTRL_COUNT + ctrl_type].nbuttons++;
+ if (!field.optional())
+ control_info[field.player() * CTRL_COUNT + ctrl_type].reqbuttons++;
+ }
+ else if (field.type() > IPT_HANAFUDA_FIRST && field.type() < IPT_HANAFUDA_LAST)
+ {
+ ctrl_type = CTRL_DIGITAL_HANAFUDA;
+ control_info[field.player() * CTRL_COUNT + ctrl_type].type = "hanafuda";
+ control_info[field.player() * CTRL_COUNT + ctrl_type].player = field.player() + 1;
+ control_info[field.player() * CTRL_COUNT + ctrl_type].nbuttons++;
+ if (!field.optional())
+ control_info[field.player() * CTRL_COUNT + ctrl_type].reqbuttons++;
+ }
+ else if (field.type() > IPT_GAMBLING_FIRST && field.type() < IPT_GAMBLING_LAST)
+ {
+ ctrl_type = CTRL_DIGITAL_GAMBLING;
+ control_info[field.player() * CTRL_COUNT + ctrl_type].type = "gambling";
+ control_info[field.player() * CTRL_COUNT + ctrl_type].player = field.player() + 1;
+ control_info[field.player() * CTRL_COUNT + ctrl_type].nbuttons++;
+ if (!field.optional())
+ control_info[field.player() * CTRL_COUNT + ctrl_type].reqbuttons++;
+ }
+ break;
+ }
+
+ if (ctrl_analog)
+ {
+ // get the analog stats
+ if (field.minval() != 0)
+ control_info[field.player() * CTRL_COUNT + ctrl_type].min = field.minval();
+ if (field.maxval() != 0)
+ control_info[field.player() * CTRL_COUNT + ctrl_type].max = field.maxval();
+ if (field.sensitivity() != 0)
+ control_info[field.player() * CTRL_COUNT + ctrl_type].sensitivity = field.sensitivity();
+ if (field.delta() != 0)
+ control_info[field.player() * CTRL_COUNT + ctrl_type].keydelta = field.delta();
+ if (field.analog_reverse() != 0)
+ control_info[field.player() * CTRL_COUNT + ctrl_type].reverse = true;
+ }
+ }
+ }
+
+ // Clean-up those entries, if any, where buttons were defined in a separate port than the actual controller they belong to.
+ // This is quite often the case, especially for arcades where controls can be easily mapped to separate input ports on PCB.
+ // If such situation would only happen for joystick, it would be possible to work it around by initializing differently
+ // ctrl_type above, but it is quite common among analog inputs as well (for instance, this is the tipical situation
+ // for lightguns) and therefore we really need this separate loop.
+ for (int i = 0; i < CTRL_PCOUNT; i++)
+ {
+ bool fix_done = false;
+ for (int j = 1; j < CTRL_COUNT; j++)
+ if (control_info[i * CTRL_COUNT].type != nullptr && control_info[i * CTRL_COUNT + j].type != nullptr && !fix_done)
+ {
+ control_info[i * CTRL_COUNT + j].nbuttons += control_info[i * CTRL_COUNT].nbuttons;
+ control_info[i * CTRL_COUNT + j].reqbuttons += control_info[i * CTRL_COUNT].reqbuttons;
+ control_info[i * CTRL_COUNT + j].maxbuttons = std::max(control_info[i * CTRL_COUNT + j].maxbuttons, control_info[i * CTRL_COUNT].maxbuttons);
+
+ memset(&control_info[i * CTRL_COUNT], 0, sizeof(control_info[0]));
+ fix_done = true;
+ }
+ }
+
+ // Output the input info
+ // First basic info
+ fprintf(m_output, "\t\t<input");
+ fprintf(m_output, " players=\"%d\"", nplayer);
+ if (ncoin != 0)
+ fprintf(m_output, " coins=\"%d\"", ncoin);
+ if (service)
+ fprintf(m_output, " service=\"yes\"");
+ if (tilt)
+ fprintf(m_output, " tilt=\"yes\"");
+ fprintf(m_output, ">\n");
+
+ // Then controller specific ones
+ for (auto & elem : control_info)
+ if (elem.type != nullptr)
+ {
+ //printf("type %s - player %d - buttons %d\n", elem.type, elem.player, elem.nbuttons);
+ if (elem.analog)
+ {
+ fprintf(m_output, "\t\t\t<control type=\"%s\"", util::xml::normalize_string(elem.type));
+ if (nplayer > 1)
+ fprintf(m_output, " player=\"%d\"", elem.player);
+ if (elem.nbuttons > 0)
+ {
+ fprintf(m_output, " buttons=\"%d\"", strcmp(elem.type, "stick") ? elem.nbuttons : elem.maxbuttons);
+ if (elem.reqbuttons < elem.nbuttons)
+ fprintf(m_output, " reqbuttons=\"%d\"", elem.reqbuttons);
+ }
+ if (elem.min != 0 || elem.max != 0)
+ fprintf(m_output, " minimum=\"%d\" maximum=\"%d\"", elem.min, elem.max);
+ if (elem.sensitivity != 0)
+ fprintf(m_output, " sensitivity=\"%d\"", elem.sensitivity);
+ if (elem.keydelta != 0)
+ fprintf(m_output, " keydelta=\"%d\"", elem.keydelta);
+ if (elem.reverse)
+ fprintf(m_output, " reverse=\"yes\"");
+
+ fprintf(m_output, "/>\n");
+ }
+ else
+ {
+ if (elem.helper[1] == 0 && elem.helper[2] != 0) { elem.helper[1] = elem.helper[2]; elem.helper[2] = 0; }
+ if (elem.helper[0] == 0 && elem.helper[1] != 0) { elem.helper[0] = elem.helper[1]; elem.helper[1] = 0; }
+ if (elem.helper[1] == 0 && elem.helper[2] != 0) { elem.helper[1] = elem.helper[2]; elem.helper[2] = 0; }
+ const char *joys = (elem.helper[2] != 0) ? "triple" : (elem.helper[1] != 0) ? "double" : "";
+ fprintf(m_output, "\t\t\t<control type=\"%s%s\"", joys, util::xml::normalize_string(elem.type));
+ if (nplayer > 1)
+ fprintf(m_output, " player=\"%d\"", elem.player);
+ if (elem.nbuttons > 0)
+ {
+ fprintf(m_output, " buttons=\"%d\"", strcmp(elem.type, "joy") ? elem.nbuttons : elem.maxbuttons);
+ if (elem.reqbuttons < elem.nbuttons)
+ fprintf(m_output, " reqbuttons=\"%d\"", elem.reqbuttons);
+ }
+ for (int lp = 0; lp < 3 && elem.helper[lp] != 0; lp++)
+ {
+ const char *plural = (lp==2) ? "3" : (lp==1) ? "2" : "";
+ const char *ways;
+ std::string helper;
+ switch (elem.helper[lp] & (DIR_UP | DIR_DOWN | DIR_LEFT | DIR_RIGHT))
+ {
+ case DIR_UP | DIR_DOWN | DIR_LEFT | DIR_RIGHT:
+ helper = string_format("%d", (elem.ways == 0) ? 8 : elem.ways);
+ ways = helper.c_str();
+ break;
+ case DIR_LEFT | DIR_RIGHT:
+ ways = "2";
+ break;
+ case DIR_UP | DIR_DOWN:
+ ways = "vertical2";
+ break;
+ case DIR_UP:
+ case DIR_DOWN:
+ case DIR_LEFT:
+ case DIR_RIGHT:
+ ways = "1";
+ break;
+ case DIR_UP | DIR_DOWN | DIR_LEFT:
+ case DIR_UP | DIR_DOWN | DIR_RIGHT:
+ case DIR_UP | DIR_LEFT | DIR_RIGHT:
+ case DIR_DOWN | DIR_LEFT | DIR_RIGHT:
+ ways = (elem.ways == 4) ? "3 (half4)" : "5 (half8)";
+ break;
+ default:
+ ways = "strange2";
+ break;
+ }
+ fprintf(m_output, " ways%s=\"%s\"", plural, ways);
+ }
+ fprintf(m_output, "/>\n");
+ }
+ }
+
+ fprintf(m_output, "\t\t</input>\n");
+}
+
+
+//-------------------------------------------------
+// output_switches - print the configurations or
+// DIP switch settings
+//-------------------------------------------------
+
+void info_xml_creator::output_switches(const ioport_list &portlist, const char *root_tag, int type, const char *outertag, const char *loctag, const char *innertag)
+{
+ // iterate looking for DIP switches
+ for (auto &port : portlist)
+ for (ioport_field const &field : port.second->fields())
+ if (field.type() == type)
+ {
+ std::string newtag(port.second->tag()), oldtag(":");
+ newtag = newtag.substr(newtag.find(oldtag.append(root_tag)) + oldtag.length());
+
+ // output the switch name information
+ std::string const normalized_field_name(util::xml::normalize_string(field.name()));
+ std::string const normalized_newtag(util::xml::normalize_string(newtag.c_str()));
+ fprintf(m_output, "\t\t<%s name=\"%s\" tag=\"%s\" mask=\"%u\">\n", outertag, normalized_field_name.c_str(), normalized_newtag.c_str(), field.mask());
+ if (!field.condition().none())
+ output_ioport_condition(field.condition(), 3);
+
+ // loop over locations
+ for (ioport_diplocation const &diploc : field.diplocations())
+ {
+ fprintf(m_output, "\t\t\t<%s name=\"%s\" number=\"%u\"", loctag, util::xml::normalize_string(diploc.name()), diploc.number());
+ if (diploc.inverted())
+ fprintf(m_output, " inverted=\"yes\"");
+ fprintf(m_output, "/>\n");
+ }
+
+ // loop over settings
+ for (ioport_setting const &setting : field.settings())
+ {
+ fprintf(m_output, "\t\t\t<%s name=\"%s\" value=\"%u\"", innertag, util::xml::normalize_string(setting.name()), setting.value());
+ if (setting.value() == field.defvalue())
+ fprintf(m_output, " default=\"yes\"");
+ if (setting.condition().none())
+ {
+ fprintf(m_output, "/>\n");
+ }
+ else
+ {
+ fprintf(m_output, ">\n");
+ output_ioport_condition(setting.condition(), 4);
+ fprintf(m_output, "\t\t\t</%s>\n", innertag);
+ }
+ }
+
+ // terminate the switch entry
+ fprintf(m_output, "\t\t</%s>\n", outertag);
+ }
+}
+
+//-------------------------------------------------
+// output_ports - print the structure of input
+// ports in the driver
+//-------------------------------------------------
+void info_xml_creator::output_ports(const ioport_list &portlist)
+{
+ // cycle through ports
+ for (auto &port : portlist)
+ {
+ fprintf(m_output,"\t\t<port tag=\"%s\">\n", util::xml::normalize_string(port.second->tag()));
+ for (ioport_field const &field : port.second->fields())
+ {
+ if (field.is_analog())
+ fprintf(m_output,"\t\t\t<analog mask=\"%u\"/>\n", field.mask());
+ }
+ fprintf(m_output,"\t\t</port>\n");
+ }
+
+}
+
+//-------------------------------------------------
+// output_adjusters - print the Analog
+// Adjusters for a game
+//-------------------------------------------------
+
+void info_xml_creator::output_adjusters(const ioport_list &portlist)
+{
+ // iterate looking for Adjusters
+ for (auto &port : portlist)
+ for (ioport_field const &field : port.second->fields())
+ if (field.type() == IPT_ADJUSTER)
+ {
+ fprintf(m_output, "\t\t<adjuster name=\"%s\" default=\"%d\"/>\n", util::xml::normalize_string(field.name()), field.defvalue());
+ }
+}
+
+
+//-------------------------------------------------
+// output_driver - print driver status
+//-------------------------------------------------
+
+void info_xml_creator::output_driver(game_driver const &driver, device_t::feature_type unemulated, device_t::feature_type imperfect)
+{
+ fprintf(m_output, "\t\t<driver");
+
+ /*
+ The status entry is an hint for frontend authors to select working
+ and not working games without the need to know all the other status
+ entries. Games marked as status=good are perfectly emulated, games
+ marked as status=imperfect are emulated with only some minor issues,
+ games marked as status=preliminary don't work or have major
+ emulation problems.
+ */
+
+ u32 const flags = driver.flags;
+ bool const machine_preliminary(flags & (machine_flags::NOT_WORKING | machine_flags::MECHANICAL));
+ bool const unemulated_preliminary(unemulated & (device_t::feature::PALETTE | device_t::feature::GRAPHICS | device_t::feature::SOUND | device_t::feature::KEYBOARD));
+ bool const imperfect_preliminary((unemulated | imperfect) & device_t::feature::PROTECTION);
+
+ if (machine_preliminary || unemulated_preliminary || imperfect_preliminary)
+ fprintf(m_output, " status=\"preliminary\"");
+ else if (imperfect)
+ fprintf(m_output, " status=\"imperfect\"");
+ else
+ fprintf(m_output, " status=\"good\"");
+
+ if (flags & machine_flags::NOT_WORKING)
+ fprintf(m_output, " emulation=\"preliminary\"");
+ else
+ fprintf(m_output, " emulation=\"good\"");
+
+ if (flags & machine_flags::NO_COCKTAIL)
+ fprintf(m_output, " cocktail=\"preliminary\"");
+
+ if (flags & machine_flags::SUPPORTS_SAVE)
+ fprintf(m_output, " savestate=\"supported\"");
+ else
+ fprintf(m_output, " savestate=\"unsupported\"");
+
+ fprintf(m_output, "/>\n");
+}
+
+
+//-------------------------------------------------
+// output_features - print emulation features of
+//
+//-------------------------------------------------
+
+void info_xml_creator::output_features(device_type type, device_t::feature_type unemulated, device_t::feature_type imperfect)
+{
+ static constexpr std::pair<device_t::feature_type, char const *> features[] = {
+ { device_t::feature::PROTECTION, "protection" },
+ { device_t::feature::TIMING, "timing" },
+ { device_t::feature::GRAPHICS, "graphics" },
+ { device_t::feature::PALETTE, "palette" },
+ { device_t::feature::SOUND, "sound" },
+ { device_t::feature::CAPTURE, "capture" },
+ { device_t::feature::CAMERA, "camera" },
+ { device_t::feature::MICROPHONE, "microphone" },
+ { device_t::feature::CONTROLS, "controls" },
+ { device_t::feature::KEYBOARD, "keyboard" },
+ { device_t::feature::MOUSE, "mouse" },
+ { device_t::feature::MEDIA, "media" },
+ { device_t::feature::DISK, "disk" },
+ { device_t::feature::PRINTER, "printer" },
+ { device_t::feature::TAPE, "tape" },
+ { device_t::feature::PUNCH, "punch" },
+ { device_t::feature::DRUM, "drum" },
+ { device_t::feature::ROM, "rom" },
+ { device_t::feature::COMMS, "comms" },
+ { device_t::feature::LAN, "lan" },
+ { device_t::feature::WAN, "wan" } };
+
+ device_t::feature_type const flags(type.unemulated_features() | type.imperfect_features() | unemulated | imperfect);
+ for (auto const &feature : features)
+ {
+ if (flags & feature.first)
+ {
+ fprintf(m_output, "\t\t<feature type=\"%s\"", feature.second);
+ if (type.unemulated_features() & feature.first)
+ {
+ fprintf(m_output, " status=\"unemulated\"");
+ }
+ else
+ {
+ if (type.imperfect_features() & feature.first)
+ fprintf(m_output, " status=\"imperfect\"");
+ if (unemulated & feature.first)
+ fprintf(m_output, " overall=\"unemulated\"");
+ else if ((~type.imperfect_features() & imperfect) & feature.first)
+ fprintf(m_output, " overall=\"imperfect\"");
+ }
+ fprintf(m_output, "/>\n");
+ }
+ }
+}
+
+
+//-------------------------------------------------
+// output_images - prints m_output all info on
+// image devices
+//-------------------------------------------------
+
+void info_xml_creator::output_images(device_t &device, const char *root_tag)
+{
+ for (const device_image_interface &imagedev : image_interface_iterator(device))
+ {
+ if (strcmp(imagedev.device().tag(), device.tag()))
+ {
+ bool loadable = imagedev.user_loadable();
+ std::string newtag(imagedev.device().tag()), oldtag(":");
+ newtag = newtag.substr(newtag.find(oldtag.append(root_tag)) + oldtag.length());
+
+ // print m_output device type
+ fprintf(m_output, "\t\t<device type=\"%s\"", util::xml::normalize_string(imagedev.image_type_name()));
+
+ // does this device have a tag?
+ if (imagedev.device().tag())
+ fprintf(m_output, " tag=\"%s\"", util::xml::normalize_string(newtag.c_str()));
+
+ // is this device available as media switch?
+ if (!loadable)
+ fprintf(m_output, " fixed_image=\"1\"");
+
+ // is this device mandatory?
+ if (imagedev.must_be_loaded())
+ fprintf(m_output, " mandatory=\"1\"");
+
+ if (imagedev.image_interface() && imagedev.image_interface()[0])
+ fprintf(m_output, " interface=\"%s\"", util::xml::normalize_string(imagedev.image_interface()));
+
+ // close the XML tag
+ fprintf(m_output, ">\n");
+
+ if (loadable)
+ {
+ const char *name = imagedev.instance_name().c_str();
+ const char *shortname = imagedev.brief_instance_name().c_str();
+
+ fprintf(m_output, "\t\t\t<instance");
+ fprintf(m_output, " name=\"%s\"", util::xml::normalize_string(name));
+ fprintf(m_output, " briefname=\"%s\"", util::xml::normalize_string(shortname));
+ fprintf(m_output, "/>\n");
+
+ std::string extensions(imagedev.file_extensions());
+
+ char *ext = strtok((char *)extensions.c_str(), ",");
+ while (ext != nullptr)
+ {
+ fprintf(m_output, "\t\t\t<extension name=\"%s\"/>\n", util::xml::normalize_string(ext));
+ ext = strtok(nullptr, ",");
+ }
+ }
+ fprintf(m_output, "\t\t</device>\n");
+ }
+ }
+}
+
+
+//-------------------------------------------------
+// output_slots - prints all info about slots
+//-------------------------------------------------
+
+void info_xml_creator::output_slots(machine_config &config, device_t &device, const char *root_tag, device_type_set *devtypes)
+{
+ for (device_slot_interface &slot : slot_interface_iterator(device))
+ {
+ // shall we list fixed slots as non-configurable?
+ bool const listed(!slot.fixed() && strcmp(slot.device().tag(), device.tag()));
+
+ if (devtypes || listed)
+ {
+ machine_config::token const tok(config.begin_configuration(slot.device()));
+ std::string newtag(slot.device().tag()), oldtag(":");
+ newtag = newtag.substr(newtag.find(oldtag.append(root_tag)) + oldtag.length());
+
+ // print m_output device type
+ if (listed)
+ fprintf(m_output, "\t\t<slot name=\"%s\">\n", util::xml::normalize_string(newtag.c_str()));
+
+ for (auto &option : slot.option_list())
+ {
+ if (devtypes || (listed && option.second->selectable()))
+ {
+ device_t *const dev = config.device_add("_dummy", option.second->devtype(), option.second->clock());
+ if (!dev->configured())
+ dev->config_complete();
+
+ if (devtypes)
+ for (device_t &subdevice : device_iterator(*dev)) devtypes->insert(&subdevice.type());
+
+ if (listed && option.second->selectable())
+ {
+ fprintf(m_output, "\t\t\t<slotoption name=\"%s\"", util::xml::normalize_string(option.second->name()));
+ fprintf(m_output, " devname=\"%s\"", util::xml::normalize_string(dev->shortname()));
+ if (slot.default_option() != nullptr && strcmp(slot.default_option(), option.second->name())==0)
+ fprintf(m_output, " default=\"yes\"");
+ fprintf(m_output, "/>\n");
+ }
+
+ config.device_remove("_dummy");
+ }
+ }
+
+ if (listed)
+ fprintf(m_output, "\t\t</slot>\n");
+ }
+ }
+}
+
+
+//-------------------------------------------------
+// output_software_list - print the information
+// for all known software lists for this system
+//-------------------------------------------------
+
+void info_xml_creator::output_software_list(device_t &root)
+{
+ for (const software_list_device &swlist : software_list_device_iterator(root))
+ {
+ fprintf(m_output, "\t\t<softwarelist name=\"%s\" status=\"%s\"", util::xml::normalize_string(swlist.list_name().c_str()), (swlist.list_type() == SOFTWARE_LIST_ORIGINAL_SYSTEM) ? "original" : "compatible");
+ if (swlist.filter())
+ fprintf(m_output, " filter=\"%s\"", util::xml::normalize_string(swlist.filter()));
+ fprintf(m_output, "/>\n");
+ }
+}
+
+
+
+//-------------------------------------------------
+// output_ramoptions - prints m_output all RAM
+// options for this system
+//-------------------------------------------------
+
+void info_xml_creator::output_ramoptions(device_t &root)
+{
+ for (const ram_device &ram : ram_device_iterator(root, 1))
+ {
+ if (!std::strcmp(ram.tag(), ":" RAM_TAG))
+ {
+ uint32_t const defsize(ram.default_size());
+ bool havedefault(false);
+ for (ram_device::extra_option const &option : ram.extra_options())
+ {
+ if (defsize == option.second)
+ {
+ assert(!havedefault);
+ havedefault = true;
+ fprintf(m_output, "\t\t<ramoption name=\"%s\" default=\"yes\">%u</ramoption>\n", util::xml::normalize_string(option.first.c_str()), option.second);
+ }
+ else
+ {
+ fprintf(m_output, "\t\t<ramoption name=\"%s\">%u</ramoption>\n", util::xml::normalize_string(option.first.c_str()), option.second);
+ }
+ }
+ if (!havedefault)
+ fprintf(m_output, "\t\t<ramoption name=\"%s\" default=\"yes\">%u</ramoption>\n", ram.default_size_string(), defsize);
+ break;
+ }
+ }
+}
+
+
+//-------------------------------------------------
+// get_merge_name - get the rom name from a
+// parent set
+//-------------------------------------------------
+
+const char *info_xml_creator::get_merge_name(driver_enumerator &drivlist, util::hash_collection const &romhashes)
+{
+ // walk the parent chain
+ for (int clone_of = drivlist.find(drivlist.driver().parent); 0 <= clone_of; clone_of = drivlist.find(drivlist.driver(clone_of).parent))
+ {
+ // look in the parent's ROMs
+ for (romload::region const &pregion : romload::entries(drivlist.driver(clone_of).rom).get_regions())
+ {
+ for (romload::file const &prom : pregion.get_files())
+ {
+ // stop when we find a match
+ util::hash_collection const phashes(prom.get_hashdata());
+ if (!phashes.flag(util::hash_collection::FLAG_NO_DUMP) && (romhashes == phashes))
+ return prom.get_name();
+ }
+ }
+ }
+
+ return nullptr;
+}
diff --git a/docs/release/src/frontend/mame/mameopts.cpp b/docs/release/src/frontend/mame/mameopts.cpp
new file mode 100644
index 00000000000..47f1a71c595
--- /dev/null
+++ b/docs/release/src/frontend/mame/mameopts.cpp
@@ -0,0 +1,210 @@
+// license:BSD-3-Clause
+// copyright-holders:Aaron Giles
+/***************************************************************************
+
+ mameopts.cpp
+
+ Options file and command line management.
+
+***************************************************************************/
+
+#include "emu.h"
+#include "mameopts.h"
+
+#include "drivenum.h"
+#include "screen.h"
+#include "softlist_dev.h"
+#include "zippath.h"
+#include "hashfile.h"
+#include "clifront.h"
+
+#include <ctype.h>
+#include <stack>
+
+
+//-------------------------------------------------
+// parse_standard_inis - parse the standard set
+// of INI files
+//-------------------------------------------------
+
+void mame_options::parse_standard_inis(emu_options &options, std::ostream &error_stream, const game_driver *driver)
+{
+ // parse the INI file defined by the platform (e.g., "mame.ini")
+ // we do this twice so that the first file can change the INI path
+ parse_one_ini(options, emulator_info::get_configname(), OPTION_PRIORITY_MAME_INI);
+ parse_one_ini(options, emulator_info::get_configname(), OPTION_PRIORITY_MAME_INI, &error_stream);
+
+ // debug mode: parse "debug.ini" as well
+ if (options.debug())
+ parse_one_ini(options, "debug", OPTION_PRIORITY_DEBUG_INI, &error_stream);
+
+ // if we have a valid system driver, parse system-specific INI files
+ game_driver const *const cursystem = !driver ? system(options) : driver;
+ if (!cursystem)
+ return;
+
+ // parse "vertical.ini" or "horizont.ini"
+ if (cursystem->flags & ORIENTATION_SWAP_XY)
+ parse_one_ini(options, "vertical", OPTION_PRIORITY_ORIENTATION_INI, &error_stream);
+ else
+ parse_one_ini(options, "horizont", OPTION_PRIORITY_ORIENTATION_INI, &error_stream);
+
+ switch (cursystem->flags & machine_flags::MASK_TYPE)
+ {
+ case machine_flags::TYPE_ARCADE:
+ parse_one_ini(options, "arcade", OPTION_PRIORITY_SYSTYPE_INI, &error_stream);
+ break;
+ case machine_flags::TYPE_CONSOLE:
+ parse_one_ini(options ,"console", OPTION_PRIORITY_SYSTYPE_INI, &error_stream);
+ break;
+ case machine_flags::TYPE_COMPUTER:
+ parse_one_ini(options, "computer", OPTION_PRIORITY_SYSTYPE_INI, &error_stream);
+ break;
+ case machine_flags::TYPE_OTHER:
+ parse_one_ini(options, "othersys", OPTION_PRIORITY_SYSTYPE_INI, &error_stream);
+ break;
+ default:
+ break;
+ }
+
+ machine_config config(*cursystem, options);
+ for (const screen_device &device : screen_device_iterator(config.root_device()))
+ {
+ // parse "raster.ini" for raster games
+ if (device.screen_type() == SCREEN_TYPE_RASTER)
+ {
+ parse_one_ini(options, "raster", OPTION_PRIORITY_SCREEN_INI, &error_stream);
+ break;
+ }
+ // parse "vector.ini" for vector games
+ if (device.screen_type() == SCREEN_TYPE_VECTOR)
+ {
+ parse_one_ini(options, "vector", OPTION_PRIORITY_SCREEN_INI, &error_stream);
+ break;
+ }
+ // parse "lcd.ini" for lcd games
+ if (device.screen_type() == SCREEN_TYPE_LCD)
+ {
+ parse_one_ini(options, "lcd", OPTION_PRIORITY_SCREEN_INI, &error_stream);
+ break;
+ }
+ }
+
+ // next parse "source/<sourcefile>.ini"
+ std::string sourcename = core_filename_extract_base(cursystem->type.source(), true).insert(0, "source" PATH_SEPARATOR);
+ parse_one_ini(options, sourcename.c_str(), OPTION_PRIORITY_SOURCE_INI, &error_stream);
+
+ // then parse the grandparent, parent, and system-specific INIs
+ int parent = driver_list::clone(*cursystem);
+ int gparent = (parent != -1) ? driver_list::clone(parent) : -1;
+ if (gparent != -1)
+ parse_one_ini(options, driver_list::driver(gparent).name, OPTION_PRIORITY_GPARENT_INI, &error_stream);
+ if (parent != -1)
+ parse_one_ini(options, driver_list::driver(parent).name, OPTION_PRIORITY_PARENT_INI, &error_stream);
+ parse_one_ini(options, cursystem->name, OPTION_PRIORITY_DRIVER_INI, &error_stream);
+}
+
+
+//-------------------------------------------------
+// system - return a pointer to the specified
+// system driver, or nullptr if no match
+//-------------------------------------------------
+
+const game_driver *mame_options::system(const emu_options &options)
+{
+ int index = driver_list::find(core_filename_extract_base(options.system_name(), true).c_str());
+ return (index != -1) ? &driver_list::driver(index) : nullptr;
+}
+
+
+//-------------------------------------------------
+// parse_one_ini - parse a single INI file
+//-------------------------------------------------
+
+void mame_options::parse_one_ini(emu_options &options, const char *basename, int priority, std::ostream *error_stream)
+{
+ // don't parse if it has been disabled
+ if (!options.read_config())
+ return;
+
+ // open the file; if we fail, that's ok
+ emu_file file(options.ini_path(), OPEN_FLAG_READ);
+ osd_printf_verbose("Attempting load of %s.ini\n", basename);
+ osd_file::error filerr = file.open(basename, ".ini");
+ if (filerr != osd_file::error::NONE)
+ return;
+
+ // parse the file
+ osd_printf_verbose("Parsing %s.ini\n", basename);
+ try
+ {
+ options.parse_ini_file((util::core_file&)file, priority, priority < OPTION_PRIORITY_DRIVER_INI, false);
+ }
+ catch (options_exception &ex)
+ {
+ if (error_stream)
+ util::stream_format(*error_stream, "While parsing %s:\n%s\n", ex.message(), file.fullpath(), ex.message());
+ return;
+ }
+
+}
+
+
+//-------------------------------------------------
+// populate_hashpath_from_args_and_inis
+//-------------------------------------------------
+
+void mame_options::populate_hashpath_from_args_and_inis(emu_options &options, const std::vector<std::string> &args)
+{
+ // The existence of this function comes from the fact that for softlist options to be properly
+ // evaluated, we need to have the hashpath variable set. The problem is that the hashpath may
+ // be set anywhere on the command line, but also in any of the myriad INI files that we parse, some
+ // of which may be system specific (e.g. - nes.ini) or otherwise influenced by the system (e.g. - vector.ini)
+ //
+ // I think that it is terrible that we have to do a completely independent pass on the command line and every
+ // argument simply because any one of these things might be setting - hashpath.Unless we invest the effort in
+ // building some sort of "late binding" apparatus for options(e.g. - delay evaluation of softlist options until
+ // we've scoured all INIs for hashpath) that can completely straddle the command line and the INI worlds, doing
+ // this is the best that we can do IMO.
+
+ // parse the command line
+ emu_options temp_options(emu_options::option_support::GENERAL_AND_SYSTEM);
+
+ // pick up whatever changes the osd did to the default inipath
+ temp_options.set_default_value(OPTION_INIPATH, options.ini_path());
+
+ try
+ {
+ temp_options.parse_command_line(args, OPTION_PRIORITY_CMDLINE, true);
+ }
+ catch (options_exception &)
+ {
+ // Something is very long; we have bigger problems than -hashpath possibly
+ // being in never-never land. Punt and let the main code fail
+ return;
+ }
+
+ // if we have an auxillary verb, hashpath is irrelevant
+ if (!temp_options.command().empty())
+ return;
+
+ // read INI files
+ if (temp_options.read_config())
+ {
+ std::ostringstream error_stream;
+ parse_standard_inis(temp_options, error_stream);
+ }
+
+ // and fish out hashpath
+ const auto entry = temp_options.get_entry(OPTION_HASHPATH);
+ if (entry)
+ {
+ try
+ {
+ options.set_value(OPTION_HASHPATH, entry->value(), entry->priority());
+ }
+ catch (options_exception &)
+ {
+ }
+ }
+}
diff --git a/docs/release/src/frontend/mame/mameopts.h b/docs/release/src/frontend/mame/mameopts.h
new file mode 100644
index 00000000000..2b178fb2ead
--- /dev/null
+++ b/docs/release/src/frontend/mame/mameopts.h
@@ -0,0 +1,64 @@
+// license:BSD-3-Clause
+// copyright-holders:Aaron Giles
+/***************************************************************************
+
+ mameopts.h
+
+ Options file and command line management.
+
+***************************************************************************/
+
+#ifndef MAME_FRONTEND_MAMEOPTS_H
+#define MAME_FRONTEND_MAMEOPTS_H
+
+#pragma once
+
+#include "emuopts.h"
+
+//**************************************************************************
+// CONSTANTS
+//**************************************************************************
+#undef OPTION_PRIORITY_CMDLINE
+
+// option priorities
+enum
+{
+ // command-line options are HIGH priority
+ OPTION_PRIORITY_SUBCMD = OPTION_PRIORITY_HIGH,
+ OPTION_PRIORITY_CMDLINE,
+
+ // INI-based options are NORMAL priority, in increasing order:
+ OPTION_PRIORITY_MAME_INI = OPTION_PRIORITY_NORMAL + 1,
+ OPTION_PRIORITY_DEBUG_INI,
+ OPTION_PRIORITY_ORIENTATION_INI,
+ OPTION_PRIORITY_SYSTYPE_INI,
+ OPTION_PRIORITY_SCREEN_INI,
+ OPTION_PRIORITY_SOURCE_INI,
+ OPTION_PRIORITY_GPARENT_INI,
+ OPTION_PRIORITY_PARENT_INI,
+ OPTION_PRIORITY_DRIVER_INI,
+ OPTION_PRIORITY_INI,
+};
+
+//**************************************************************************
+// TYPE DEFINITIONS
+//**************************************************************************
+
+// forward references
+class game_driver;
+class software_part;
+
+class mame_options
+{
+public:
+ // parsing wrappers
+ static void parse_standard_inis(emu_options &options, std::ostream &error_stream, const game_driver *driver = nullptr);
+ static const game_driver *system(const emu_options &options);
+ static void populate_hashpath_from_args_and_inis(emu_options &options, const std::vector<std::string> &args);
+
+private:
+ // INI parsing helper
+ static void parse_one_ini(emu_options &options, const char *basename, int priority, std::ostream *error_stream = nullptr);
+};
+
+#endif // MAME_FRONTEND_MAMEOPTS_H