summaryrefslogtreecommitdiffstats
path: root/docs/release/src/frontend
diff options
context:
space:
mode:
Diffstat (limited to 'docs/release/src/frontend')
-rw-r--r--docs/release/src/frontend/mame/audit.cpp784
-rw-r--r--docs/release/src/frontend/mame/audit.h178
-rw-r--r--docs/release/src/frontend/mame/clifront.cpp1789
-rw-r--r--docs/release/src/frontend/mame/language.cpp42
-rw-r--r--docs/release/src/frontend/mame/mameopts.cpp211
-rw-r--r--docs/release/src/frontend/mame/mameopts.h64
-rw-r--r--docs/release/src/frontend/mame/ui/about.cpp123
-rw-r--r--docs/release/src/frontend/mame/ui/inifile.cpp580
8 files changed, 3771 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..71781f2421a
--- /dev/null
+++ b/docs/release/src/frontend/mame/audit.cpp
@@ -0,0 +1,784 @@
+// license:BSD-3-Clause
+// copyright-holders:Aaron Giles
+/***************************************************************************
+
+ audit.cpp
+
+ ROM set auditing functions.
+
+***************************************************************************/
+
+#include "emu.h"
+#include "audit.h"
+
+#include "sound/samples.h"
+
+#include "emuopts.h"
+#include "drivenum.h"
+#include "fileio.h"
+#include "romload.h"
+#include "softlist_dev.h"
+
+#include "chd.h"
+#include "path.h"
+
+#include <algorithm>
+
+//#define VERBOSE 1
+#define LOG_OUTPUT_FUNC osd_printf_verbose
+#include "logmacro.h"
+
+
+namespace {
+
+struct parent_rom
+{
+ parent_rom(device_type t, rom_entry const *r) : type(t), name(r->name()), hashes(r->hashdata()), length(rom_file_size(r)) { }
+
+ std::reference_wrapper<std::remove_reference_t<device_type> > type;
+ std::string name;
+ util::hash_collection hashes;
+ uint64_t length;
+};
+
+
+class parent_rom_vector : public std::vector<parent_rom>
+{
+public:
+ using std::vector<parent_rom>::vector;
+
+ void remove_redundant_parents()
+ {
+ while (!empty())
+ {
+ // find where the next parent starts
+ auto const last(
+ std::find_if(
+ std::next(cbegin()),
+ cend(),
+ [this] (parent_rom const &r) { return &front().type.get() != &r.type.get(); }));
+
+ // examine dumped ROMs in this generation
+ for (auto i = cbegin(); last != i; ++i)
+ {
+ if (!i->hashes.flag(util::hash_collection::FLAG_NO_DUMP))
+ {
+ auto const match(
+ std::find_if(
+ last,
+ cend(),
+ [&i] (parent_rom const &r) { return (i->length == r.length) && (i->hashes == r.hashes); }));
+ if (cend() == match)
+ return;
+ }
+ }
+ erase(cbegin(), last);
+ }
+ }
+
+ std::add_pointer_t<device_type> find_shared_device(device_t &current, std::string_view name, util::hash_collection const &hashes, uint64_t length) const
+ {
+ // if we're examining a child device, it will always have a perfect match
+ if (current.owner())
+ return &current.type();
+
+ // scan backwards through parents for a matching definition
+ bool const dumped(!hashes.flag(util::hash_collection::FLAG_NO_DUMP));
+ std::add_pointer_t<device_type> best(nullptr);
+ for (const_reverse_iterator it = crbegin(); crend() != it; ++it)
+ {
+ if (it->length == length)
+ {
+ if (dumped)
+ {
+ if (it->hashes == hashes)
+ return &it->type.get();
+ }
+ else if (it->name == name)
+ {
+ if (it->hashes.flag(util::hash_collection::FLAG_NO_DUMP))
+ return &it->type.get();
+ else if (!best)
+ best = &it->type.get();
+ }
+ }
+ }
+ return best;
+ }
+
+ std::pair<std::add_pointer_t<device_type>, bool> actual_matches_shared(device_t &current, media_auditor::audit_record const &record)
+ {
+ // no result if no matching file was found
+ if ((record.status() != media_auditor::audit_status::GOOD) && (record.status() != media_auditor::audit_status::FOUND_INVALID))
+ return std::make_pair(nullptr, false);
+
+ // if we're examining a child device, scan it first
+ bool matches_device_undumped(false);
+ if (current.owner())
+ {
+ for (const rom_entry *region = rom_first_region(current); region; region = rom_next_region(region))
+ {
+ for (const rom_entry *rom = rom_first_file(region); rom; rom = rom_next_file(rom))
+ {
+ if (rom_file_size(rom) == record.actual_length())
+ {
+ util::hash_collection const hashes(rom->hashdata());
+ if (hashes == record.actual_hashes())
+ return std::make_pair(&current.type(), empty());
+ else if (hashes.flag(util::hash_collection::FLAG_NO_DUMP) && (rom->name() == record.name()))
+ matches_device_undumped = true;
+ }
+ }
+ }
+ }
+
+ // look for a matching parent ROM
+ std::add_pointer_t<device_type> closest_bad(nullptr);
+ for (const_reverse_iterator it = crbegin(); crend() != it; ++it)
+ {
+ if (it->length == record.actual_length())
+ {
+ if (it->hashes == record.actual_hashes())
+ return std::make_pair(&it->type.get(), it->type.get() == front().type.get());
+ else if (it->hashes.flag(util::hash_collection::FLAG_NO_DUMP) && (it->name == record.name()))
+ closest_bad = &it->type.get();
+ }
+ }
+
+ // fall back to the nearest bad dump
+ if (closest_bad)
+ return std::make_pair(closest_bad, front().type.get() == *closest_bad);
+ else if (matches_device_undumped)
+ return std::make_pair(&current.type(), empty());
+ else
+ return std::make_pair(nullptr, false);
+ }
+};
+
+} // anonymous namespace
+
+
+
+//**************************************************************************
+// CORE FUNCTIONS
+//**************************************************************************
+
+//-------------------------------------------------
+// media_auditor - constructor
+//-------------------------------------------------
+
+media_auditor::media_auditor(const driver_enumerator &enumerator)
+ : m_enumerator(enumerator)
+ , m_validation(AUDIT_VALIDATE_FULL)
+{
+}
+
+
+//-------------------------------------------------
+// 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;
+
+ // first walk the parent chain for required ROMs
+ parent_rom_vector parentroms;
+ for (auto drvindex = m_enumerator.find(m_enumerator.driver().parent); 0 <= drvindex; drvindex = m_enumerator.find(m_enumerator.driver(drvindex).parent))
+ {
+ game_driver const &parent(m_enumerator.driver(drvindex));
+ LOG("Checking parent %s for ROM files\n", parent.type.shortname());
+ std::vector<rom_entry> const roms(rom_build_entries(parent.rom));
+ for (rom_entry const *region = rom_first_region(&roms.front()); region; region = rom_next_region(region))
+ {
+ for (rom_entry const *rom = rom_first_file(region); rom; rom = rom_next_file(rom))
+ {
+ LOG("Adding parent ROM %s\n", rom->name());
+ parentroms.emplace_back(parent.type, rom);
+ }
+ }
+ }
+ parentroms.remove_redundant_parents();
+
+ // count ROMs required/found
+ std::size_t found(0);
+ std::size_t required(0);
+ std::size_t shared_found(0);
+ std::size_t shared_required(0);
+ std::size_t parent_found(0);
+
+ // iterate over devices and regions
+ std::vector<std::string> searchpath;
+ for (device_t &device : device_enumerator(m_enumerator.config()->root_device()))
+ {
+ searchpath.clear();
+
+ // now iterate over regions and ROMs within
+ 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 (searchpath.empty())
+ {
+ LOG("Audit media for device %s(%s)\n", device.shortname(), device.tag());
+ searchpath = device.searchpath();
+ }
+
+ // look for a matching parent or device ROM
+ std::string const &name(rom->name());
+ util::hash_collection const hashes(rom->hashdata());
+ bool const dumped(!hashes.flag(util::hash_collection::FLAG_NO_DUMP));
+ std::add_pointer_t<device_type> const shared_device(parentroms.find_shared_device(device, name, hashes, rom_file_size(rom)));
+ if (shared_device)
+ LOG("File '%s' %s%sdumped shared with %s\n", name, ROM_ISOPTIONAL(rom) ? "optional " : "", dumped ? "" : "un", shared_device->shortname());
+ else
+ LOG("File '%s' %s%sdumped\n", name, ROM_ISOPTIONAL(rom) ? "optional " : "", dumped ? "" : "un");
+
+ // count the number of files with hashes
+ if (dumped && !ROM_ISOPTIONAL(rom))
+ {
+ required++;
+ if (shared_device)
+ shared_required++;
+ }
+
+ audit_record *record(nullptr);
+ if (ROMREGION_ISROMDATA(region))
+ record = &audit_one_rom(searchpath, rom);
+ else if (ROMREGION_ISDISKDATA(region))
+ record = &audit_one_disk(rom, device);
+
+ if (record)
+ {
+ // see if the actual content found belongs to a parent
+ auto const matchesshared(parentroms.actual_matches_shared(device, *record));
+ if (matchesshared.first)
+ LOG("Actual ROM file shared with %sparent %s\n", matchesshared.second ? "immediate " : "", matchesshared.first->shortname());
+
+ // count the number of files that are found.
+ if ((record->status() == audit_status::GOOD) || ((record->status() == audit_status::FOUND_INVALID) && !matchesshared.first))
+ {
+ found++;
+ if (shared_device)
+ shared_found++;
+ if (matchesshared.second)
+ parent_found++;
+ }
+
+ record->set_shared_device(shared_device);
+ }
+ }
+ }
+
+ if (!searchpath.empty())
+ LOG("Total required=%u (shared=%u) found=%u (shared=%u parent=%u)\n", required, shared_required, found, shared_found, parent_found);
+ }
+
+ // 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 && ((required != shared_required) || !parent_found))
+ {
+ 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;
+
+ std::size_t found = 0;
+ std::size_t required = 0;
+
+ std::vector<std::string> searchpath;
+ audit_regions(
+ [this, &device, &searchpath] (rom_entry const *region, rom_entry const *rom) -> audit_record const *
+ {
+ if (ROMREGION_ISROMDATA(region))
+ {
+ if (searchpath.empty())
+ searchpath = device.searchpath();
+ return &audit_one_rom(searchpath, rom);
+ }
+ else if (ROMREGION_ISDISKDATA(region))
+ {
+ return &audit_one_disk(rom, device);
+ }
+ else
+ {
+ return nullptr;
+ }
+ },
+ rom_first_region(device),
+ 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(software_list_device &swlist, const software_info &swinfo, const char *validation)
+{
+ // start fresh
+ m_record_list.clear();
+
+ // store validation for later
+ m_validation = validation;
+
+ std::size_t found = 0;
+ std::size_t required = 0;
+
+ // now iterate over software parts
+ std::vector<std::string> searchpath;
+ auto const do_audit =
+ [this, &swlist, &swinfo, &searchpath] (rom_entry const *region, rom_entry const *rom) -> audit_record const *
+ {
+ if (ROMREGION_ISROMDATA(region))
+ {
+ if (searchpath.empty())
+ searchpath = rom_load_manager::get_software_searchpath(swlist, swinfo);
+ return &audit_one_rom(searchpath, rom);
+ }
+ else if (ROMREGION_ISDISKDATA(region))
+ {
+ return &audit_one_disk(rom, swlist, swinfo);
+ }
+ else
+ {
+ return nullptr;
+ }
+ };
+ for (const software_part &part : swinfo.parts())
+ audit_regions(do_audit, part.romdata().data(), found, required);
+
+ if ((found == 0) && (required > 0))
+ {
+ m_record_list.clear();
+ return NOTFOUND;
+ }
+
+ // return a summary
+ return summarize(swlist.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_enumerator(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);
+ std::string curpath;
+ while (path.next(curpath))
+ {
+ util::path_append(curpath, samplename);
+
+ // attempt to access the file (.flac) or (.wav)
+ std::error_condition filerr = file.open(curpath + ".flac");
+ if (filerr)
+ filerr = file.open(curpath + ".wav");
+
+ if (!filerr)
+ {
+ 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)
+ {
+ std::add_pointer_t<device_type> 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
+//-------------------------------------------------
+
+template <typename T>
+void media_auditor::audit_regions(T do_audit, const rom_entry *region, std::size_t &found, std::size_t &required)
+{
+ // now iterate over regions
+ std::vector<std::string> searchpath;
+ for ( ; region; region = rom_next_region(region))
+ {
+ // now iterate over rom definitions
+ for (rom_entry const *rom = rom_first_file(region); rom; rom = rom_next_file(rom))
+ {
+ // count the number of files with hashes
+ util::hash_collection const hashes(rom->hashdata());
+ if (!hashes.flag(util::hash_collection::FLAG_NO_DUMP) && !ROM_ISOPTIONAL(rom))
+ required++;
+
+ audit_record const *const record = do_audit(region, rom);
+
+ // 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 std::vector<std::string> &searchpath, 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(), searchpath, OPEN_FLAG_READ | OPEN_FLAG_NO_PRELOAD);
+ file.set_restrict_to_mediapath(1);
+
+ // open the file if we can
+ std::error_condition filerr;
+ if (has_crc)
+ filerr = file.open(record.name(), crc);
+ else
+ filerr = file.open(record.name());
+
+ // if it worked, get the actual length and hashes, then stop
+ if (!filerr)
+ record.set_actual(file.hashes(m_validation), file.size());
+
+ // compute the final status
+ compute_status(record, rom, record.actual_length() != 0);
+ return record;
+}
+
+
+//-------------------------------------------------
+// audit_one_disk - validate a single disk entry
+//-------------------------------------------------
+
+template <typename... T>
+media_auditor::audit_record &media_auditor::audit_one_disk(const rom_entry *rom, T &&... args)
+{
+ // 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;
+ const std::error_condition err = rom_load_manager::open_disk_image(m_enumerator.options(), std::forward<T>(args)..., rom, source);
+
+ // if we succeeded, get the hashes
+ if (!err)
+ {
+ 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);
+ 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);
+ }
+}
+
+
+//-------------------------------------------------
+// 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(media.name())
+ , m_explength(rom_file_size(&media))
+ , m_length(0)
+ , m_exphashes(media.hashdata())
+ , m_hashes()
+ , m_shared_device(nullptr)
+{
+}
+
+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)
+ {
+ std::add_pointer_t<device_type> const 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..e1857de5cdf
--- /dev/null
+++ b/docs/release/src/frontend/mame/audit.h
@@ -0,0 +1,178 @@
+// license:BSD-3-Clause
+// copyright-holders:Aaron Giles
+/***************************************************************************
+
+ audit.h
+
+ ROM, disk, and sample auditing functions.
+
+***************************************************************************/
+#ifndef MAME_FRONTEND_AUDIT_H
+#define MAME_FRONTEND_AUDIT_H
+
+#pragma once
+
+#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;
+class software_list_device;
+
+
+
+// ======================> 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 std::string &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; }
+ std::add_pointer_t<device_type> 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(std::add_pointer_t<device_type> 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
+ std::string 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
+ std::add_pointer_t<device_type> 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(software_list_device &swlist, 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
+ template <typename T> void audit_regions(T do_audit, const rom_entry *region, std::size_t &found, std::size_t &required);
+ audit_record &audit_one_rom(const std::vector<std::string> &searchpath, const rom_entry *rom);
+ template <typename... T> audit_record &audit_one_disk(const rom_entry *rom, T &&... args);
+ void compute_status(audit_record &record, const rom_entry *rom, bool found);
+
+ // internal state
+ record_list m_record_list;
+ const driver_enumerator & m_enumerator;
+ const char * m_validation;
+};
+
+
+#endif // MAME_FRONTEND_AUDIT_H
diff --git a/docs/release/src/frontend/mame/clifront.cpp b/docs/release/src/frontend/mame/clifront.cpp
new file mode 100644
index 00000000000..a7779183894
--- /dev/null
+++ b/docs/release/src/frontend/mame/clifront.cpp
@@ -0,0 +1,1789 @@
+// license:BSD-3-Clause
+// copyright-holders:Aaron Giles
+/***************************************************************************
+
+ clifront.cpp
+
+ Command-line interface frontend for MAME.
+
+***************************************************************************/
+
+#include "emu.h"
+#include "clifront.h"
+
+#include "ui/moptions.h"
+
+#include "audit.h"
+#include "infoxml.h"
+#include "language.h"
+#include "luaengine.h"
+#include "mame.h"
+#include "mameopts.h"
+#include "media_ident.h"
+#include "pluginopts.h"
+
+#include "emuopts.h"
+#include "fileio.h"
+#include "romload.h"
+#include "softlist_dev.h"
+#include "validity.h"
+#include "sound/samples.h"
+
+#include "chd.h"
+#include "corestr.h"
+#include "unzip.h"
+#include "xmlfile.h"
+
+#include "osdepend.h"
+
+#include <algorithm>
+#include <new>
+#include <set>
+#include <tuple>
+#include <cctype>
+#include <iostream>
+
+
+//**************************************************************************
+// CONSTANTS
+//**************************************************************************
+
+// core commands
+#define CLICOMMAND_HELP "help"
+#define CLICOMMAND_VALIDATE "validate"
+
+// configuration commands
+#define CLICOMMAND_CREATECONFIG "createconfig"
+#define CLICOMMAND_SHOWCONFIG "showconfig"
+#define CLICOMMAND_SHOWUSAGE "showusage"
+
+// frontend commands
+#define CLICOMMAND_LISTXML "listxml"
+#define CLICOMMAND_LISTFULL "listfull"
+#define CLICOMMAND_LISTSOURCE "listsource"
+#define CLICOMMAND_LISTCLONES "listclones"
+#define CLICOMMAND_LISTBROTHERS "listbrothers"
+#define CLICOMMAND_LISTCRC "listcrc"
+#define CLICOMMAND_LISTROMS "listroms"
+#define CLICOMMAND_LISTSAMPLES "listsamples"
+#define CLICOMMAND_VERIFYROMS "verifyroms"
+#define CLICOMMAND_VERIFYSAMPLES "verifysamples"
+#define CLICOMMAND_ROMIDENT "romident"
+#define CLICOMMAND_LISTDEVICES "listdevices"
+#define CLICOMMAND_LISTSLOTS "listslots"
+#define CLICOMMAND_LISTMEDIA "listmedia"
+#define CLICOMMAND_LISTSOFTWARE "listsoftware"
+#define CLICOMMAND_VERIFYSOFTWARE "verifysoftware"
+#define CLICOMMAND_GETSOFTLIST "getsoftlist"
+#define CLICOMMAND_VERIFYSOFTLIST "verifysoftlist"
+#define CLICOMMAND_VERSION "version"
+
+// command options
+#define CLIOPTION_DTD "dtd"
+
+
+namespace {
+
+//**************************************************************************
+// COMMAND-LINE OPTIONS
+//**************************************************************************
+
+const options_entry cli_option_entries[] =
+{
+ /* core commands */
+ { nullptr, nullptr, OPTION_HEADER, "CORE COMMANDS" },
+ { CLICOMMAND_HELP ";h;?", "0", OPTION_COMMAND, "show help message" },
+ { CLICOMMAND_VALIDATE ";valid", "0", OPTION_COMMAND, "perform validation on system drivers and devices" },
+
+ /* configuration commands */
+ { nullptr, nullptr, OPTION_HEADER, "CONFIGURATION COMMANDS" },
+ { CLICOMMAND_CREATECONFIG ";cc", "0", OPTION_COMMAND, "create the default configuration file" },
+ { CLICOMMAND_SHOWCONFIG ";sc", "0", OPTION_COMMAND, "display running parameters" },
+ { CLICOMMAND_SHOWUSAGE ";su", "0", OPTION_COMMAND, "show this help" },
+
+ /* frontend commands */
+ { nullptr, nullptr, OPTION_HEADER, "FRONTEND COMMANDS" },
+ { CLICOMMAND_LISTXML ";lx", "0", OPTION_COMMAND, "all available info on driver in XML format" },
+ { CLICOMMAND_LISTFULL ";ll", "0", OPTION_COMMAND, "short name, full name" },
+ { CLICOMMAND_LISTSOURCE ";ls", "0", OPTION_COMMAND, "driver sourcefile" },
+ { CLICOMMAND_LISTCLONES ";lc", "0", OPTION_COMMAND, "show clones" },
+ { CLICOMMAND_LISTBROTHERS ";lb", "0", OPTION_COMMAND, "show \"brothers\", or other drivers from same sourcefile" },
+ { CLICOMMAND_LISTCRC, "0", OPTION_COMMAND, "CRC-32s" },
+ { CLICOMMAND_LISTROMS ";lr", "0", OPTION_COMMAND, "list required ROMs for a driver" },
+ { CLICOMMAND_LISTSAMPLES, "0", OPTION_COMMAND, "list optional samples for a driver" },
+ { CLICOMMAND_VERIFYROMS, "0", OPTION_COMMAND, "report romsets that have problems" },
+ { CLICOMMAND_VERIFYSAMPLES, "0", OPTION_COMMAND, "report samplesets that have problems" },
+ { CLICOMMAND_ROMIDENT, "0", OPTION_COMMAND, "compare files with known MAME ROMs" },
+ { CLICOMMAND_LISTDEVICES ";ld", "0", OPTION_COMMAND, "list available devices" },
+ { CLICOMMAND_LISTSLOTS ";lslot", "0", OPTION_COMMAND, "list available slots and slot devices" },
+ { CLICOMMAND_LISTMEDIA ";lm", "0", OPTION_COMMAND, "list available media for the system" },
+ { CLICOMMAND_LISTSOFTWARE ";lsoft", "0", OPTION_COMMAND, "list known software for the system" },
+ { CLICOMMAND_VERIFYSOFTWARE ";vsoft", "0", OPTION_COMMAND, "verify known software for the system" },
+ { CLICOMMAND_GETSOFTLIST ";glist", "0", OPTION_COMMAND, "retrieve software list by name" },
+ { CLICOMMAND_VERIFYSOFTLIST ";vlist", "0", OPTION_COMMAND, "verify software list by name" },
+ { CLICOMMAND_VERSION, "0", OPTION_COMMAND, "get MAME version" },
+
+ { nullptr, nullptr, OPTION_HEADER, "FRONTEND COMMAND OPTIONS" },
+ { CLIOPTION_DTD, "1", OPTION_BOOLEAN, "include DTD in XML output" },
+ { nullptr }
+};
+
+
+void print_summary(
+ const media_auditor &auditor, media_auditor::summary summary, bool record_none_needed,
+ const char *type, const char *name, const char *parent,
+ unsigned &correct, unsigned &incorrect, unsigned &notfound,
+ util::ovectorstream &buffer)
+{
+ if (summary == media_auditor::NOTFOUND)
+ {
+ // if not found, count that and leave it at that
+ ++notfound;
+ }
+ else if (record_none_needed || (summary != media_auditor::NONE_NEEDED))
+ {
+ // output the summary of the audit
+ buffer.clear();
+ buffer.seekp(0);
+ auditor.summarize(name, &buffer);
+ buffer.put('\0');
+ osd_printf_info("%s", &buffer.vec()[0]);
+
+ // output the name of the driver and its parent
+ osd_printf_info("%sset %s ", type, name);
+ if (parent)
+ osd_printf_info("[%s] ", parent);
+
+ // switch off of the result
+ switch (summary)
+ {
+ case media_auditor::INCORRECT:
+ osd_printf_info("is bad\n");
+ ++incorrect;
+ return;
+
+ case media_auditor::CORRECT:
+ osd_printf_info("is good\n");
+ ++correct;
+ return;
+
+ case media_auditor::BEST_AVAILABLE:
+ case media_auditor::NONE_NEEDED:
+ osd_printf_info("is best available\n");
+ ++correct;
+ return;
+
+ case media_auditor::NOTFOUND:
+ osd_printf_info("not found\n");
+ return;
+ }
+ assert(false);
+ osd_printf_error("has unknown status (%u)\n", unsigned(summary));
+ }
+}
+
+} // anonymous namespace
+
+
+//**************************************************************************
+// CLI FRONTEND
+//**************************************************************************
+
+//-------------------------------------------------
+// cli_frontend - constructor
+//-------------------------------------------------
+
+cli_frontend::cli_frontend(emu_options &options, osd_interface &osd)
+ : m_options(options)
+ , m_osd(osd)
+ , m_result(EMU_ERR_NONE)
+{
+ m_options.add_entries(cli_option_entries);
+}
+
+
+//-------------------------------------------------
+// ~cli_frontend - destructor
+//-------------------------------------------------
+
+cli_frontend::~cli_frontend()
+{
+}
+
+void cli_frontend::start_execution(mame_machine_manager *manager, const std::vector<std::string> &args)
+{
+ std::ostringstream option_errors;
+
+ // because softlist evaluation relies on hashpath being populated, we are going to go through
+ // a special step to force it to be evaluated
+ mame_options::populate_hashpath_from_args_and_inis(m_options, args);
+
+ // parse the command line, adding any system-specific options
+ try
+ {
+ m_options.parse_command_line(args, OPTION_PRIORITY_CMDLINE);
+ }
+ catch (options_warning_exception &ex)
+ {
+ osd_printf_error("%s", ex.message());
+ }
+ catch (options_exception &ex)
+ {
+ // if we failed, check for no command and a system name first; in that case error on the name
+ if (m_options.command().empty() && mame_options::system(m_options) == nullptr && !m_options.attempted_system_name().empty())
+ throw emu_fatalerror(EMU_ERR_NO_SUCH_SYSTEM, "Unknown system '%s'", m_options.attempted_system_name());
+
+ // otherwise, error on the options
+ throw emu_fatalerror(EMU_ERR_INVALID_CONFIG, "%s", ex.message());
+ }
+ m_osd.set_verbose(m_options.verbose());
+
+ // determine the base name of the EXE
+ std::string_view exename = core_filename_extract_base(args[0], true);
+
+ // if we have a command, execute that
+ if (!m_options.command().empty())
+ {
+ execute_commands(exename);
+ return;
+ }
+
+ // read INI's, if appropriate
+ if (m_options.read_config())
+ {
+ mame_options::parse_standard_inis(m_options, option_errors);
+ m_osd.set_verbose(m_options.verbose());
+ }
+
+ // otherwise, check for a valid system
+ load_translation(m_options);
+
+ manager->start_http_server();
+
+ manager->start_luaengine();
+
+ if (option_errors.tellp() > 0)
+ osd_printf_error("Error in command line:\n%s\n", strtrimspace(option_errors.str()));
+
+ // if we can't find it, give an appropriate error
+ const game_driver *system = mame_options::system(m_options);
+ if (system == nullptr && *(m_options.system_name()) != 0)
+ throw emu_fatalerror(EMU_ERR_NO_SUCH_SYSTEM, "Unknown system '%s'", m_options.system_name());
+
+ // otherwise just run the game
+ m_result = manager->execute();
+}
+
+//-------------------------------------------------
+// execute - execute a game via the standard
+// command line interface
+//-------------------------------------------------
+
+int cli_frontend::execute(std::vector<std::string> &args)
+{
+ // wrap the core execution in a try/catch to field all fatal errors
+ m_result = EMU_ERR_NONE;
+ mame_machine_manager *manager = mame_machine_manager::instance(m_options, m_osd);
+
+ try
+ {
+ start_execution(manager, args);
+ }
+ // handle exceptions of various types
+ catch (emu_fatalerror &fatal)
+ {
+ osd_printf_error("%s\n", strtrimspace(fatal.what()));
+ m_result = (fatal.exitcode() != 0) ? fatal.exitcode() : EMU_ERR_FATALERROR;
+
+ // if a game was specified, wasn't a wildcard, and our error indicates this was the
+ // reason for failure, offer some suggestions
+ if (m_result == EMU_ERR_NO_SUCH_SYSTEM
+ && !m_options.attempted_system_name().empty()
+ && !core_iswildstr(m_options.attempted_system_name().c_str())
+ && mame_options::system(m_options) == nullptr)
+ {
+ // get the top 16 approximate matches
+ driver_enumerator drivlist(m_options);
+ int matches[16];
+ drivlist.find_approximate_matches(m_options.attempted_system_name(), std::size(matches), matches);
+
+ // work out how wide the titles need to be
+ int titlelen(0);
+ for (int match : matches)
+ if (0 <= match)
+ titlelen = (std::max)(titlelen, int(strlen(drivlist.driver(match).type.fullname())));
+
+ // print them out
+ osd_printf_error("\n\"%s\" approximately matches the following\n"
+ "supported machines (best match first):\n\n", m_options.attempted_system_name());
+ for (int match : matches)
+ {
+ if (0 <= match)
+ {
+ game_driver const &drv(drivlist.driver(match));
+ osd_printf_error("%-18s%-*s(%s, %s)\n", drv.name, titlelen + 2, drv.type.fullname(), drv.manufacturer, drv.year);
+ }
+ }
+ }
+ }
+ catch (emu_exception &)
+ {
+ osd_printf_error("Caught unhandled emulator exception\n");
+ m_result = EMU_ERR_FATALERROR;
+ }
+ catch (tag_add_exception &aex)
+ {
+ osd_printf_error("Tag '%s' already exists in tagged map\n", aex.tag());
+ m_result = EMU_ERR_FATALERROR;
+ }
+ catch (std::exception &ex)
+ {
+ osd_printf_error("Caught unhandled %s exception: %s\n", typeid(ex).name(), ex.what());
+ m_result = EMU_ERR_FATALERROR;
+ }
+ catch (...)
+ {
+ osd_printf_error("Caught unhandled exception\n");
+ m_result = EMU_ERR_FATALERROR;
+ }
+
+ util::archive_file::cache_clear();
+ delete manager;
+
+ return m_result;
+}
+
+
+//-------------------------------------------------
+// listxml - output the XML data for one or more
+// games
+//-------------------------------------------------
+
+void cli_frontend::listxml(const std::vector<std::string> &args)
+{
+ // create the XML and print it to stdout
+ info_xml_creator creator(m_options, m_options.bool_value(CLIOPTION_DTD));
+ creator.output(std::cout, args);
+}
+
+
+//-------------------------------------------------
+// listfull - output the name and description of
+// one or more games
+//-------------------------------------------------
+
+void cli_frontend::listfull(const std::vector<std::string> &args)
+{
+ auto const list_system_name = [] (device_type type, bool first)
+ {
+ // print the header
+ if (first)
+ osd_printf_info("Name: Description:\n");
+
+ osd_printf_info("%-17s \"%s\"\n", type.shortname(), type.fullname());
+ };
+ apply_action(
+ args,
+ [&list_system_name] (driver_enumerator &drivlist, bool first)
+ { list_system_name(drivlist.driver().type, first); },
+ [&list_system_name] (device_type type, bool first)
+ { list_system_name(type, first); });
+}
+
+
+//-------------------------------------------------
+// listsource - output the name and source
+// filename of one or more games
+//-------------------------------------------------
+
+void cli_frontend::listsource(const std::vector<std::string> &args)
+{
+ auto const list_system_source = [] (device_type type)
+ {
+ osd_printf_info("%-16s %s\n", type.shortname(), core_filename_extract_base(type.source()));
+ };
+ apply_action(
+ args,
+ [&list_system_source] (driver_enumerator &drivlist, bool first)
+ { list_system_source(drivlist.driver().type); },
+ [&list_system_source] (device_type type, bool first)
+ { list_system_source(type); });
+}
+
+
+//-------------------------------------------------
+// listclones - output the name and parent of all
+// clones matching the given pattern
+//-------------------------------------------------
+
+void cli_frontend::listclones(const std::vector<std::string> &args)
+{
+ const char *gamename = args.empty() ? nullptr : args[0].c_str();
+
+ // start with a filtered list of drivers
+ driver_enumerator drivlist(m_options, gamename);
+ int const original_count = drivlist.count();
+
+ // iterate through the remaining ones to see if their parent matches
+ while (drivlist.next_excluded())
+ {
+ // if we have a non-bios clone and it matches, keep it
+ int const clone_of = drivlist.clone();
+ if ((clone_of >= 0) && !(drivlist.driver(clone_of).flags & machine_flags::IS_BIOS_ROOT))
+ if (drivlist.matches(gamename, drivlist.driver(clone_of).name))
+ drivlist.include();
+ }
+
+ // return an error if none found
+ if (drivlist.count() == 0)
+ {
+ // see if we match but just weren't a clone
+ if (original_count == 0)
+ throw emu_fatalerror(EMU_ERR_NO_SUCH_SYSTEM, "No matching systems found for '%s'", gamename);
+ else
+ osd_printf_info("Found %lu match(es) for '%s' but none were clones\n", (unsigned long)drivlist.count(), gamename); // FIXME: this never gets hit
+ return;
+ }
+
+ // print the header
+ osd_printf_info("Name: Clone of:\n");
+
+ // iterate through drivers and output the info
+ drivlist.reset();
+ while (drivlist.next())
+ {
+ int clone_of = drivlist.clone();
+ if ((clone_of >= 0) && !(drivlist.driver(clone_of).flags & machine_flags::IS_BIOS_ROOT))
+ osd_printf_info("%-16s %s\n", drivlist.driver().name, drivlist.driver(clone_of).name);
+ }
+}
+
+
+//-------------------------------------------------
+// listbrothers - for each matching game, output
+// the list of other games that share the same
+// source file
+//-------------------------------------------------
+
+void cli_frontend::listbrothers(const std::vector<std::string> &args)
+{
+ const char *gamename = args.empty() ? nullptr : args[0].c_str();
+
+ // start with a filtered list of drivers; return an error if none found
+ driver_enumerator initial_drivlist(m_options, gamename);
+ if (initial_drivlist.count() == 0)
+ throw emu_fatalerror(EMU_ERR_NO_SUCH_SYSTEM, "No matching systems found for '%s'", gamename);
+
+ // for the final list, start with an empty driver list
+ driver_enumerator drivlist(m_options);
+ drivlist.exclude_all();
+
+ // scan through the initially-selected drivers
+ while (initial_drivlist.next())
+ {
+ // if we are already marked in the final list, we don't need to do anything
+ if (drivlist.included(initial_drivlist.current()))
+ continue;
+
+ // otherwise, walk excluded items in the final list and mark any that match
+ drivlist.reset();
+ while (drivlist.next_excluded())
+ if (strcmp(drivlist.driver().type.source(), initial_drivlist.driver().type.source()) == 0)
+ drivlist.include();
+ }
+
+ // print the header
+ osd_printf_info("%-20s %-16s %s\n", "Source file:", "Name:", "Parent:");
+
+ // output the entries found
+ drivlist.reset();
+ while (drivlist.next())
+ {
+ int clone_of = drivlist.clone();
+ if (clone_of != -1)
+ osd_printf_info("%-20s %-16s %s\n", core_filename_extract_base(drivlist.driver().type.source()), drivlist.driver().name, (clone_of == -1 ? "" : drivlist.driver(clone_of).name));
+ else
+ osd_printf_info("%-20s %s\n", core_filename_extract_base(drivlist.driver().type.source()), drivlist.driver().name);
+ }
+}
+
+
+//-------------------------------------------------
+// listcrc - output the CRC and name of all ROMs
+// referenced by the emulator
+//-------------------------------------------------
+
+void cli_frontend::listcrc(const std::vector<std::string> &args)
+{
+ apply_device_action(
+ args,
+ [] (device_t &root, char const *type, bool first)
+ {
+ for (device_t const &device : device_enumerator(root))
+ {
+ for (tiny_rom_entry const *rom = device.rom_region(); rom && !ROMENTRY_ISEND(rom); ++rom)
+ {
+ if (ROMENTRY_ISFILE(rom))
+ {
+ // if we have a CRC, display it
+ uint32_t crc;
+ if (util::hash_collection(rom->hashdata).crc(crc))
+ osd_printf_info("%08x %-32s\t%-16s\t%s\n", crc, rom->name, device.shortname(), device.name());
+ }
+ }
+ }
+ });
+}
+
+
+//-------------------------------------------------
+// listroms - output the list of ROMs referenced
+// by matching systems/devices
+//-------------------------------------------------
+
+void cli_frontend::listroms(const std::vector<std::string> &args)
+{
+ apply_device_action(
+ args,
+ [] (device_t &root, char const *type, bool first)
+ {
+ // space between items
+ if (!first)
+ osd_printf_info("\n");
+
+ // iterate through ROMs
+ std::list<std::tuple<std::string, int64_t, std::string>> entries;
+ std::set<std::string_view> devnames;
+ for (device_t const &device : device_enumerator(root))
+ {
+ bool hasroms = false;
+ 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 (!hasroms)
+ {
+ hasroms = true;
+ if (&device != &root)
+ devnames.insert(device.shortname());
+ }
+
+ // accumulate the total length of all chunks
+ int64_t length = -1;
+ if (ROMREGION_ISROMDATA(region))
+ length = rom_file_size(rom);
+
+ entries.emplace_back(rom->name(), length, rom->hashdata());
+ }
+ }
+ }
+
+ // print results
+ if (entries.empty())
+ osd_printf_info("No ROMs required for %s \"%s\".\n", type, root.shortname());
+ else
+ {
+ // print a header
+ osd_printf_info("ROMs required for %s \"%s\"", type, root.shortname());
+ if (!devnames.empty())
+ {
+ osd_printf_info(" (including device%s", devnames.size() > 1 ? "s" : "");
+ bool first = true;
+ for (const std::string_view &devname : devnames)
+ {
+ if (first)
+ first = false;
+ else
+ osd_printf_info(",");
+ osd_printf_info(" \"%s\"", devname);
+ }
+ osd_printf_info(")");
+ }
+ osd_printf_info(".\n%-32s %10s %s\n", "Name", "Size", "Checksum");
+
+ for (auto &entry : entries)
+ {
+ // start with the name
+ osd_printf_info("%-32s ", std::get<0>(entry));
+
+ // output the length next
+ int64_t length = std::get<1>(entry);
+ if (length >= 0)
+ osd_printf_info("%10u", unsigned(uint64_t(length)));
+ else
+ osd_printf_info("%10s", "");
+
+ // output the hash data
+ util::hash_collection hashes(std::get<2>(entry));
+ if (!hashes.flag(util::hash_collection::FLAG_NO_DUMP))
+ {
+ if (hashes.flag(util::hash_collection::FLAG_BAD_DUMP))
+ osd_printf_info(" BAD");
+ osd_printf_info(" %s", hashes.macro_string());
+ }
+ else
+ osd_printf_info(" NO GOOD DUMP KNOWN");
+
+ // end with a CR
+ osd_printf_info("\n");
+ }
+ }
+ });
+}
+
+
+//-------------------------------------------------
+// listsamples - output the list of samples
+// referenced by a given game or set of games
+//-------------------------------------------------
+
+void cli_frontend::listsamples(const std::vector<std::string> &args)
+{
+ const char *gamename = args.empty() ? nullptr : args[0].c_str();
+
+ // determine which drivers to output; return an error if none found
+ driver_enumerator drivlist(m_options, gamename);
+ if (drivlist.count() == 0)
+ throw emu_fatalerror(EMU_ERR_NO_SUCH_SYSTEM, "No matching systems found for '%s'", gamename);
+
+ // iterate over drivers, looking for SAMPLES devices
+ bool first = true;
+ while (drivlist.next())
+ {
+ // see if we have samples
+ samples_device_enumerator iter(drivlist.config()->root_device());
+ if (iter.count() == 0)
+ continue;
+
+ // print a header
+ if (!first)
+ osd_printf_info("\n");
+ first = false;
+ osd_printf_info("Samples required for driver \"%s\".\n", drivlist.driver().name);
+
+ // iterate over samples devices and print the samples from each one
+ for (samples_device &device : iter)
+ {
+ samples_iterator sampiter(device);
+ for (const char *samplename = sampiter.first(); samplename != nullptr; samplename = sampiter.next())
+ osd_printf_info("%s\n", samplename);
+ }
+ }
+}
+
+
+//-------------------------------------------------
+// listdevices - output the list of devices
+// referenced by a given game or set of games
+//-------------------------------------------------
+
+void cli_frontend::listdevices(const std::vector<std::string> &args)
+{
+ const char *gamename = args.empty() ? nullptr : args[0].c_str();
+
+ // determine which drivers to output; return an error if none found
+ driver_enumerator drivlist(m_options, gamename);
+ if (drivlist.count() == 0)
+ throw emu_fatalerror(EMU_ERR_NO_SUCH_SYSTEM, "No matching systems found for '%s'", gamename);
+
+ // iterate over drivers, looking for SAMPLES devices
+ bool first = true;
+ while (drivlist.next())
+ {
+ // print a header
+ if (!first)
+ printf("\n");
+ first = false;
+ printf("Driver %s (%s):\n", drivlist.driver().name, drivlist.driver().type.fullname());
+
+ // build a list of devices
+ std::vector<device_t *> device_list;
+ for (device_t &device : device_enumerator(drivlist.config()->root_device()))
+ device_list.push_back(&device);
+
+ // sort them by tag
+ std::sort(device_list.begin(), device_list.end(), [](device_t *dev1, device_t *dev2) {
+ // end of string < ':' < '0'
+ const char *tag1 = dev1->tag();
+ const char *tag2 = dev2->tag();
+ while (*tag1 == *tag2 && *tag1 != '\0' && *tag2 != '\0')
+ {
+ tag1++;
+ tag2++;
+ }
+ return (*tag1 == ':' ? ' ' : *tag1) < (*tag2 == ':' ? ' ' : *tag2);
+ });
+
+ // dump the results
+ for (auto device : device_list)
+ {
+ // extract the tag, stripping the leading colon
+ const char *tag = device->tag();
+ if (*tag == ':')
+ tag++;
+
+ // determine the depth
+ int depth = 1;
+ if (*tag == 0)
+ {
+ tag = "<root>";
+ depth = 0;
+ }
+ else
+ {
+ for (const char *c = tag; *c != 0; c++)
+ if (*c == ':')
+ {
+ tag = c + 1;
+ depth++;
+ }
+ }
+ printf(" %*s%-*s %s", depth * 2, "", 30 - depth * 2, tag, device->name());
+
+ // add more information
+ uint32_t clock = device->clock();
+ if (clock >= 1000000000)
+ printf(" @ %d.%02d GHz\n", clock / 1000000000, (clock / 10000000) % 100);
+ else if (clock >= 1000000)
+ printf(" @ %d.%02d MHz\n", clock / 1000000, (clock / 10000) % 100);
+ else if (clock >= 1000)
+ printf(" @ %d.%02d kHz\n", clock / 1000, (clock / 10) % 100);
+ else if (clock > 0)
+ printf(" @ %d Hz\n", clock);
+ else
+ printf("\n");
+ }
+ }
+}
+
+
+//-------------------------------------------------
+// listslots - output the list of slot devices
+// referenced by a given game or set of games
+//-------------------------------------------------
+
+void cli_frontend::listslots(const std::vector<std::string> &args)
+{
+ const char *gamename = args.empty() ? nullptr : args[0].c_str();
+
+ // determine which drivers to output; return an error if none found
+ driver_enumerator drivlist(m_options, gamename);
+ if (drivlist.count() == 0)
+ throw emu_fatalerror(EMU_ERR_NO_SUCH_SYSTEM, "No matching systems found for '%s'", gamename);
+
+ // print header
+ printf("%-16s %-16s %-16s %s\n", "SYSTEM", "SLOT NAME", "SLOT OPTIONS", "SLOT DEVICE NAME");
+ printf("%s %s %s %s\n", std::string(16,'-').c_str(), std::string(16,'-').c_str(), std::string(16,'-').c_str(), std::string(28,'-').c_str());
+
+ // iterate over drivers
+ while (drivlist.next())
+ {
+ // iterate
+ bool first = true;
+ for (const device_slot_interface &slot : slot_interface_enumerator(drivlist.config()->root_device()))
+ {
+ if (slot.fixed()) continue;
+
+ // build a list of user-selectable options
+ std::vector<device_slot_interface::slot_option const *> option_list;
+ for (auto &option : slot.option_list())
+ if (option.second->selectable())
+ option_list.push_back(option.second.get());
+
+ // sort them by name
+ std::sort(option_list.begin(), option_list.end(), [](device_slot_interface::slot_option const *opt1, device_slot_interface::slot_option const *opt2) {
+ return strcmp(opt1->name(), opt2->name()) < 0;
+ });
+
+
+ // output the line, up to the list of extensions
+ printf("%-16s %-16s ", first ? drivlist.driver().name : "", slot.device().tag()+1);
+
+ bool first_option = true;
+
+ // get the options and print them
+ for (device_slot_interface::slot_option const *opt : option_list)
+ {
+ if (first_option)
+ printf("%-16s %s\n", opt->name(), opt->devtype().fullname());
+ else
+ printf("%-34s%-16s %s\n", "", opt->name(), opt->devtype().fullname());
+
+ first_option = false;
+ }
+ if (first_option)
+ printf("%-16s %s\n", "[none]","No options available");
+ // end the line
+ printf("\n");
+ first = false;
+ }
+
+ // if we didn't get any at all, just print a none line
+ if (first)
+ printf("%-16s (none)\n", drivlist.driver().name);
+ }
+}
+
+
+//-------------------------------------------------
+// listmedia - output the list of image devices
+// referenced by a given game or set of games
+//-------------------------------------------------
+
+void cli_frontend::listmedia(const std::vector<std::string> &args)
+{
+ const char *gamename = args.empty() ? nullptr : args[0].c_str();
+
+ // determine which drivers to output; return an error if none found
+ driver_enumerator drivlist(m_options, gamename);
+ if (drivlist.count() == 0)
+ throw emu_fatalerror(EMU_ERR_NO_SUCH_SYSTEM, "No matching systems found for '%s'", gamename);
+
+ // print header
+ printf("%-16s %-16s %-10s %s\n", "SYSTEM", "MEDIA NAME", "(brief)", "IMAGE FILE EXTENSIONS SUPPORTED");
+ printf("%s %s-%s %s\n", std::string(16,'-').c_str(), std::string(16,'-').c_str(), std::string(10,'-').c_str(), std::string(31,'-').c_str());
+
+ // iterate over drivers
+ while (drivlist.next())
+ {
+ // iterate
+ bool first = true;
+ for (const device_image_interface &imagedev : image_interface_enumerator(drivlist.config()->root_device()))
+ {
+ if (!imagedev.user_loadable())
+ continue;
+
+ // extract the shortname with parentheses
+ std::string paren_shortname = string_format("(%s)", imagedev.brief_instance_name());
+
+ // output the line, up to the list of extensions
+ printf("%-16s %-16s %-10s ", drivlist.driver().name, imagedev.instance_name().c_str(), paren_shortname.c_str());
+
+ // get the extensions and print them
+ std::string extensions(imagedev.file_extensions());
+ for (int start = 0, end = extensions.find_first_of(',');; start = end + 1, end = extensions.find_first_of(',', start))
+ {
+ std::string curext(extensions, start, (end == -1) ? extensions.length() - start : end - start);
+ printf(".%-5s", curext.c_str());
+ if (end == -1)
+ break;
+ }
+
+ // end the line
+ printf("\n");
+ first = false;
+ }
+
+ // if we didn't get any at all, just print a none line
+ if (first)
+ printf("%-16s (none)\n", drivlist.driver().name);
+ }
+}
+
+//-------------------------------------------------
+// verifyroms - verify the ROM sets of one or
+// more games
+//-------------------------------------------------
+void cli_frontend::verifyroms(const std::vector<std::string> &args)
+{
+ bool const iswild((1U != args.size()) || core_iswildstr(args[0].c_str()));
+ std::vector<bool> matched(args.size(), false);
+ unsigned matchcount = 0;
+ auto const included = [&args, &matched, &matchcount] (char const *name) -> bool
+ {
+ if (args.empty())
+ {
+ ++matchcount;
+ return true;
+ }
+
+ bool result = false;
+ auto it = matched.begin();
+ for (std::string const &pat : args)
+ {
+ if (!core_strwildcmp(pat.c_str(), name))
+ {
+ ++matchcount;
+ result = true;
+ *it = true;
+ }
+ ++it;
+ }
+ return result;
+ };
+
+ unsigned correct = 0;
+ unsigned incorrect = 0;
+ unsigned notfound = 0;
+
+ // iterate over drivers
+ driver_enumerator drivlist(m_options);
+ media_auditor auditor(drivlist);
+ util::ovectorstream summary_string;
+ while (drivlist.next())
+ {
+ if (included(drivlist.driver().name))
+ {
+ // audit the ROMs in this set
+ media_auditor::summary summary = auditor.audit_media(AUDIT_VALIDATE_FAST);
+
+ auto const clone_of = drivlist.clone();
+ print_summary(
+ auditor, summary, true,
+ "rom", drivlist.driver().name, (clone_of >= 0) ? drivlist.driver(clone_of).name : nullptr,
+ correct, incorrect, notfound,
+ summary_string);
+
+ // if it wasn't a wildcard, there can only be one
+ if (!iswild)
+ break;
+ }
+ }
+
+ if (iswild || !matchcount)
+ {
+ machine_config config(GAME_NAME(___empty), m_options);
+ machine_config::token const tok(config.begin_configuration(config.root_device()));
+ for (device_type type : registered_device_types)
+ {
+ if (included(type.shortname()))
+ {
+ // audit the ROMs in this set
+ device_t *const dev = config.device_add("_tmp", type, 0);
+ media_auditor::summary summary = auditor.audit_device(*dev, AUDIT_VALIDATE_FAST);
+
+ print_summary(
+ auditor, summary, false,
+ "rom", dev->shortname(), nullptr,
+ correct, incorrect, notfound,
+ summary_string);
+ config.device_remove("_tmp");
+
+ // if it wasn't a wildcard, there can only be one
+ if (!iswild)
+ break;
+ }
+ }
+ }
+
+ // clear out any cached files
+ util::archive_file::cache_clear();
+
+ // return an error if none found
+ auto it = matched.begin();
+ for (std::string const &pat : args)
+ {
+ if (!*it)
+ throw emu_fatalerror(EMU_ERR_NO_SUCH_SYSTEM, "No matching systems found for '%s'", pat);
+
+ ++it;
+ }
+
+ if ((1U == args.size()) && (matchcount > 0) && (correct == 0) && (incorrect == 0))
+ {
+ // if we didn't get anything at all, display a generic end message
+ if (notfound > 0)
+ throw emu_fatalerror(EMU_ERR_MISSING_FILES, "romset \"%s\" not found!\n", args[0]);
+ else
+ throw emu_fatalerror(EMU_ERR_MISSING_FILES, "romset \"%s\" has no roms!\n", args[0]);
+ }
+ else
+ {
+ // otherwise, print a summary
+ if (incorrect > 0)
+ throw emu_fatalerror(EMU_ERR_MISSING_FILES, "%u romsets found, %u were OK.\n", correct + incorrect, correct);
+ else
+ osd_printf_info("%u romsets found, %u were OK.\n", correct, correct);
+ }
+}
+
+
+//-------------------------------------------------
+// info_verifysamples - verify the sample sets of
+// one or more games
+//-------------------------------------------------
+
+void cli_frontend::verifysamples(const std::vector<std::string> &args)
+{
+ const char *gamename = args.empty() ? "*" : args[0].c_str();
+
+ // determine which drivers to output; return an error if none found
+ driver_enumerator drivlist(m_options, gamename);
+
+ unsigned correct = 0;
+ unsigned incorrect = 0;
+ unsigned notfound = 0;
+ unsigned matched = 0;
+
+ // iterate over drivers
+ media_auditor auditor(drivlist);
+ util::ovectorstream summary_string;
+ while (drivlist.next())
+ {
+ matched++;
+
+ // audit the samples in this set
+ media_auditor::summary summary = auditor.audit_samples();
+
+ auto const clone_of = drivlist.clone();
+ print_summary(
+ auditor, summary, false,
+ "sample", drivlist.driver().name, (clone_of >= 0) ? drivlist.driver(clone_of).name : nullptr,
+ correct, incorrect, notfound,
+ summary_string);
+ }
+
+ // clear out any cached files
+ util::archive_file::cache_clear();
+
+ // return an error if none found
+ if (matched == 0)
+ throw emu_fatalerror(EMU_ERR_NO_SUCH_SYSTEM, "No matching systems found for '%s'", gamename);
+
+ // if we didn't get anything at all, display a generic end message
+ if (matched > 0 && correct == 0 && incorrect == 0)
+ {
+ if (notfound > 0)
+ throw emu_fatalerror(EMU_ERR_MISSING_FILES, "sampleset \"%s\" not found!\n", gamename);
+ else
+ throw emu_fatalerror(EMU_ERR_MISSING_FILES, "sampleset \"%s\" not required!\n", gamename);
+ }
+
+ // otherwise, print a summary
+ else
+ {
+ if (incorrect > 0)
+ throw emu_fatalerror(EMU_ERR_MISSING_FILES, "%u samplesets found, %u were OK.\n", correct + incorrect, correct);
+ osd_printf_info("%u samplesets found, %u were OK.\n", correct, correct);
+ }
+}
+
+const char cli_frontend::s_softlist_xml_dtd[] =
+ "<?xml version=\"1.0\"?>\n" \
+ "<!DOCTYPE softwarelists [\n" \
+ "<!ELEMENT softwarelists (softwarelist*)>\n" \
+ "\t<!ELEMENT softwarelist (notes?, software+)>\n" \
+ "\t\t<!ATTLIST softwarelist name CDATA #REQUIRED>\n" \
+ "\t\t<!ATTLIST softwarelist description CDATA #IMPLIED>\n" \
+ "\t\t<!ELEMENT notes (#PCDATA)>\n" \
+ "\t\t<!ELEMENT software (description, year, publisher, notes?, info*, sharedfeat*, part*)>\n" \
+ "\t\t\t<!ATTLIST software name CDATA #REQUIRED>\n" \
+ "\t\t\t<!ATTLIST software cloneof CDATA #IMPLIED>\n" \
+ "\t\t\t<!ATTLIST software supported (yes|partial|no) \"yes\">\n" \
+ "\t\t\t<!ELEMENT description (#PCDATA)>\n" \
+ "\t\t\t<!ELEMENT year (#PCDATA)>\n" \
+ "\t\t\t<!ELEMENT publisher (#PCDATA)>\n" \
+ "\t\t\t<!ELEMENT notes (#PCDATA)>\n" \
+ "\t\t\t<!ELEMENT info EMPTY>\n" \
+ "\t\t\t\t<!ATTLIST info name CDATA #REQUIRED>\n" \
+ "\t\t\t\t<!ATTLIST info value CDATA #IMPLIED>\n" \
+ "\t\t\t<!ELEMENT sharedfeat EMPTY>\n" \
+ "\t\t\t\t<!ATTLIST sharedfeat name CDATA #REQUIRED>\n" \
+ "\t\t\t\t<!ATTLIST sharedfeat value CDATA #IMPLIED>\n" \
+ "\t\t\t<!ELEMENT part (feature*, dataarea*, diskarea*, dipswitch*)>\n" \
+ "\t\t\t\t<!ATTLIST part name CDATA #REQUIRED>\n" \
+ "\t\t\t\t<!ATTLIST part interface CDATA #REQUIRED>\n" \
+ "\t\t\t\t<!ELEMENT feature EMPTY>\n" \
+ "\t\t\t\t\t<!ATTLIST feature name CDATA #REQUIRED>\n" \
+ "\t\t\t\t\t<!ATTLIST feature value CDATA #IMPLIED>\n" \
+ "\t\t\t\t<!ELEMENT dataarea (rom*)>\n" \
+ "\t\t\t\t\t<!ATTLIST dataarea name CDATA #REQUIRED>\n" \
+ "\t\t\t\t\t<!ATTLIST dataarea size CDATA #REQUIRED>\n" \
+ "\t\t\t\t\t<!ATTLIST dataarea databits (8|16|32|64) \"8\">\n" \
+ "\t\t\t\t\t<!ATTLIST dataarea endian (big|little) \"little\">\n" \
+ "\t\t\t\t\t<!ELEMENT rom EMPTY>\n" \
+ "\t\t\t\t\t\t<!ATTLIST rom name CDATA #IMPLIED>\n" \
+ "\t\t\t\t\t\t<!ATTLIST rom size CDATA #IMPLIED>\n" \
+ "\t\t\t\t\t\t<!ATTLIST rom length CDATA #IMPLIED>\n" \
+ "\t\t\t\t\t\t<!ATTLIST rom crc CDATA #IMPLIED>\n" \
+ "\t\t\t\t\t\t<!ATTLIST rom sha1 CDATA #IMPLIED>\n" \
+ "\t\t\t\t\t\t<!ATTLIST rom offset CDATA #IMPLIED>\n" \
+ "\t\t\t\t\t\t<!ATTLIST rom value CDATA #IMPLIED>\n" \
+ "\t\t\t\t\t\t<!ATTLIST rom status (baddump|nodump|good) \"good\">\n" \
+ "\t\t\t\t\t\t<!ATTLIST rom loadflag (load16_byte|load16_word|load16_word_swap|load32_byte|load32_word|load32_word_swap|load32_dword|load64_word|load64_word_swap|reload|fill|continue|reload_plain) #IMPLIED>\n" \
+ "\t\t\t\t<!ELEMENT diskarea (disk*)>\n" \
+ "\t\t\t\t\t<!ATTLIST diskarea name CDATA #REQUIRED>\n" \
+ "\t\t\t\t\t<!ELEMENT disk EMPTY>\n" \
+ "\t\t\t\t\t\t<!ATTLIST disk name CDATA #REQUIRED>\n" \
+ "\t\t\t\t\t\t<!ATTLIST disk sha1 CDATA #IMPLIED>\n" \
+ "\t\t\t\t\t\t<!ATTLIST disk status (baddump|nodump|good) \"good\">\n" \
+ "\t\t\t\t\t\t<!ATTLIST disk writeable (yes|no) \"no\">\n" \
+ "\t\t\t\t<!ELEMENT dipswitch (dipvalue*)>\n" \
+ "\t\t\t\t\t<!ATTLIST dipswitch name CDATA #REQUIRED>\n" \
+ "\t\t\t\t\t<!ATTLIST dipswitch tag CDATA #REQUIRED>\n" \
+ "\t\t\t\t\t<!ATTLIST dipswitch mask CDATA #REQUIRED>\n" \
+ "\t\t\t\t\t<!ELEMENT dipvalue EMPTY>\n" \
+ "\t\t\t\t\t\t<!ATTLIST dipvalue name CDATA #REQUIRED>\n" \
+ "\t\t\t\t\t\t<!ATTLIST dipvalue value CDATA #REQUIRED>\n" \
+ "\t\t\t\t\t\t<!ATTLIST dipvalue default (yes|no) \"no\">\n" \
+ "]>\n\n";
+
+void cli_frontend::output_single_softlist(std::ostream &out, software_list_device &swlistdev)
+{
+ util::stream_format(out, "\t<softwarelist name=\"%s\" description=\"%s\">\n", swlistdev.list_name(), util::xml::normalize_string(swlistdev.description().c_str()));
+ for (const software_info &swinfo : swlistdev.get_info())
+ {
+ util::stream_format(out, "\t\t<software name=\"%s\"", util::xml::normalize_string(swinfo.shortname().c_str()));
+ if (!swinfo.parentname().empty())
+ util::stream_format(out, " cloneof=\"%s\"", util::xml::normalize_string(swinfo.parentname().c_str()));
+ if (swinfo.supported() == software_support::PARTIALLY_SUPPORTED)
+ out << " supported=\"partial\"";
+ else if (swinfo.supported() == software_support::UNSUPPORTED)
+ out << " supported=\"no\"";
+ out << ">\n";
+ util::stream_format(out, "\t\t\t<description>%s</description>\n", util::xml::normalize_string(swinfo.longname().c_str()));
+ util::stream_format(out, "\t\t\t<year>%s</year>\n", util::xml::normalize_string(swinfo.year().c_str()));
+ util::stream_format(out, "\t\t\t<publisher>%s</publisher>\n", util::xml::normalize_string(swinfo.publisher().c_str()));
+
+ for (const auto &flist : swinfo.info())
+ util::stream_format(out, "\t\t\t<info name=\"%s\" value=\"%s\"/>\n", flist.name(), util::xml::normalize_string(flist.value().c_str()));
+
+ for (const auto &flist : swinfo.shared_features())
+ util::stream_format(out, "\t\t\t<sharedfeat name=\"%s\" value=\"%s\"/>\n", flist.name(), util::xml::normalize_string(flist.value().c_str()));
+
+ for (const software_part &part : swinfo.parts())
+ {
+ util::stream_format(out, "\t\t\t<part name=\"%s\"", util::xml::normalize_string(part.name().c_str()));
+ if (!part.interface().empty())
+ util::stream_format(out, " interface=\"%s\"", util::xml::normalize_string(part.interface().c_str()));
+
+ out << ">\n";
+
+ for (const auto &flist : part.features())
+ util::stream_format(out, "\t\t\t\t<feature name=\"%s\" value=\"%s\" />\n", flist.name(), util::xml::normalize_string(flist.value().c_str()));
+
+ // TODO: display ROM region information
+ for (const rom_entry *region = part.romdata().data(); region; region = rom_next_region(region))
+ {
+ int is_disk = ROMREGION_ISDISKDATA(region);
+
+ if (!is_disk)
+ util::stream_format(out, "\t\t\t\t<dataarea name=\"%s\" size=\"%d\">\n", util::xml::normalize_string(region->name().c_str()), region->get_length());
+ else
+ util::stream_format(out, "\t\t\t\t<diskarea name=\"%s\">\n", util::xml::normalize_string(region->name().c_str()));
+
+ for (const rom_entry *rom = rom_first_file(region); rom && !ROMENTRY_ISREGIONEND(rom); rom++)
+ {
+ if (ROMENTRY_ISFILE(rom))
+ {
+ if (!is_disk)
+ util::stream_format(out, "\t\t\t\t\t<rom name=\"%s\" size=\"%d\"", util::xml::normalize_string(ROM_GETNAME(rom)), rom_file_size(rom));
+ else
+ util::stream_format(out, "\t\t\t\t\t<disk name=\"%s\"", util::xml::normalize_string(ROM_GETNAME(rom)));
+
+ // dump checksum information only if there is a known dump
+ util::hash_collection hashes(rom->hashdata());
+ if (!hashes.flag(util::hash_collection::FLAG_NO_DUMP))
+ util::stream_format(out, " %s", hashes.attribute_string());
+ else
+ out << " status=\"nodump\"";
+
+ if (is_disk)
+ util::stream_format(out, " writeable=\"%s\"", (ROM_GETFLAGS(rom) & DISK_READONLYMASK) ? "no" : "yes");
+
+ if ((ROM_GETFLAGS(rom) & ROM_SKIPMASK) == ROM_SKIP(1))
+ out << " loadflag=\"load16_byte\"";
+
+ if ((ROM_GETFLAGS(rom) & ROM_SKIPMASK) == ROM_SKIP(3))
+ out << " loadflag=\"load32_byte\"";
+
+ if (((ROM_GETFLAGS(rom) & ROM_SKIPMASK) == ROM_SKIP(2)) && ((ROM_GETFLAGS(rom) & ROM_GROUPMASK) == ROM_GROUPWORD))
+ {
+ if (!(ROM_GETFLAGS(rom) & ROM_REVERSEMASK))
+ out << " loadflag=\"load32_word\"";
+ else
+ out << " loadflag=\"load32_word_swap\"";
+ }
+
+ if (((ROM_GETFLAGS(rom) & ROM_SKIPMASK) == ROM_SKIP(6)) && ((ROM_GETFLAGS(rom) & ROM_GROUPMASK) == ROM_GROUPWORD))
+ {
+ if (!(ROM_GETFLAGS(rom) & ROM_REVERSEMASK))
+ out << " loadflag=\"load64_word\"";
+ else
+ out << " loadflag=\"load64_word_swap\"";
+ }
+
+ if (((ROM_GETFLAGS(rom) & ROM_SKIPMASK) == ROM_NOSKIP) && ((ROM_GETFLAGS(rom) & ROM_GROUPMASK) == ROM_GROUPWORD))
+ {
+ if (!(ROM_GETFLAGS(rom) & ROM_REVERSEMASK))
+ out << " loadflag=\"load32_dword\"";
+ else
+ out << " loadflag=\"load16_word_swap\"";
+ }
+
+ out << "/>\n";
+ }
+ else if (ROMENTRY_ISRELOAD(rom))
+ {
+ util::stream_format(out, "\t\t\t\t\t<rom size=\"%d\" offset=\"0x%x\" loadflag=\"reload\" />\n", ROM_GETLENGTH(rom), ROM_GETOFFSET(rom));
+ }
+ else if (ROMENTRY_ISFILL(rom))
+ {
+ util::stream_format(out, "\t\t\t\t\t<rom size=\"%d\" offset=\"0x%x\" loadflag=\"fill\" />\n", ROM_GETLENGTH(rom), ROM_GETOFFSET(rom));
+ }
+ }
+
+ if (!is_disk)
+ out << "\t\t\t\t</dataarea>\n";
+ else
+ out << "\t\t\t\t</diskarea>\n";
+ }
+
+ out << "\t\t\t</part>\n";
+ }
+
+ out << "\t\t</software>\n";
+ }
+ out << "\t</softwarelist>\n";
+}
+
+
+/*-------------------------------------------------
+ info_listsoftware - output the list of
+ software supported by a given game or set of
+ games
+ TODO: Add all information read from the source files
+-------------------------------------------------*/
+
+void cli_frontend::listsoftware(const std::vector<std::string> &args)
+{
+ std::unordered_set<std::string> list_map;
+ bool firstlist(true);
+ apply_device_action(
+ args,
+ [this, &list_map, &firstlist] (device_t &root, char const *type, bool first)
+ {
+ for (software_list_device &swlistdev : software_list_device_enumerator(root))
+ {
+ if (list_map.insert(swlistdev.list_name()).second)
+ {
+ if (!swlistdev.get_info().empty())
+ {
+ if (firstlist)
+ {
+ if (m_options.bool_value(CLIOPTION_DTD))
+ std::cout << s_softlist_xml_dtd;
+ std::cout << "<softwarelists>\n";
+ firstlist = false;
+ }
+ output_single_softlist(std::cout, swlistdev);
+ }
+ }
+ }
+ });
+
+ if (!firstlist)
+ std::cout << "</softwarelists>\n";
+ else
+ fprintf(stdout, "No software lists found for this system\n"); // TODO: should this go to stderr instead?
+}
+
+
+/*-------------------------------------------------
+ verifysoftware - verify ROMs from the software
+ list of the specified driver(s)
+-------------------------------------------------*/
+void cli_frontend::verifysoftware(const std::vector<std::string> &args)
+{
+ const char *gamename = args.empty() ? "*" : args[0].c_str();
+
+ std::unordered_set<std::string> list_map;
+
+ unsigned correct = 0;
+ unsigned incorrect = 0;
+ unsigned notfound = 0;
+ unsigned matched = 0;
+ unsigned nrlists = 0;
+
+ // determine which drivers to process; return an error if none found
+ driver_enumerator drivlist(m_options, gamename);
+ if (drivlist.count() == 0)
+ throw emu_fatalerror(EMU_ERR_NO_SUCH_SYSTEM, "No matching systems found for '%s'", gamename);
+
+ media_auditor auditor(drivlist);
+ util::ovectorstream summary_string;
+ while (drivlist.next())
+ {
+ matched++;
+
+ for (software_list_device &swlistdev : software_list_device_enumerator(drivlist.config()->root_device()))
+ {
+ if (swlistdev.is_original())
+ {
+ if (list_map.insert(swlistdev.list_name()).second)
+ {
+ if (!swlistdev.get_info().empty())
+ {
+ nrlists++;
+ for (const software_info &swinfo : swlistdev.get_info())
+ {
+ media_auditor::summary summary = auditor.audit_software(swlistdev, swinfo, AUDIT_VALIDATE_FAST);
+
+ print_summary(
+ auditor, summary, false,
+ "rom", util::string_format("%s:%s", swlistdev.list_name(), swinfo.shortname()).c_str(), nullptr,
+ correct, incorrect, notfound,
+ summary_string);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // clear out any cached files
+ util::archive_file::cache_clear();
+
+ // return an error if none found
+ if (matched == 0)
+ throw emu_fatalerror(EMU_ERR_NO_SUCH_SYSTEM, "No matching systems found for '%s'", gamename);
+
+ // if we didn't get anything at all, display a generic end message
+ if (matched > 0 && correct == 0 && incorrect == 0)
+ {
+ throw emu_fatalerror(EMU_ERR_MISSING_FILES, "romset \"%s\" has no software entries defined!\n", gamename);
+ }
+ // otherwise, print a summary
+ else
+ {
+ if (incorrect > 0)
+ throw emu_fatalerror(EMU_ERR_MISSING_FILES, "%u romsets found in %u software lists, %u were OK.\n", correct + incorrect, nrlists, correct);
+ osd_printf_info("%u romsets found in %u software lists, %u romsets were OK.\n", correct, nrlists, correct);
+ }
+
+}
+
+
+/*-------------------------------------------------
+ getsoftlist - retrieve software list by name
+-------------------------------------------------*/
+
+void cli_frontend::getsoftlist(const std::vector<std::string> &args)
+{
+ const char *gamename = args.empty() ? "*" : args[0].c_str();
+
+ std::unordered_set<std::string> list_map;
+ bool firstlist(true);
+ apply_device_action(
+ std::vector<std::string>(),
+ [this, gamename, &list_map, &firstlist] (device_t &root, char const *type, bool first)
+ {
+ for (software_list_device &swlistdev : software_list_device_enumerator(root))
+ {
+ if (core_strwildcmp(gamename, swlistdev.list_name().c_str()) == 0 && list_map.insert(swlistdev.list_name()).second)
+ {
+ if (!swlistdev.get_info().empty())
+ {
+ if (firstlist)
+ {
+ if (m_options.bool_value(CLIOPTION_DTD))
+ std::cout << s_softlist_xml_dtd;
+ std::cout << "<softwarelists>\n";
+ firstlist = false;
+ }
+ output_single_softlist(std::cout, swlistdev);
+ }
+ }
+ }
+ });
+
+ if (!firstlist)
+ std::cout << "</softwarelists>\n";
+ else
+ fprintf(stdout, "No such software lists found\n"); // TODO: should this go to stderr instead?
+}
+
+
+/*-------------------------------------------------
+ verifysoftlist - verify software list by name
+-------------------------------------------------*/
+void cli_frontend::verifysoftlist(const std::vector<std::string> &args)
+{
+ const char *gamename = args.empty() ? "*" : args[0].c_str();
+
+ std::unordered_set<std::string> list_map;
+ unsigned correct = 0;
+ unsigned incorrect = 0;
+ unsigned notfound = 0;
+ unsigned matched = 0;
+
+ driver_enumerator drivlist(m_options);
+ media_auditor auditor(drivlist);
+ util::ovectorstream summary_string;
+
+ while (drivlist.next())
+ {
+ for (software_list_device &swlistdev : software_list_device_enumerator(drivlist.config()->root_device()))
+ {
+ if (core_strwildcmp(gamename, swlistdev.list_name().c_str()) == 0 && list_map.insert(swlistdev.list_name()).second)
+ {
+ if (!swlistdev.get_info().empty())
+ {
+ matched++;
+
+ // Get the actual software list contents
+ for (const software_info &swinfo : swlistdev.get_info())
+ {
+ media_auditor::summary summary = auditor.audit_software(swlistdev, swinfo, AUDIT_VALIDATE_FAST);
+
+ print_summary(
+ auditor, summary, false,
+ "rom", util::string_format("%s:%s", swlistdev.list_name(), swinfo.shortname()).c_str(), nullptr,
+ correct, incorrect, notfound,
+ summary_string);
+ }
+ }
+ }
+ }
+ }
+
+ // clear out any cached files
+ util::archive_file::cache_clear();
+
+ // return an error if none found
+ if (matched == 0)
+ throw emu_fatalerror(EMU_ERR_NO_SUCH_SYSTEM, "No matching software lists found for '%s'", gamename);
+
+ // if we didn't get anything at all, display a generic end message
+ if (matched > 0 && correct == 0 && incorrect == 0)
+ {
+ throw emu_fatalerror(EMU_ERR_MISSING_FILES, "no romsets found for software list \"%s\"!\n", gamename);
+ }
+ // otherwise, print a summary
+ else
+ {
+ if (incorrect > 0)
+ throw emu_fatalerror(EMU_ERR_MISSING_FILES, "%u romsets found in %u software lists, %u were OK.\n", correct + incorrect, matched, correct);
+ osd_printf_info("%u romsets found in %u software lists, %u romsets were OK.\n", correct, matched, correct);
+ }
+}
+
+
+//-------------------------------------------------
+// version - emit MAME version to stdout
+//-------------------------------------------------
+
+void cli_frontend::version(const std::vector<std::string> &args)
+{
+ osd_printf_info("%s", emulator_info::get_build_version());
+}
+
+
+//-------------------------------------------------
+// romident - identify ROMs by looking for
+// matches in our internal database
+//-------------------------------------------------
+
+void cli_frontend::romident(const std::vector<std::string> &args)
+{
+ const char *filename = args[0].c_str();
+
+ // create our own copy of options for the purposes of ROM identification
+ // so we are not "polluted" with driver-specific slot/image options
+ emu_options options;
+ options.set_value(OPTION_HASHPATH, m_options.hash_path(), OPTION_PRIORITY_DEFAULT);
+
+ media_identifier ident(options);
+
+ // identify the file, then output results
+ osd_printf_info("Identifying %s....\n", filename);
+ ident.identify(filename);
+
+ // return the appropriate error code
+ if (ident.total() == 0)
+ throw emu_fatalerror(EMU_ERR_MISSING_FILES, "No files found.\n");
+ else if (ident.matches() == ident.total())
+ return;
+ else if (ident.matches() == ident.total() - ident.nonroms())
+ throw emu_fatalerror(EMU_ERR_IDENT_NONROMS, "Out of %d files, %d matched, %d are not roms.\n", ident.total(), ident.matches(), ident.nonroms());
+ else if (ident.matches() > 0)
+ throw emu_fatalerror(EMU_ERR_IDENT_PARTIAL, "Out of %d files, %d matched, %d did not match.\n", ident.total(), ident.matches(), ident.total() - ident.matches());
+ else
+ throw emu_fatalerror(EMU_ERR_IDENT_NONE, "No roms matched.\n");
+}
+
+
+//-------------------------------------------------
+// apply_action - apply action to matching
+// systems/devices
+//-------------------------------------------------
+
+template <typename T, typename U> void cli_frontend::apply_action(const std::vector<std::string> &args, T &&drvact, U &&devact)
+
+{
+ bool const iswild((1U != args.size()) || core_iswildstr(args[0].c_str()));
+ std::vector<bool> matched(args.size(), false);
+ auto const included = [&args, &matched] (char const *name) -> bool
+ {
+ if (args.empty())
+ return true;
+
+ bool result = false;
+ auto it = matched.begin();
+ for (std::string const &pat : args)
+ {
+ if (!core_strwildcmp(pat.c_str(), name))
+ {
+ result = true;
+ *it = true;
+ }
+ ++it;
+ }
+ return result;
+ };
+
+ // determine which drivers to output
+ driver_enumerator drivlist(m_options);
+
+ // iterate through matches
+ bool first(true);
+ while (drivlist.next())
+ {
+ if (included(drivlist.driver().name))
+ {
+ drvact(drivlist, first);
+ first = false;
+
+ // if it wasn't a wildcard, there can only be one
+ if (!iswild)
+ break;
+ }
+ }
+
+ if (iswild || first)
+ {
+ for (device_type type : registered_device_types)
+ {
+ if (included(type.shortname()))
+ {
+ devact(type, first);
+ first = false;
+
+ // if it wasn't a wildcard, there can only be one
+ if (!iswild)
+ break;
+ }
+ }
+ }
+
+ // return an error if none found
+ auto it = matched.begin();
+ for (std::string const &pat : args)
+ {
+ if (!*it)
+ throw emu_fatalerror(EMU_ERR_NO_SUCH_SYSTEM, "No matching systems found for '%s'", pat);
+
+ ++it;
+ }
+}
+
+
+//-------------------------------------------------
+// apply_device_action - apply action to matching
+// systems/devices
+//-------------------------------------------------
+
+template <typename T> void cli_frontend::apply_device_action(const std::vector<std::string> &args, T &&action)
+{
+ machine_config config(GAME_NAME(___empty), m_options);
+ machine_config::token const tok(config.begin_configuration(config.root_device()));
+ apply_action(
+ args,
+ [&action] (driver_enumerator &drivlist, bool first)
+ {
+ action(drivlist.config()->root_device(), "driver", first);
+ },
+ [&action, &config] (device_type type, bool first)
+ {
+ device_t *const dev = config.device_add("_tmp", type, 0);
+ action(*dev, "device", first);
+ config.device_remove("_tmp");
+ });
+}
+
+
+//-------------------------------------------------
+// find_command
+//-------------------------------------------------
+
+const cli_frontend::info_command_struct *cli_frontend::find_command(const std::string &s)
+{
+ static const info_command_struct s_info_commands[] =
+ {
+ { CLICOMMAND_LISTXML, 0, -1, &cli_frontend::listxml, "[pattern] ..." },
+ { CLICOMMAND_LISTFULL, 0, -1, &cli_frontend::listfull, "[pattern] ..." },
+ { CLICOMMAND_LISTSOURCE, 0, -1, &cli_frontend::listsource, "[system name]" },
+ { CLICOMMAND_LISTCLONES, 0, 1, &cli_frontend::listclones, "[system name]" },
+ { CLICOMMAND_LISTBROTHERS, 0, 1, &cli_frontend::listbrothers, "[system name]" },
+ { CLICOMMAND_LISTCRC, 0, -1, &cli_frontend::listcrc, "[system name]" },
+ { CLICOMMAND_LISTDEVICES, 0, 1, &cli_frontend::listdevices, "[system name]" },
+ { CLICOMMAND_LISTSLOTS, 0, 1, &cli_frontend::listslots, "[system name]" },
+ { CLICOMMAND_LISTROMS, 0, -1, &cli_frontend::listroms, "[pattern] ..." },
+ { CLICOMMAND_LISTSAMPLES, 0, 1, &cli_frontend::listsamples, "[system name]" },
+ { CLICOMMAND_VERIFYROMS, 0, -1, &cli_frontend::verifyroms, "[pattern] ..." },
+ { CLICOMMAND_VERIFYSAMPLES, 0, 1, &cli_frontend::verifysamples, "[system name|*]" },
+ { CLICOMMAND_LISTMEDIA, 0, 1, &cli_frontend::listmedia, "[system name]" },
+ { CLICOMMAND_LISTSOFTWARE, 0, 1, &cli_frontend::listsoftware, "[system name]" },
+ { CLICOMMAND_VERIFYSOFTWARE, 0, 1, &cli_frontend::verifysoftware, "[system name|*]" },
+ { CLICOMMAND_ROMIDENT, 1, 1, &cli_frontend::romident, "(file or directory path)" },
+ { CLICOMMAND_GETSOFTLIST, 0, 1, &cli_frontend::getsoftlist, "[system name|*]" },
+ { CLICOMMAND_VERIFYSOFTLIST, 0, 1, &cli_frontend::verifysoftlist, "[system name|*]" },
+ { CLICOMMAND_VERSION, 0, 0, &cli_frontend::version, "" }
+ };
+
+ for (const auto &info_command : s_info_commands)
+ {
+ if (s == info_command.option)
+ return &info_command;
+ }
+ return nullptr;
+}
+
+
+//-------------------------------------------------
+// execute_commands - execute various frontend
+// commands
+//-------------------------------------------------
+
+void cli_frontend::execute_commands(std::string_view exename)
+{
+ // help?
+ if (m_options.command() == CLICOMMAND_HELP)
+ {
+ display_help(exename);
+ return;
+ }
+
+ // showusage?
+ if (m_options.command() == CLICOMMAND_SHOWUSAGE)
+ {
+ osd_printf_info("Usage: %s [machine] [media] [software] [options]",exename);
+ osd_printf_info("\n\nOptions:\n%s", m_options.output_help());
+ return;
+ }
+
+ // validate?
+ if (m_options.command() == CLICOMMAND_VALIDATE)
+ {
+ if (m_options.command_arguments().size() > 1)
+ {
+ osd_printf_error("Auxiliary verb -validate takes at most 1 argument\n");
+ return;
+ }
+ validity_checker valid(m_options, false);
+ const char *sysname = m_options.command_arguments().empty() ? nullptr : m_options.command_arguments()[0].c_str();
+ bool result = valid.check_all_matching(sysname);
+ if (!result)
+ throw emu_fatalerror(EMU_ERR_FAILED_VALIDITY, "Validity check failed (%d errors, %d warnings in total)\n", valid.errors(), valid.warnings());
+ return;
+ }
+
+ // other commands need the INIs parsed
+ std::ostringstream option_errors;
+ mame_options::parse_standard_inis(m_options,option_errors);
+ if (option_errors.tellp() > 0)
+ osd_printf_error("%s\n", option_errors.str());
+
+ // createconfig?
+ if (m_options.command() == CLICOMMAND_CREATECONFIG)
+ {
+ // attempt to open the output file
+ emu_file file(OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS);
+ if (file.open(std::string(emulator_info::get_configname()) + ".ini"))
+ throw emu_fatalerror("Unable to create file %s.ini\n",emulator_info::get_configname());
+
+ // generate the updated INI
+ file.puts(m_options.output_ini());
+
+ ui_options ui_opts;
+ emu_file file_ui(OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS);
+ if (file_ui.open("ui.ini"))
+ throw emu_fatalerror("Unable to create file ui.ini\n");
+
+ // generate the updated INI
+ file_ui.puts(ui_opts.output_ini());
+
+ plugin_options plugin_opts;
+ path_iterator iter(m_options.plugins_path());
+ std::string pluginpath;
+ while (iter.next(pluginpath))
+ {
+ osd_subst_env(pluginpath, pluginpath);
+ plugin_opts.scan_directory(pluginpath, true);
+ }
+ emu_file file_plugin(OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS);
+ if (file_plugin.open("plugin.ini"))
+ throw emu_fatalerror("Unable to create file plugin.ini\n");
+
+ // generate the updated INI
+ file_plugin.puts(plugin_opts.output_ini());
+
+ return;
+ }
+
+ // showconfig?
+ if (m_options.command() == CLICOMMAND_SHOWCONFIG)
+ {
+ // print the INI text
+ printf("%s\n", m_options.output_ini().c_str());
+ return;
+ }
+
+ // all other commands call out to one of the info_commands helpers; first
+ // find the command
+ const auto *info_command = find_command(m_options.command());
+ if (info_command)
+ {
+ // validate argument count
+ const char *error_message = nullptr;
+ if (m_options.command_arguments().size() < info_command->min_args)
+ error_message = "Auxiliary verb -%s requires at least %d argument(s)\n";
+ if ((info_command->max_args >= 0) && (m_options.command_arguments().size() > info_command->max_args))
+ error_message = "Auxiliary verb -%s takes at most %d argument(s)\n";
+ if (error_message)
+ {
+ osd_printf_info(error_message, info_command->option, info_command->max_args);
+ osd_printf_info("\n");
+ osd_printf_info("Usage: %s -%s %s\n", exename, info_command->option, info_command->usage);
+ return;
+ }
+
+ // invoke the auxiliary command!
+ (this->*info_command->function)(m_options.command_arguments());
+ return;
+ }
+
+ if (!m_osd.execute_command(m_options.command().c_str()))
+ // if we get here, we don't know what has been requested
+ throw emu_fatalerror(EMU_ERR_INVALID_CONFIG, "Unknown command '%s' specified", m_options.command());
+}
+
+
+//-------------------------------------------------
+// display_help - display help to standard
+// output
+//-------------------------------------------------
+
+// HBMAME - remove reference to software and media.
+void cli_frontend::display_help(std::string_view exename)
+{
+ osd_printf_info(
+ "%3$s v%2$s\n"
+ "%5$s\n"
+ "\n"
+ "This software reproduces, more or less faithfully, the behaviour of a wide range\n"
+ "of machines. But hardware is useless without software, so images of the ROMs and\n"
+ "other media which run on that hardware are also required.\n"
+ "\n"
+ "Usage: %1$s [machine] [options]\n"
+ "\n"
+ " %1$s -showusage for a list of options\n"
+ " %1$s -showconfig to show current configuration in %4$s.ini format\n"
+ " %1$s -createconfig to create a %4$s.ini file\n"
+ "\n",
+ exename,
+ build_version,
+ emulator_info::get_appname(),
+ emulator_info::get_configname(),
+ emulator_info::get_copyright_info());
+}
diff --git a/docs/release/src/frontend/mame/language.cpp b/docs/release/src/frontend/mame/language.cpp
new file mode 100644
index 00000000000..3e660130ffd
--- /dev/null
+++ b/docs/release/src/frontend/mame/language.cpp
@@ -0,0 +1,42 @@
+// license:BSD-3-Clause
+// copyright-holders:Vas Crabb
+/***************************************************************************
+
+ language.cpp
+
+ Multi-language support.
+
+***************************************************************************/
+
+#include "emu.h"
+#include "language.h"
+
+#include "emuopts.h"
+#include "fileio.h"
+
+#include "corestr.h"
+
+#include <string>
+
+
+void load_translation(emu_options &m_options)
+{
+ util::unload_translation();
+
+ std::string name = m_options.language();
+ if (name.empty())
+ return;
+
+ strreplace(name, " ", "_");
+ strreplace(name, "(", "");
+ strreplace(name, ")", "");
+ emu_file file(m_options.language_path(), OPEN_FLAG_READ);
+ if (file.open(name + PATH_SEPARATOR "strings.mo"))
+ {
+ osd_printf_error("Error opening translation file %s\n", name);
+ return;
+ }
+
+ osd_printf_verbose("Loading translation file %s\n", file.fullpath());
+ util::load_translation(file);
+}
diff --git a/docs/release/src/frontend/mame/mameopts.cpp b/docs/release/src/frontend/mame/mameopts.cpp
new file mode 100644
index 00000000000..bbdf8a692b4
--- /dev/null
+++ b/docs/release/src/frontend/mame/mameopts.cpp
@@ -0,0 +1,211 @@
+// 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 "fileio.h"
+#include "screen.h"
+#include "softlist_dev.h"
+#include "zippath.h"
+#include "hashfile.h"
+#include "clifront.h"
+
+#include <cctype>
+#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_enumerator(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 = std::string(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(std::string(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);
+ std::error_condition const filerr = file.open(std::string(basename) + ".ini");
+ if (filerr)
+ 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", 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
diff --git a/docs/release/src/frontend/mame/ui/about.cpp b/docs/release/src/frontend/mame/ui/about.cpp
new file mode 100644
index 00000000000..fae6e6d7038
--- /dev/null
+++ b/docs/release/src/frontend/mame/ui/about.cpp
@@ -0,0 +1,123 @@
+// license:BSD-3-Clause
+// copyright-holders:Vas Crabb
+/***************************************************************************
+
+ ui/about.cpp
+
+ About box
+
+***************************************************************************/
+
+#include "emu.h"
+#include "ui/about.h"
+
+#include "ui/ui.h"
+
+#include "mame.h"
+
+
+namespace ui {
+
+namespace {
+
+#include "copying.ipp"
+
+} // anonymous namespace
+
+
+/**************************************************
+ ABOUT BOX
+**************************************************/
+
+
+//-------------------------------------------------
+// ctor
+//-------------------------------------------------
+
+menu_about::menu_about(mame_ui_manager &mui, render_container &container)
+ : menu_textbox(mui, container)
+ , m_header{
+ util::string_format(
+#ifdef MAME_DEBUG
+ _("about-header", "%1$s %2$s (%3$s%4$sP%5$s, debug)"),
+#else
+ _("about-header", "%1$s %2$s (%3$s%4$sP%5$s)"),
+#endif
+ emulator_info::get_appname(),
+ bare_build_version,
+ (sizeof(int) == sizeof(void *)) ? "I" : "",
+ (sizeof(long) == sizeof(void *)) ? "L" : (sizeof(long long) == sizeof(void *)) ? "LL" : "",
+ sizeof(void *) * 8),
+ util::string_format(_("about-header", "Date: %1$s"), " " __DATE__) } // MESSUI
+{
+ set_process_flags(PROCESS_CUSTOM_NAV);
+}
+
+
+//-------------------------------------------------
+// dtor
+//-------------------------------------------------
+
+menu_about::~menu_about()
+{
+}
+
+
+//-------------------------------------------------
+// perform our special rendering
+//-------------------------------------------------
+
+void menu_about::custom_render(void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2)
+{
+ // draw the title
+ draw_text_box(
+ std::begin(m_header), std::end(m_header),
+ origx1, origx2, origy1 - top, origy1 - ui().box_tb_border(),
+ text_layout::text_justify::CENTER, text_layout::word_wrapping::TRUNCATE, false,
+ ui().colors().text_color(), UI_GREEN_COLOR, 1.0f);
+}
+
+
+//-------------------------------------------------
+// populate_text - populate the about box text
+//-------------------------------------------------
+
+void menu_about::populate_text(std::optional<text_layout> &layout, float &width, int &lines)
+{
+ if (!layout || (layout->width() != width))
+ {
+ rgb_t const color = ui().colors().text_color();
+ layout.emplace(ui().create_layout(container(), width));
+ for (char const *const *line = copying_text; *line; ++line)
+ {
+ layout->add_text(*line, color);
+ layout->add_text("\n", color);
+ }
+ lines = layout->lines();
+ }
+ width = layout->actual_width();
+}
+
+
+//-------------------------------------------------
+// populate - populates the about modal
+//-------------------------------------------------
+
+void menu_about::populate(float &customtop, float &custombottom)
+{
+ // make space for the title and revision
+ customtop = (ui().get_line_height() * m_header.size()) + (ui().box_tb_border() * 3.0f);
+}
+
+
+//-------------------------------------------------
+// handle - manages inputs in the about modal
+//-------------------------------------------------
+
+void menu_about::handle(event const *ev)
+{
+ if (ev)
+ handle_key(ev->iptkey);
+}
+
+} // namespace ui
diff --git a/docs/release/src/frontend/mame/ui/inifile.cpp b/docs/release/src/frontend/mame/ui/inifile.cpp
new file mode 100644
index 00000000000..6cf9886324d
--- /dev/null
+++ b/docs/release/src/frontend/mame/ui/inifile.cpp
@@ -0,0 +1,580 @@
+// license:BSD-3-Clause
+// copyright-holders:Maurizio Petrarota, Vas Crabb
+/***************************************************************************
+
+ ui/inifile.cpp
+
+ UI INIs file manager.
+
+***************************************************************************/
+
+#include "emu.h"
+#include "ui/inifile.h"
+
+#include "ui/moptions.h"
+
+#include "language.h"
+
+#include "drivenum.h"
+#include "fileio.h"
+#include "softlist_dev.h"
+
+#include "corestr.h"
+
+#include <algorithm>
+#include <cstring>
+#include <iterator>
+
+
+namespace {
+
+char const FAVORITE_FILENAME[] = "favorites.ini";
+
+} // anonymous namespace
+
+
+//-------------------------------------------------
+// ctor
+//-------------------------------------------------
+
+inifile_manager::inifile_manager(ui_options &options)
+ : m_options(options)
+ , m_ini_index()
+{
+ // scan directories and create index
+ file_enumerator path(m_options.categoryini_path());
+ for (osd::directory::entry const *dir = path.next(); dir; dir = path.next())
+ {
+ std::string name(dir->name);
+ if (core_filename_ends_with(name, ".ini"))
+ {
+ emu_file file(m_options.categoryini_path(), OPEN_FLAG_READ);
+ if (!file.open(name))
+ {
+ init_category(std::move(name), file);
+ file.close();
+ }
+ }
+ }
+ std::stable_sort(m_ini_index.begin(), m_ini_index.end(), [] (auto const &x, auto const &y) { return 0 > core_stricmp(x.first.c_str(), y.first.c_str()); });
+}
+
+//-------------------------------------------------
+// load and indexing ini files
+//-------------------------------------------------
+
+void inifile_manager::load_ini_category(size_t file, size_t category, std::unordered_set<game_driver const *> &result) const
+{
+ std::string const &filename(m_ini_index[file].first);
+ emu_file fp(m_options.categoryini_path(), OPEN_FLAG_READ);
+ if (fp.open(filename))
+ {
+ osd_printf_error("Failed to open category file %s for reading\n", filename);
+ return;
+ }
+
+ int64_t const offset(m_ini_index[file].second[category].second);
+ if (fp.seek(offset, SEEK_SET) || (fp.tell() != offset))
+ {
+ fp.close();
+ osd_printf_error("Failed to seek to category offset in file %s\n", filename);
+ return;
+ }
+
+ char rbuf[MAX_CHAR_INFO];
+ while (fp.gets(rbuf, MAX_CHAR_INFO) && rbuf[0] && ('[' != rbuf[0]))
+ {
+ auto const tail(std::find_if(std::begin(rbuf), std::prev(std::end(rbuf)), [] (char ch) { return !ch || ('\r' == ch) || ('\n' == ch); }));
+ *tail = '\0';
+ int const dfind(driver_list::find(rbuf));
+ if (0 <= dfind)
+ result.emplace(&driver_list::driver(dfind));
+ }
+
+ fp.close();
+}
+
+//-------------------------------------------------
+// initialize category
+//-------------------------------------------------
+
+void inifile_manager::init_category(std::string &&filename, util::core_file &file)
+{
+ categoryindex index;
+ char rbuf[MAX_CHAR_INFO];
+ std::string name;
+ while (file.gets(rbuf, std::size(rbuf)))
+ {
+ if ('[' == rbuf[0])
+ {
+ auto const head(std::next(std::begin(rbuf)));
+ auto const tail(std::find_if(head, std::end(rbuf), [] (char ch) { return !ch || (']' == ch); }));
+ name.assign(head, tail);
+ if ("FOLDER_SETTINGS" != name)
+ {
+ u64 result;
+ if (!file.tell(result))
+ index.emplace_back(std::move(name), result);
+ }
+ }
+ }
+ std::stable_sort(index.begin(), index.end(), [] (auto const &x, auto const &y) { return 0 > core_stricmp(x.first.c_str(), y.first.c_str()); });
+ if (!index.empty())
+ m_ini_index.emplace_back(std::move(filename), std::move(index));
+}
+
+
+/**************************************************************************
+ FAVORITE MANAGER
+**************************************************************************/
+
+bool favorite_manager::favorite_compare::operator()(ui_software_info const &lhs, ui_software_info const &rhs) const
+{
+ assert(lhs.driver);
+ assert(rhs.driver);
+
+ if (!lhs.startempty)
+ {
+ if (rhs.startempty)
+ return false;
+ else if (lhs.listname < rhs.listname)
+ return true;
+ else if (lhs.listname > rhs.listname)
+ return false;
+ else if (lhs.shortname < rhs.shortname)
+ return true;
+ else if (lhs.shortname > rhs.shortname)
+ return false;
+ }
+ else if (!rhs.startempty)
+ {
+ return true;
+ }
+
+ return 0 > std::strncmp(lhs.driver->name, rhs.driver->name, std::size(lhs.driver->name));
+}
+
+bool favorite_manager::favorite_compare::operator()(ui_software_info const &lhs, game_driver const &rhs) const
+{
+ assert(lhs.driver);
+
+ if (!lhs.startempty)
+ return false;
+ else
+ return 0 > std::strncmp(lhs.driver->name, rhs.name, std::size(rhs.name));
+}
+
+bool favorite_manager::favorite_compare::operator()(game_driver const &lhs, ui_software_info const &rhs) const
+{
+ assert(rhs.driver);
+
+ if (!rhs.startempty)
+ return true;
+ else
+ return 0 > std::strncmp(lhs.name, rhs.driver->name, std::size(lhs.name));
+}
+
+bool favorite_manager::favorite_compare::operator()(ui_software_info const &lhs, running_software_key const &rhs) const
+{
+ assert(lhs.driver);
+ assert(std::get<1>(rhs));
+
+ if (lhs.startempty)
+ return true;
+ else if (lhs.listname < std::get<1>(rhs))
+ return true;
+ else if (lhs.listname > std::get<1>(rhs))
+ return false;
+ else if (lhs.shortname < std::get<2>(rhs))
+ return true;
+ else if (lhs.shortname > std::get<2>(rhs))
+ return false;
+ else
+ return 0 > std::strncmp(lhs.driver->name, std::get<0>(rhs).name, std::size(lhs.driver->name));
+}
+
+bool favorite_manager::favorite_compare::operator()(running_software_key const &lhs, ui_software_info const &rhs) const
+{
+ assert(std::get<1>(lhs));
+ assert(rhs.driver);
+
+ if (rhs.startempty)
+ return false;
+ else if (std::get<1>(lhs) < rhs.listname)
+ return true;
+ else if (std::get<1>(lhs) > rhs.listname)
+ return false;
+ else if (std::get<2>(lhs) < rhs.shortname)
+ return true;
+ else if (std::get<2>(lhs) > rhs.shortname)
+ return false;
+ else
+ return 0 > std::strncmp(std::get<0>(lhs).name, rhs.driver->name, std::size(rhs.driver->name));
+}
+
+
+//-------------------------------------------------
+// construction/destruction
+//-------------------------------------------------
+
+favorite_manager::favorite_manager(ui_options &options)
+ : m_options(options)
+ , m_favorites()
+ , m_sorted()
+ , m_need_sort(true)
+{
+ emu_file file(m_options.ui_path(), OPEN_FLAG_READ);
+ if (!file.open(FAVORITE_FILENAME))
+ {
+ char readbuf[1024];
+ file.gets(readbuf, std::size(readbuf));
+
+ while (readbuf[0] == '[')
+ file.gets(readbuf, std::size(readbuf));
+
+ while (file.gets(readbuf, std::size(readbuf)))
+ {
+ ui_software_info tmpmatches;
+ tmpmatches.shortname = chartrimcarriage(readbuf);
+ file.gets(readbuf, std::size(readbuf));
+ tmpmatches.longname = chartrimcarriage(readbuf);
+ file.gets(readbuf, std::size(readbuf));
+ tmpmatches.parentname = chartrimcarriage(readbuf);
+ file.gets(readbuf, std::size(readbuf));
+ tmpmatches.year = chartrimcarriage(readbuf);
+ file.gets(readbuf, std::size(readbuf));
+ tmpmatches.publisher = chartrimcarriage(readbuf);
+ file.gets(readbuf, std::size(readbuf));
+ tmpmatches.supported = software_support(atoi(readbuf));
+ file.gets(readbuf, std::size(readbuf));
+ tmpmatches.part = chartrimcarriage(readbuf);
+ file.gets(readbuf, std::size(readbuf));
+ chartrimcarriage(readbuf);
+ auto dx = driver_list::find(readbuf);
+ if (0 > dx)
+ continue;
+ tmpmatches.driver = &driver_list::driver(dx);
+ file.gets(readbuf, std::size(readbuf));
+ tmpmatches.listname = chartrimcarriage(readbuf);
+ file.gets(readbuf, std::size(readbuf));
+ tmpmatches.interface = chartrimcarriage(readbuf);
+ file.gets(readbuf, std::size(readbuf));
+ tmpmatches.instance = chartrimcarriage(readbuf);
+ file.gets(readbuf, std::size(readbuf));
+ tmpmatches.startempty = atoi(readbuf);
+ file.gets(readbuf, std::size(readbuf));
+ tmpmatches.parentlongname = chartrimcarriage(readbuf);
+ file.gets(readbuf, std::size(readbuf));
+ //tmpmatches.usage = chartrimcarriage(readbuf); TODO: recover multi-line info
+ file.gets(readbuf, std::size(readbuf));
+ tmpmatches.devicetype = chartrimcarriage(readbuf);
+ file.gets(readbuf, std::size(readbuf));
+ tmpmatches.available = atoi(readbuf);
+
+ // need to populate this, it isn't displayed anywhere else
+ tmpmatches.infotext.append(tmpmatches.longname);
+ tmpmatches.infotext.append(1, '\n');
+ tmpmatches.infotext.append(_("swlist-info", "Software list/item"));
+ tmpmatches.infotext.append(1, '\n');
+ tmpmatches.infotext.append(tmpmatches.listname);
+ tmpmatches.infotext.append(1, ':');
+ tmpmatches.infotext.append(tmpmatches.shortname);
+
+ m_favorites.emplace(std::move(tmpmatches));
+ }
+ file.close();
+ }
+}
+
+
+//-------------------------------------------------
+// add
+//-------------------------------------------------
+
+void favorite_manager::add_favorite_system(game_driver const &driver)
+{
+ add_impl(driver);
+}
+
+void favorite_manager::add_favorite_software(ui_software_info const &swinfo)
+{
+ add_impl(swinfo);
+}
+
+void favorite_manager::add_favorite(running_machine &machine)
+{
+ apply_running_machine(
+ machine,
+ [this, &machine] (game_driver const &driver, device_image_interface *imagedev, software_info const *software, bool &done)
+ {
+ if (imagedev)
+ {
+ // creating this is fairly expensive, but we'll assume this usually succeeds
+ software_part const *const part(imagedev->part_entry());
+ assert(software);
+ assert(part);
+ ui_software_info info(
+ *software,
+ *part,
+ driver,
+ imagedev->software_list_name(),
+ imagedev->instance_name(),
+ strensure(imagedev->image_type_name()));
+
+ // assume it's available if it's mounted
+ info.available = true;
+
+ // look up the parent in the list if necessary (eugh, O(n) walk)
+ if (!info.parentname.empty())
+ {
+ auto const listdev = software_list_device::find_by_name(machine.config(), info.listname);
+ assert(listdev);
+ for (software_info const &other : listdev->get_info())
+ {
+ if (other.shortname() == info.parentname)
+ {
+ info.parentlongname = other.longname();
+ break;
+ }
+ }
+ }
+
+ // hooray for move semantics!
+ add_impl(std::move(info));
+ }
+ else
+ {
+ add_impl(driver);
+ }
+ });
+}
+
+template <typename T> void favorite_manager::add_impl(T &&key)
+{
+ auto const ins(m_favorites.emplace(std::forward<T>(key)));
+ if (ins.second)
+ {
+ if (!m_sorted.empty())
+ m_sorted.emplace_back(std::ref(*ins.first));
+ m_need_sort = true;
+ save_favorites();
+ }
+}
+
+
+//-------------------------------------------------
+// check
+//-------------------------------------------------
+
+bool favorite_manager::is_favorite_system(game_driver const &driver) const
+{
+ return check_impl(driver);
+}
+
+bool favorite_manager::is_favorite_software(ui_software_info const &swinfo) const
+{
+ auto found(m_favorites.lower_bound(swinfo));
+ if ((m_favorites.end() != found) && (found->listname == swinfo.listname) && (found->shortname == swinfo.shortname))
+ return true;
+ else if (m_favorites.begin() == found)
+ return false;
+
+ // need to back up and check for matching software with lexically earlier driver
+ --found;
+ return (found->listname == swinfo.listname) && (found->shortname == swinfo.shortname);
+}
+
+bool favorite_manager::is_favorite(running_machine &machine) const
+{
+ bool result(false);
+ apply_running_machine(
+ machine,
+ [this, &result] (game_driver const &driver, device_image_interface *imagedev, software_info const *software, bool &done)
+ {
+ assert(!result);
+ result = imagedev
+ ? check_impl(running_software_key(driver, imagedev->software_list_name(), software->shortname()))
+ : check_impl(driver);
+ done = done || result;
+ });
+ return result;
+}
+
+bool favorite_manager::is_favorite_system_software(ui_software_info const &swinfo) const
+{
+ return check_impl(swinfo);
+}
+
+template <typename T> bool favorite_manager::check_impl(T const &key) const
+{
+ return m_favorites.find(key) != m_favorites.end();
+}
+
+
+//-------------------------------------------------
+// remove
+//-------------------------------------------------
+
+void favorite_manager::remove_favorite_system(game_driver const &driver)
+{
+ remove_impl(driver);
+}
+
+void favorite_manager::remove_favorite_software(ui_software_info const &swinfo)
+{
+ remove_impl(swinfo);
+}
+
+void favorite_manager::remove_favorite(running_machine &machine)
+{
+ apply_running_machine(
+ machine,
+ [this] (game_driver const &driver, device_image_interface *imagedev, software_info const *software, bool &done)
+ {
+ done = imagedev
+ ? remove_impl(running_software_key(driver, imagedev->software_list_name(), software->shortname()))
+ : remove_impl(driver);
+ });
+}
+
+template <typename T> bool favorite_manager::remove_impl(T const &key)
+{
+ auto const found(m_favorites.find(key));
+ if (m_favorites.end() != found)
+ {
+ m_favorites.erase(found);
+ m_sorted.clear();
+ m_need_sort = true;
+ save_favorites();
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+
+//-------------------------------------------------
+// implementation
+//-------------------------------------------------
+
+template <typename T>
+void favorite_manager::apply_running_machine(running_machine &machine, T &&action)
+{
+ bool done(false);
+
+ // TODO: this should be changed - it interacts poorly with cartslots on arcade systems
+ if ((machine.system().flags & machine_flags::MASK_TYPE) == machine_flags::TYPE_ARCADE)
+ {
+ action(machine.system(), nullptr, nullptr, done);
+ }
+ else
+ {
+ bool have_software(false);
+ for (device_image_interface &image_dev : image_interface_enumerator(machine.root_device()))
+ {
+ software_info const *const sw(image_dev.software_entry());
+ if (image_dev.exists() && image_dev.loaded_through_softlist() && sw)
+ {
+ assert(image_dev.software_list_name());
+
+ have_software = true;
+ action(machine.system(), &image_dev, sw, done);
+ if (done)
+ return;
+ }
+ }
+
+ if (!have_software)
+ action(machine.system(), nullptr, nullptr, done);
+ }
+}
+
+void favorite_manager::update_sorted()
+{
+ if (m_need_sort)
+ {
+ if (m_sorted.empty())
+ std::copy(m_favorites.begin(), m_favorites.end(), std::back_inserter(m_sorted));
+
+ assert(m_favorites.size() == m_sorted.size());
+ std::stable_sort(
+ m_sorted.begin(),
+ m_sorted.end(),
+ [] (ui_software_info const &lhs, ui_software_info const &rhs) -> bool
+ {
+ assert(lhs.driver);
+ assert(rhs.driver);
+
+ int cmp;
+
+ cmp = core_stricmp(lhs.longname.c_str(), rhs.longname.c_str());
+ if (0 > cmp)
+ return true;
+ else if (0 < cmp)
+ return false;
+
+ cmp = core_stricmp(lhs.driver->type.fullname(), rhs.driver->type.fullname());
+ if (0 > cmp)
+ return true;
+ else if (0 < cmp)
+ return false;
+
+ cmp = std::strcmp(lhs.listname.c_str(), rhs.listname.c_str());
+ if (0 > cmp)
+ return true;
+ else if (0 < cmp)
+ return false;
+
+ return false;
+ });
+
+ m_need_sort = false;
+ }
+}
+
+void favorite_manager::save_favorites()
+{
+ // attempt to open the output file
+ emu_file file(m_options.ui_path(), OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS);
+ if (!file.open(FAVORITE_FILENAME))
+ {
+ if (m_favorites.empty())
+ {
+ // delete it if there are no favorites
+ file.remove_on_close();
+ }
+ else
+ {
+ // generate the favorite INI
+ file.puts("[ROOT_FOLDER]\n[Favorite]\n\n");
+ util::ovectorstream buf;
+ for (ui_software_info const &info : m_favorites)
+ {
+ buf.clear();
+ buf.rdbuf()->clear();
+
+ buf << info.shortname << '\n';
+ buf << info.longname << '\n';
+ buf << info.parentname << '\n';
+ buf << info.year << '\n';
+ buf << info.publisher << '\n';
+ util::stream_format(buf, "%d\n", int(info.supported));
+ buf << info.part << '\n';
+ util::stream_format(buf, "%s\n", info.driver->name);
+ buf << info.listname << '\n';
+ buf << info.interface << '\n';
+ buf << info.instance << '\n';
+ util::stream_format(buf, "%d\n", info.startempty);
+ buf << info.parentlongname << '\n';
+ buf << '\n'; //buf << info.usage << '\n'; TODO: store multi-line info in a recoverable format
+ buf << info.devicetype << '\n';
+ util::stream_format(buf, "%d\n", info.available);
+
+ file.puts(util::buf_to_string_view(buf));
+ }
+ }
+ file.close();
+ }
+}