diff options
Diffstat (limited to 'docs/release/src/frontend')
-rw-r--r-- | docs/release/src/frontend/mame/audit.cpp | 661 | ||||
-rw-r--r-- | docs/release/src/frontend/mame/audit.h | 181 | ||||
-rw-r--r-- | docs/release/src/frontend/mame/language.cpp | 185 | ||||
-rw-r--r-- | docs/release/src/frontend/mame/mameopts.cpp | 210 | ||||
-rw-r--r-- | docs/release/src/frontend/mame/mameopts.h | 64 |
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 |