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.cpp661
-rw-r--r--docs/release/src/frontend/mame/audit.h181
-rw-r--r--docs/release/src/frontend/mame/language.cpp185
-rw-r--r--docs/release/src/frontend/mame/mameopts.cpp210
-rw-r--r--docs/release/src/frontend/mame/mameopts.h64
5 files changed, 1301 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..55c86ef1c2f
--- /dev/null
+++ b/docs/release/src/frontend/mame/audit.cpp
@@ -0,0 +1,661 @@
+// 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 "romload.h"
+#include "softlist_dev.h"
+
+#include "chd.h"
+
+#include <algorithm>
+
+
+//**************************************************************************
+// 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;
+
+ std::size_t found = 0;
+ std::size_t required = 0;
+ std::size_t shared_found = 0;
+ std::size_t shared_required = 0;
+
+ // iterate over devices and regions
+ std::vector<std::string> searchpath;
+ for (device_t &device : device_iterator(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())
+ searchpath = device.searchpath();
+
+ char const *const name(ROM_GETNAME(rom));
+ util::hash_collection const hashes(ROM_GETHASHDATA(rom));
+ device_t *const shared_device(find_shared_device(device, name, hashes, ROM_GETLENGTH(rom)));
+
+ // count the number of files with hashes
+ if (!hashes.flag(util::hash_collection::FLAG_NO_DUMP) && !ROM_ISOPTIONAL(rom))
+ {
+ required++;
+ if (shared_device)
+ shared_required++;
+ }
+
+ audit_record *record = nullptr;
+ if (ROMREGION_ISROMDATA(region))
+ record = &audit_one_rom(searchpath, rom);
+ else if (ROMREGION_ISDISKDATA(region))
+ record = &audit_one_disk(rom, device);
+
+ if (record)
+ {
+ // count the number of files that are found.
+ if ((record->status() == audit_status::GOOD) || ((record->status() == audit_status::FOUND_INVALID) && !find_shared_device(device, name, record->actual_hashes(), record->actual_length())))
+ {
+ found++;
+ if (shared_device)
+ shared_found++;
+ }
+
+ record->set_shared_device(shared_device);
+ }
+ }
+ }
+ }
+
+ // if we only find files that are in the parent & either the set has no unique files or the parent is not found, then assume we don't have the set at all
+ if ((found == shared_found) && (required > 0) && ((required != shared_required) || (shared_found == 0)))
+ {
+ m_record_list.clear();
+ return NOTFOUND;
+ }
+
+ // return a summary
+ return summarize(m_enumerator.driver().name);
+}
+
+
+//-------------------------------------------------
+// audit_device - audit the device
+//-------------------------------------------------
+
+media_auditor::summary media_auditor::audit_device(device_t &device, const char *validation)
+{
+ // start fresh
+ m_record_list.clear();
+
+ // store validation for later
+ m_validation = validation;
+
+ 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_iterator(m_enumerator.config()->root_device()))
+ {
+ // by default we just search using the driver name
+ std::string searchpath(m_enumerator.driver().name);
+
+ // add the alternate path if present
+ samples_iterator iter(device);
+ if (iter.altbasename() != nullptr)
+ searchpath.append(";").append(iter.altbasename());
+
+ // iterate over samples in this entry
+ for (const char *samplename = iter.first(); samplename; samplename = iter.next())
+ {
+ required++;
+
+ // create a new record
+ audit_record &record = *m_record_list.emplace(m_record_list.end(), samplename, media_type::SAMPLE);
+
+ // look for the files
+ emu_file file(m_enumerator.options().sample_path(), OPEN_FLAG_READ | OPEN_FLAG_NO_PRELOAD);
+ path_iterator path(searchpath);
+ std::string curpath;
+ while (path.next(curpath, samplename))
+ {
+ // attempt to access the file (.flac) or (.wav)
+ osd_file::error filerr = file.open(curpath + ".flac");
+ if (filerr != osd_file::error::NONE)
+ filerr = file.open(curpath + ".wav");
+
+ if (filerr == osd_file::error::NONE)
+ {
+ record.set_status(audit_status::GOOD, audit_substatus::GOOD);
+ found++;
+ }
+ else
+ {
+ record.set_status(audit_status::NOT_FOUND, audit_substatus::NOT_FOUND);
+ }
+ }
+ }
+ }
+
+ if ((found == 0) && (required > 0))
+ {
+ m_record_list.clear();
+ return NOTFOUND;
+ }
+
+ // return a summary
+ return summarize(m_enumerator.driver().name);
+}
+
+
+//-------------------------------------------------
+// summary - generate a summary, with an optional
+// string format
+//-------------------------------------------------
+
+media_auditor::summary media_auditor::summarize(const char *name, std::ostream *output) const
+{
+ if (m_record_list.empty())
+ return NONE_NEEDED;
+
+ // loop over records
+ summary overall_status = CORRECT;
+ for (audit_record const &record : m_record_list)
+ {
+ // skip anything that's fine
+ if (record.substatus() == audit_substatus::GOOD)
+ continue;
+
+ // output the game name, file name, and length (if applicable)
+ if (output)
+ {
+ if (name)
+ util::stream_format(*output, "%-12s: %s", name, record.name());
+ else
+ util::stream_format(*output, "%s", record.name());
+ if (record.expected_length() > 0)
+ util::stream_format(*output, " (%d bytes)", record.expected_length());
+ *output << " - ";
+ }
+
+ // use the substatus for finer details
+ summary best_new_status = INCORRECT;
+ switch (record.substatus())
+ {
+ case audit_substatus::GOOD_NEEDS_REDUMP:
+ if (output) *output << "NEEDS REDUMP\n";
+ best_new_status = BEST_AVAILABLE;
+ break;
+
+ case audit_substatus::FOUND_NODUMP:
+ if (output) *output << "NO GOOD DUMP KNOWN\n";
+ best_new_status = BEST_AVAILABLE;
+ break;
+
+ case audit_substatus::FOUND_BAD_CHECKSUM:
+ if (output)
+ {
+ util::stream_format(*output, "INCORRECT CHECKSUM:\n");
+ util::stream_format(*output, "EXPECTED: %s\n", record.expected_hashes().macro_string());
+ util::stream_format(*output, " FOUND: %s\n", record.actual_hashes().macro_string());
+ }
+ break;
+
+ case audit_substatus::FOUND_WRONG_LENGTH:
+ if (output) util::stream_format(*output, "INCORRECT LENGTH: %d bytes\n", record.actual_length());
+ break;
+
+ case audit_substatus::NOT_FOUND:
+ if (output)
+ {
+ device_t *const shared_device = record.shared_device();
+ if (shared_device)
+ util::stream_format(*output, "NOT FOUND (%s)\n", shared_device->shortname());
+ else
+ util::stream_format(*output, "NOT FOUND\n");
+ }
+ break;
+
+ case audit_substatus::NOT_FOUND_NODUMP:
+ if (output) *output << "NOT FOUND - NO GOOD DUMP KNOWN\n";
+ best_new_status = BEST_AVAILABLE;
+ break;
+
+ case audit_substatus::NOT_FOUND_OPTIONAL:
+ if (output) *output << "NOT FOUND BUT OPTIONAL\n";
+ best_new_status = BEST_AVAILABLE;
+ break;
+
+ default:
+ assert(false);
+ }
+
+ // downgrade the overall status if necessary
+ overall_status = (std::max)(overall_status, best_new_status);
+ }
+ return overall_status;
+}
+
+
+//-------------------------------------------------
+// audit_regions - validate/count for regions
+//-------------------------------------------------
+
+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_GETHASHDATA(rom));
+ 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
+ osd_file::error 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 == osd_file::error::NONE)
+ 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 chd_error err = rom_load_manager::open_disk_image(m_enumerator.options(), std::forward<T>(args)..., rom, source);
+
+ // if we succeeded, get the hashes
+ if (err == CHDERR_NONE)
+ {
+ util::hash_collection hashes;
+
+ // if there's a SHA1 hash, add them to the output hash
+ if (source.sha1() != util::sha1_t::null)
+ hashes.add_sha1(source.sha1());
+
+ // update the actual values
+ record.set_actual(hashes);
+ }
+
+ // compute the final status
+ compute_status(record, rom, err == CHDERR_NONE);
+ return record;
+}
+
+
+//-------------------------------------------------
+// compute_status - compute a detailed status
+// based on the information we have
+//-------------------------------------------------
+
+void media_auditor::compute_status(audit_record &record, const rom_entry *rom, bool found)
+{
+ // if not found, provide more details
+ if (!found)
+ {
+ if (record.expected_hashes().flag(util::hash_collection::FLAG_NO_DUMP))
+ record.set_status(audit_status::NOT_FOUND, audit_substatus::NOT_FOUND_NODUMP);
+ else if (ROM_ISOPTIONAL(rom))
+ record.set_status(audit_status::NOT_FOUND, audit_substatus::NOT_FOUND_OPTIONAL);
+ else
+ record.set_status(audit_status::NOT_FOUND, audit_substatus::NOT_FOUND);
+ }
+ else
+ {
+ if (record.expected_length() != record.actual_length())
+ record.set_status(audit_status::FOUND_INVALID, audit_substatus::FOUND_WRONG_LENGTH);
+ else if (record.expected_hashes().flag(util::hash_collection::FLAG_NO_DUMP))
+ record.set_status(audit_status::GOOD, audit_substatus::FOUND_NODUMP);
+ else if (record.expected_hashes() != record.actual_hashes())
+ record.set_status(audit_status::FOUND_INVALID, audit_substatus::FOUND_BAD_CHECKSUM);
+ else if (record.expected_hashes().flag(util::hash_collection::FLAG_BAD_DUMP))
+ record.set_status(audit_status::GOOD, audit_substatus::GOOD_NEEDS_REDUMP);
+ else
+ record.set_status(audit_status::GOOD, audit_substatus::GOOD);
+ }
+}
+
+
+//-------------------------------------------------
+// find_shared_device - return the source that
+// shares a media entry with the same hashes
+//-------------------------------------------------
+
+device_t *media_auditor::find_shared_device(device_t &device, const char *name, const util::hash_collection &romhashes, uint64_t romlength)
+{
+ bool const dumped = !romhashes.flag(util::hash_collection::FLAG_NO_DUMP);
+
+ // special case for non-root devices
+ device_t *highest_device = nullptr;
+ if (device.owner())
+ {
+ for (const rom_entry *region = rom_first_region(device); region; region = rom_next_region(region))
+ {
+ for (const rom_entry *rom = rom_first_file(region); rom; rom = rom_next_file(rom))
+ {
+ if (ROM_GETLENGTH(rom) == romlength)
+ {
+ util::hash_collection hashes(ROM_GETHASHDATA(rom));
+ if ((dumped && hashes == romhashes) || (!dumped && ROM_GETNAME(rom) == name))
+ highest_device = &device;
+ }
+ }
+ }
+ }
+ else
+ {
+ // iterate up the parent chain
+ for (auto drvindex = m_enumerator.find(m_enumerator.driver().parent); drvindex >= 0; drvindex = m_enumerator.find(m_enumerator.driver(drvindex).parent))
+ {
+ for (device_t &scandevice : device_iterator(m_enumerator.config(drvindex)->root_device()))
+ {
+ for (const rom_entry *region = rom_first_region(scandevice); region; region = rom_next_region(region))
+ {
+ for (const rom_entry *rom = rom_first_file(region); rom; rom = rom_next_file(rom))
+ {
+ if (ROM_GETLENGTH(rom) == romlength)
+ {
+ util::hash_collection hashes(ROM_GETHASHDATA(rom));
+ if ((dumped && hashes == romhashes) || (!dumped && ROM_GETNAME(rom) == name))
+ highest_device = &scandevice;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return highest_device;
+}
+
+
+//-------------------------------------------------
+// audit_record - constructor
+//-------------------------------------------------
+
+media_auditor::audit_record::audit_record(const rom_entry &media, media_type type)
+ : m_type(type)
+ , m_status(audit_status::UNVERIFIED)
+ , m_substatus(audit_substatus::UNVERIFIED)
+ , m_name(ROM_GETNAME(&media))
+ , m_explength(rom_file_size(&media))
+ , m_length(0)
+ , m_exphashes()
+ , m_hashes()
+ , m_shared_device(nullptr)
+{
+ m_exphashes.from_internal_string(ROM_GETHASHDATA(&media));
+}
+
+media_auditor::audit_record::audit_record(const char *name, media_type type)
+ : m_type(type)
+ , m_status(audit_status::UNVERIFIED)
+ , m_substatus(audit_substatus::UNVERIFIED)
+ , m_name(name)
+ , m_explength(0)
+ , m_length(0)
+ , m_exphashes()
+ , m_hashes()
+ , m_shared_device(nullptr)
+{
+}
+
+
+
+// MESSUI - only report problems that the user can fix
+media_auditor::summary media_auditor::winui_summarize(const char *name, std::string *output)
+{
+ if (m_record_list.empty())
+ return NONE_NEEDED;
+
+ // loop over records
+ summary overall_status = CORRECT;
+ for (audit_record const &record : m_record_list)
+ {
+ summary best_new_status = INCORRECT;
+
+ // skip anything that's fine
+ if ( (record.substatus() == audit_substatus::GOOD)
+ || (record.substatus() == audit_substatus::GOOD_NEEDS_REDUMP)
+ || (record.substatus() == audit_substatus::NOT_FOUND_NODUMP)
+ || (record.substatus() == audit_substatus::FOUND_NODUMP)
+ )
+ continue;
+
+ // output the game name, file name, and length (if applicable)
+ //if (output)
+ {
+ output->append(string_format("%-12s: %s", name, record.name()));
+ if (record.expected_length() > 0)
+ output->append(string_format(" (%d bytes)", record.expected_length()));
+ output->append(" - ");
+ }
+
+ // use the substatus for finer details
+ switch (record.substatus())
+ {
+ case audit_substatus::FOUND_NODUMP:
+ if (output) output->append("NO GOOD DUMP KNOWN\n");
+ best_new_status = BEST_AVAILABLE;
+ break;
+
+ case audit_substatus::FOUND_BAD_CHECKSUM:
+ if (output)
+ {
+ output->append("INCORRECT CHECKSUM:\n");
+ output->append(string_format("EXPECTED: %s\n", record.expected_hashes().macro_string().c_str()));
+ output->append(string_format(" FOUND: %s\n", record.actual_hashes().macro_string().c_str()));
+ }
+ break;
+
+ case audit_substatus::FOUND_WRONG_LENGTH:
+ if (output) output->append(string_format("INCORRECT LENGTH: %d bytes\n", record.actual_length()));
+ break;
+
+ case audit_substatus::NOT_FOUND:
+ if (output)
+ {
+ device_t *shared_device = record.shared_device();
+ if (shared_device == NULL)
+ output->append("NOT FOUND\n");
+ else
+ output->append(string_format("NOT FOUND (%s)\n", shared_device->shortname()));
+ }
+ break;
+
+ case audit_substatus::NOT_FOUND_OPTIONAL:
+ if (output) output->append("NOT FOUND BUT OPTIONAL\n");
+ best_new_status = BEST_AVAILABLE;
+ break;
+
+ default:
+ break;
+ }
+
+ // downgrade the overall status if necessary
+ overall_status = (std::max)(overall_status, best_new_status);
+ }
+ return overall_status;
+}
diff --git a/docs/release/src/frontend/mame/audit.h b/docs/release/src/frontend/mame/audit.h
new file mode 100644
index 00000000000..f7ccf91be36
--- /dev/null
+++ b/docs/release/src/frontend/mame/audit.h
@@ -0,0 +1,181 @@
+// 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 "hash.h"
+
+#include <iosfwd>
+#include <list>
+#include <utility>
+
+
+
+//**************************************************************************
+// CONSTANTS
+//**************************************************************************
+
+// hashes to use for validation
+#define AUDIT_VALIDATE_FAST "R" /* CRC only */
+#define AUDIT_VALIDATE_FULL "RS" /* CRC + SHA1 */
+
+
+
+//**************************************************************************
+// TYPE DEFINITIONS
+//**************************************************************************
+
+
+// forward declarations
+class driver_enumerator;
+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 char *name() const { return m_name; }
+ uint64_t expected_length() const { return m_explength; }
+ uint64_t actual_length() const { return m_length; }
+ const util::hash_collection &expected_hashes() const { return m_exphashes; }
+ const util::hash_collection &actual_hashes() const { return m_hashes; }
+ device_t *shared_device() const { return m_shared_device; }
+
+ // setters
+ void set_status(audit_status status, audit_substatus substatus)
+ {
+ m_status = status;
+ m_substatus = substatus;
+ }
+
+ void set_actual(const util::hash_collection &hashes, uint64_t length = 0)
+ {
+ m_hashes = hashes;
+ m_length = length;
+ }
+
+ void set_actual(util::hash_collection &&hashes, uint64_t length = 0)
+ {
+ m_hashes = std::move(hashes);
+ m_length = length;
+ }
+
+ void set_shared_device(device_t *shared_device)
+ {
+ m_shared_device = shared_device;
+ }
+
+ private:
+ // internal state
+ media_type m_type; // type of item that was audited
+ audit_status m_status; // status of audit on this item
+ audit_substatus m_substatus; // finer-detail status
+ const char * m_name; // name of item
+ uint64_t m_explength; // expected length of item
+ uint64_t m_length; // actual length of item
+ util::hash_collection m_exphashes; // expected hash data
+ util::hash_collection m_hashes; // actual hash information
+ device_t * m_shared_device; // device that shares the rom
+ };
+ using record_list = std::list<audit_record>;
+
+ // construction/destruction
+ media_auditor(const driver_enumerator &enumerator);
+
+ // getters
+ const record_list &records() const { return m_record_list; }
+
+ // audit operations
+ summary audit_media(const char *validation = AUDIT_VALIDATE_FULL);
+ summary audit_device(device_t &device, const char *validation = AUDIT_VALIDATE_FULL);
+ summary audit_software(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);
+ device_t *find_shared_device(device_t &device, const char *name, const util::hash_collection &romhashes, uint64_t romlength);
+
+ // internal state
+ record_list m_record_list;
+ const driver_enumerator & m_enumerator;
+ const char * m_validation;
+};
+
+
+#endif // MAME_FRONTEND_AUDIT_H
diff --git a/docs/release/src/frontend/mame/language.cpp b/docs/release/src/frontend/mame/language.cpp
new file mode 100644
index 00000000000..52b6ee6e3f6
--- /dev/null
+++ b/docs/release/src/frontend/mame/language.cpp
@@ -0,0 +1,185 @@
+// license:BSD-3-Clause
+// copyright-holders:Vas Crabb
+/***************************************************************************
+
+ language.cpp
+
+ Multi-language support.
+
+***************************************************************************/
+
+#include "emu.h"
+#include "emuopts.h"
+
+#include <cstring>
+#include <memory>
+#include <new>
+#include <unordered_map>
+
+
+namespace {
+
+constexpr uint32_t MO_MAGIC = 0x950412de;
+constexpr uint32_t MO_MAGIC_REVERSED = 0xde120495;
+
+struct cstr_hash
+{
+ size_t operator()(char const *s) const noexcept
+ {
+ // Bernstein string hash
+ size_t result(5381);
+ while (*s)
+ result = ((result << 5) + result) + u8(*s++);
+ return result;
+ }
+};
+
+struct cstr_compare
+{
+ size_t operator()(char const *x, char const *y) const noexcept
+ {
+ return !std::strcmp(x, y);
+ }
+};
+
+std::unique_ptr<u32 []> f_translation_data;
+std::unordered_map<char const *, char const *, cstr_hash, cstr_compare> f_translation_map;
+
+} // anonymous namespace
+
+
+char const *lang_translate(char const *word)
+{
+ auto const found = f_translation_map.find(word);
+ return (f_translation_map.end() != found) ? found->second : word;
+}
+
+
+void load_translation(emu_options &m_options)
+{
+ f_translation_data.reset();
+ f_translation_map.clear();
+
+ std::string name = m_options.language();
+ if (name.empty())
+ return;
+
+ strreplace(name, " ", "_");
+ strreplace(name, "(", "");
+ strreplace(name, ")", "");
+
+ // MESSUI: See if language file exists. If not, try English, see if that exists. If not, use inbuilt default.
+ emu_file file(m_options.language_path(), OPEN_FLAG_READ);
+ if (file.open(name + PATH_SEPARATOR "strings.mo") != osd_file::error::NONE)
+ {
+ osd_printf_verbose("Error opening translation file %s\n", name);
+ name = "English";
+ if (file.open(name + PATH_SEPARATOR "strings.mo") != osd_file::error::NONE)
+ {
+ osd_printf_verbose("Error opening translation file %s\n", name);
+ return;
+ }
+ }
+
+ u64 const size = file.size();
+ if (20 > size)
+ {
+ file.close();
+ osd_printf_error("Error reading translation file %s: %u-byte file is too small to contain translation data\n", name, size);
+ return;
+ }
+
+ f_translation_data.reset(new (std::nothrow) uint32_t [(size + 3) / 4]);
+ if (!f_translation_data)
+ {
+ file.close();
+ osd_printf_error("Failed to allocate %u bytes to load translation data file %s\n", size, name);
+ return;
+ }
+
+ auto const read = file.read(f_translation_data.get(), size);
+ file.close();
+ if (read != size)
+ {
+ osd_printf_error("Error reading translation file %s: requested %u bytes but got %u bytes\n", name, size, read);
+ f_translation_data.reset();
+ return;
+ }
+
+ if ((f_translation_data[0] != MO_MAGIC) && (f_translation_data[0] != MO_MAGIC_REVERSED))
+ {
+ osd_printf_error("Error reading translation file %s: unrecognized magic number 0x%08X\n", name, f_translation_data[0]);
+ f_translation_data.reset();
+ return;
+ }
+
+ auto fetch_word =
+ [reversed = f_translation_data[0] == MO_MAGIC_REVERSED, words = f_translation_data.get()] (size_t offset)
+ {
+ return reversed ? swapendian_int32(words[offset]) : words[offset];
+ };
+
+ // FIXME: check major/minor version number
+
+ if ((fetch_word(3) % 4) || (fetch_word(4) % 4))
+ {
+ osd_printf_error("Error reading translation file %s: table offsets %u and %u are not word-aligned\n", name, fetch_word(3), fetch_word(4));
+ f_translation_data.reset();
+ return;
+ }
+
+ u32 const number_of_strings = fetch_word(2);
+ u32 const original_table_offset = fetch_word(3) >> 2;
+ u32 const translation_table_offset = fetch_word(4) >> 2;
+ if ((4 * (original_table_offset + (u64(number_of_strings) * 2))) > size)
+ {
+ osd_printf_error("Error reading translation file %s: %u-entry original string table at offset %u extends past end of %u-byte file\n", name, number_of_strings, fetch_word(3), size);
+ f_translation_data.reset();
+ return;
+ }
+ if ((4 * (translation_table_offset + (u64(number_of_strings) * 2))) > size)
+ {
+ osd_printf_error("Error reading translation file %s: %u-entry translated string table at offset %u extends past end of %u-byte file\n", name, number_of_strings, fetch_word(4), size);
+ f_translation_data.reset();
+ return;
+ }
+ osd_printf_verbose("Reading translation file %s: %u strings, original table at word offset %u, translated table at word offset %u\n", name, number_of_strings, original_table_offset, translation_table_offset);
+
+ char const *const data = reinterpret_cast<char const *>(f_translation_data.get());
+ for (u32 i = 1; number_of_strings > i; ++i)
+ {
+ u32 const original_length = fetch_word(original_table_offset + (2 * i));
+ u32 const original_offset = fetch_word(original_table_offset + (2 * i) + 1);
+ if ((original_length + original_offset) >= size)
+ {
+ osd_printf_error("Error reading translation file %s: %u-byte original string %u at offset %u extends past end of %u-byte file\n", name, original_length, i, original_offset, size);
+ continue;
+ }
+ if (data[original_length + original_offset])
+ {
+ osd_printf_error("Error reading translation file %s: %u-byte original string %u at offset %u is not correctly NUL-terminated\n", name, original_length, i, original_offset);
+ continue;
+ }
+
+ u32 const translation_length = fetch_word(translation_table_offset + (2 * i));
+ u32 const translation_offset = fetch_word(translation_table_offset + (2 * i) + 1);
+ if ((translation_length + translation_offset) >= size)
+ {
+ osd_printf_error("Error reading translation file %s: %u-byte translated string %u at offset %u extends past end of %u-byte file\n", name, translation_length, i, translation_offset, size);
+ continue;
+ }
+ if (data[translation_length + translation_offset])
+ {
+ osd_printf_error("Error reading translation file %s: %u-byte translated string %u at offset %u is not correctly NUL-terminated\n", name, translation_length, i, translation_offset);
+ continue;
+ }
+
+ char const *const original = &data[original_offset];
+ char const *const translation = &data[translation_offset];
+ auto const ins = f_translation_map.emplace(original, translation);
+ if (!ins.second)
+ osd_printf_warning("Loading translation file %s: translation %u '%s'='%s' conflicts with previous translation '%s'='%s'\n", name, i, original, translation, ins.first->first, ins.first->second);
+ }
+
+ osd_printf_verbose("Loaded %u translations from file %s\n", f_translation_map.size(), name);
+}
diff --git a/docs/release/src/frontend/mame/mameopts.cpp b/docs/release/src/frontend/mame/mameopts.cpp
new file mode 100644
index 00000000000..296d900d679
--- /dev/null
+++ b/docs/release/src/frontend/mame/mameopts.cpp
@@ -0,0 +1,210 @@
+// license:BSD-3-Clause
+// copyright-holders:Aaron Giles
+/***************************************************************************
+
+ mameopts.cpp
+
+ Options file and command line management.
+
+***************************************************************************/
+
+#include "emu.h"
+#include "mameopts.h"
+
+#include "drivenum.h"
+#include "screen.h"
+#include "softlist_dev.h"
+#include "zippath.h"
+#include "hashfile.h"
+#include "clifront.h"
+
+#include <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_iterator(config.root_device()))
+ {
+ // parse "raster.ini" for raster games
+ if (device.screen_type() == SCREEN_TYPE_RASTER)
+ {
+ parse_one_ini(options, "raster", OPTION_PRIORITY_SCREEN_INI, &error_stream);
+ break;
+ }
+ // parse "vector.ini" for vector games
+ if (device.screen_type() == SCREEN_TYPE_VECTOR)
+ {
+ parse_one_ini(options, "vector", OPTION_PRIORITY_SCREEN_INI, &error_stream);
+ break;
+ }
+ // parse "lcd.ini" for lcd games
+ if (device.screen_type() == SCREEN_TYPE_LCD)
+ {
+ parse_one_ini(options, "lcd", OPTION_PRIORITY_SCREEN_INI, &error_stream);
+ break;
+ }
+ }
+
+ // next parse "source/<sourcefile>.ini"
+ std::string sourcename = core_filename_extract_base(cursystem->type.source(), true).insert(0, "source" PATH_SEPARATOR);
+ parse_one_ini(options, sourcename.c_str(), OPTION_PRIORITY_SOURCE_INI, &error_stream);
+
+ // then parse the grandparent, parent, and system-specific INIs
+ int parent = driver_list::clone(*cursystem);
+ int gparent = (parent != -1) ? driver_list::clone(parent) : -1;
+ if (gparent != -1)
+ parse_one_ini(options, driver_list::driver(gparent).name, OPTION_PRIORITY_GPARENT_INI, &error_stream);
+ if (parent != -1)
+ parse_one_ini(options, driver_list::driver(parent).name, OPTION_PRIORITY_PARENT_INI, &error_stream);
+ parse_one_ini(options, cursystem->name, OPTION_PRIORITY_DRIVER_INI, &error_stream);
+}
+
+
+//-------------------------------------------------
+// system - return a pointer to the specified
+// system driver, or nullptr if no match
+//-------------------------------------------------
+
+const game_driver *mame_options::system(const emu_options &options)
+{
+ int index = driver_list::find(core_filename_extract_base(options.system_name(), true).c_str());
+ return (index != -1) ? &driver_list::driver(index) : nullptr;
+}
+
+
+//-------------------------------------------------
+// parse_one_ini - parse a single INI file
+//-------------------------------------------------
+
+void mame_options::parse_one_ini(emu_options &options, const char *basename, int priority, std::ostream *error_stream)
+{
+ // don't parse if it has been disabled
+ if (!options.read_config())
+ return;
+
+ // open the file; if we fail, that's ok
+ emu_file file(options.ini_path(), OPEN_FLAG_READ);
+ osd_printf_verbose("Attempting load of %s.ini\n", basename);
+ osd_file::error filerr = file.open(std::string(basename) + ".ini");
+ if (filerr != osd_file::error::NONE)
+ return;
+
+ // parse the file
+ osd_printf_verbose("Parsing %s.ini\n", basename);
+ try
+ {
+ options.parse_ini_file((util::core_file&)file, priority, priority < OPTION_PRIORITY_DRIVER_INI, false);
+ }
+ catch (options_exception &ex)
+ {
+ if (error_stream)
+ util::stream_format(*error_stream, "While parsing %s:\n%s\n", 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