diff options
Diffstat (limited to 'src/emu/softlist_dev.cpp')
-rw-r--r-- | src/emu/softlist_dev.cpp | 298 |
1 files changed, 189 insertions, 109 deletions
diff --git a/src/emu/softlist_dev.cpp b/src/emu/softlist_dev.cpp index 831fc3f6734..b65f5f28300 100644 --- a/src/emu/softlist_dev.cpp +++ b/src/emu/softlist_dev.cpp @@ -9,13 +9,18 @@ ***************************************************************************/ #include "emu.h" -#include "emuopts.h" +#include "softlist_dev.h" + #include "diimage.h" +#include "emuopts.h" +#include "fileio.h" #include "romload.h" -#include "softlist_dev.h" #include "validity.h" -#include <ctype.h> +#include "corestr.h" +#include "unicode.h" + +#include <cctype> //************************************************************************** @@ -45,9 +50,9 @@ image_software_list_loader image_software_list_loader::s_instance; // false_software_list_loader::load_software //------------------------------------------------- -bool false_software_list_loader::load_software(device_image_interface &image, software_list_device &swlist, const char *swname, const rom_entry *start_entry) const +std::error_condition false_software_list_loader::load_software(device_image_interface &image, software_list_device &swlist, std::string_view swname, const rom_entry *start_entry) const { - return false; + return image_error::UNSUPPORTED; } @@ -55,10 +60,10 @@ bool false_software_list_loader::load_software(device_image_interface &image, so // rom_software_list_loader::load_software //------------------------------------------------- -bool rom_software_list_loader::load_software(device_image_interface &image, software_list_device &swlist, const char *swname, const rom_entry *start_entry) const +std::error_condition rom_software_list_loader::load_software(device_image_interface &image, software_list_device &swlist, std::string_view swname, const rom_entry *start_entry) const { swlist.machine().rom_load().load_software_part_region(image.device(), swlist, swname, start_entry); - return true; + return std::error_condition(); } @@ -66,7 +71,7 @@ bool rom_software_list_loader::load_software(device_image_interface &image, soft // image_software_list_loader::load_software //------------------------------------------------- -bool image_software_list_loader::load_software(device_image_interface &image, software_list_device &swlist, const char *swname, const rom_entry *start_entry) const +std::error_condition image_software_list_loader::load_software(device_image_interface &image, software_list_device &swlist, std::string_view swname, const rom_entry *start_entry) const { return image.load_software(swlist, swname, start_entry); } @@ -80,12 +85,11 @@ bool image_software_list_loader::load_software(device_image_interface &image, so // software_list_device - constructor //------------------------------------------------- -software_list_device::software_list_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock) - : device_t(mconfig, SOFTWARE_LIST, tag, owner, clock), - m_list_type(SOFTWARE_LIST_ORIGINAL_SYSTEM), +software_list_device::software_list_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock) : + device_t(mconfig, SOFTWARE_LIST, tag, owner, clock), + m_list_type(softlist_type::ORIGINAL_SYSTEM), m_filter(nullptr), m_parsed(false), - m_file(mconfig.options().hash_path(), OPEN_FLAG_READ), m_description("") { } @@ -106,7 +110,7 @@ void software_list_device::device_start() // and optional interface //------------------------------------------------- -void software_list_device::find_approx_matches(const std::string &name, int matches, const software_info **list, const char *interface) +void software_list_device::find_approx_matches(std::string_view name, int matches, const software_info **list, const char *interface) { // if no name, return if (name.empty()) @@ -172,8 +176,10 @@ void software_list_device::find_approx_matches(const std::string &name, int matc void software_list_device::release() { - osd_printf_verbose("Resetting %s\n", m_file.filename()); + osd_printf_verbose("%s: Resetting %s\n", tag(), m_list_name); m_parsed = false; + m_filename.clear(); + m_shortname.clear(); m_description.clear(); m_errors.clear(); m_infolist.clear(); @@ -185,10 +191,10 @@ void software_list_device::release() // across all software list devices //------------------------------------------------- -software_list_device *software_list_device::find_by_name(const machine_config &config, const std::string &name) +software_list_device *software_list_device::find_by_name(const machine_config &config, std::string_view name) { // iterate over each device in the system and find a match - for (software_list_device &swlistdev : software_list_device_iterator(config.root_device())) + for (software_list_device &swlistdev : software_list_device_enumerator(config.root_device())) if (swlistdev.list_name() == name) return &swlistdev; return nullptr; @@ -201,35 +207,35 @@ software_list_device *software_list_device::find_by_name(const machine_config &c // name, across all software list devices //------------------------------------------------- -void software_list_device::display_matches(const machine_config &config, const char *interface, const std::string &name) +void software_list_device::display_matches(const machine_config &config, const char *interface, std::string_view name) { // check if there is at least one software list - software_list_device_iterator deviter(config.root_device()); + software_list_device_enumerator deviter(config.root_device()); if (deviter.first() != nullptr) osd_printf_error("\n\"%s\" approximately matches the following\n" - "supported software items (best match first):\n\n", name.c_str()); + "supported software items (best match first):\n\n", name); // iterate through lists for (software_list_device &swlistdev : deviter) { // get the top 16 approximate matches for the selected device interface (i.e. only carts for cartslot, etc.) const software_info *matches[16] = { nullptr }; - swlistdev.find_approx_matches(name, ARRAY_LENGTH(matches), matches, interface); + swlistdev.find_approx_matches(name, std::size(matches), matches, interface); // if we found some, print them if (matches[0] != nullptr) { // different output depending on original system or compatible - if (swlistdev.list_type() == SOFTWARE_LIST_ORIGINAL_SYSTEM) - osd_printf_error("* Software list \"%s\" (%s) matches: \n", swlistdev.list_name().c_str(), swlistdev.description().c_str()); + if (swlistdev.is_original()) + osd_printf_error("* Software list \"%s\" (%s) matches: \n", swlistdev.list_name(), swlistdev.description()); else - osd_printf_error("* Compatible software list \"%s\" (%s) matches: \n", swlistdev.list_name().c_str(), swlistdev.description().c_str()); + osd_printf_error("* Compatible software list \"%s\" (%s) matches: \n", swlistdev.list_name(), swlistdev.description()); // print them out for (auto &match : matches) { if (match != nullptr) - osd_printf_error("%-18s%s\n", match->shortname().c_str(), match->longname().c_str()); + osd_printf_error("%-18s%s\n", match->shortname(), match->longname()); } osd_printf_error("\n"); @@ -244,7 +250,7 @@ void software_list_device::display_matches(const machine_config &config, const c // from an intermediate point //------------------------------------------------- -const software_info *software_list_device::find(const std::string &look_for) +const software_info *software_list_device::find(std::string_view look_for) { // empty search returns nothing if (look_for.empty()) @@ -259,9 +265,9 @@ const software_info *software_list_device::find(const std::string &look_for) info_list.end(), [&look_for, iswild] (const software_info &info) { - const char *shortname = info.shortname().c_str(); - return (iswild && core_strwildcmp(look_for.c_str(), shortname) == 0) - || core_stricmp(look_for.c_str(), shortname) == 0; + std::string_view shortname = info.shortname(); + return (iswild && core_strwildcmp(look_for, shortname) == 0) + || util::streqlower(look_for, shortname); }); return iter != info_list.end() ? &*iter : nullptr; @@ -282,17 +288,25 @@ void software_list_device::parse() m_errors.clear(); // attempt to open the file - osd_file::error filerr = m_file.open(m_list_name.c_str(), ".xml"); - if (filerr == osd_file::error::NONE) + emu_file file(mconfig().options().hash_path(), OPEN_FLAG_READ); + const std::error_condition filerr = file.open(m_list_name + ".xml"); + m_filename = file.filename(); + if (!filerr) { // parse if no error std::ostringstream errs; - softlist_parser parser(m_file, m_file.filename(), m_description, m_infolist, errs); - m_file.close(); + parse_software_list(file, m_filename, m_shortname, m_description, m_infolist, errs); + file.close(); m_errors = errs.str(); } + else if (std::errc::no_such_file_or_directory == filerr) + { + osd_printf_verbose("%s: Software list %s not found\n", tag(), m_filename); + } else - m_errors = string_format("Error opening file: %s\n", filename()); + { + m_errors = string_format("Error opening file: %s\n", m_filename); + } // indicate that we've been parsed m_parsed = true; @@ -360,7 +374,7 @@ device_image_interface *software_list_device::find_mountable_image(const machine if (mount != nullptr && strcmp(mount, "no") == 0) return nullptr; - for (device_image_interface &image : image_interface_iterator(mconfig.root_device())) + for (device_image_interface &image : image_interface_enumerator(mconfig.root_device())) { const char *interface = image.image_interface(); if (interface != nullptr && part.matches_interface(interface) && filter(image)) @@ -384,7 +398,7 @@ device_image_interface *software_list_device::find_mountable_image(const machine // When softlists were refactored in MAME 0.183, this was changed to build a "plan" for what needs to be loaded, so // it was incorrect to check the image slot. This is why an overload for find_mountable_image() was created that // takes an std::function. This overload is being preserved for compatibility with existing code, but I regard the - // continued existance of this overload is a red flag. + // continued existence of this overload as a red flag. return find_mountable_image( mconfig, part, @@ -404,12 +418,9 @@ void software_list_device::device_validity_check(validity_checker &valid) const if (valid.already_checked(std::string("softlist/").append(m_list_name).c_str())) return; - // do device validation only in case of validate command - if (!valid.validate_all()) - return; - - // actually do the validate - const_cast<software_list_device *>(this)->internal_validity_check(valid); + // skip in case of quick validation + if (!valid.quick()) + const_cast<software_list_device *>(this)->internal_validity_check(valid); } @@ -420,118 +431,187 @@ void software_list_device::device_validity_check(validity_checker &valid) const void software_list_device::internal_validity_check(validity_checker &valid) { - enum { NAME_LEN_PARENT = 16, NAME_LEN_CLONE = 16 }; + enum { NAME_LEN_LIST = 24, NAME_LEN_PARENT = 16, NAME_LEN_CLONE = 16, NAME_LEN_PART = 16 }; + auto const valid_name_char = [] (char ch) { return ((ch >= '0') && (ch <= '9')) || ((ch >= 'a') && (ch <= 'z')) || (ch == '_'); }; + auto const valid_tag_char = [] (char ch) { return ((ch >= '0') && (ch <= '9')) || ((ch >= 'a') && (ch <= 'z')) || strchr("$.:_", u8(ch)); }; + auto const valid_year_char = [] (char ch) { return isdigit(u8(ch)) || (ch == '?') || (ch == '+'); }; + auto const valid_label_char = [] (char ch) { return (ch >= ' ') && (ch <= '~') && !strchr("!$%/:\\", u8(ch)); }; + + // first parse and output core errors if any + auto const &info(get_info()); + if (!m_errors.empty()) + { + osd_printf_error("%s: Errors parsing software list:\n%s", m_filename, m_errors); + release(); + return; + } + // ignore empty shortname to work around ignoring missing software lists + if (!m_shortname.empty()) + { + if (m_list_name != m_shortname) + osd_printf_error("%s: Software list name %s does not match filename %s\n", m_filename, m_shortname, m_list_name); + + if (m_shortname.length() > NAME_LEN_LIST) + osd_printf_error("%s: %s software list name must be %d characters or less\n", m_filename, m_shortname, NAME_LEN_LIST); + + if (std::find_if_not(m_shortname.begin(), m_shortname.end(), valid_name_char) != m_shortname.end()) + osd_printf_error("%s: %s software list name contains invalid characters\n", m_filename, m_shortname); + } + + // now check the software items softlist_map names; softlist_map descriptions; - for (const software_info &swinfo : get_info()) + for (const software_info &swinfo : info) { std::string const &shortname(swinfo.shortname()); - // first parse and output core errors if any - if (m_errors.length() > 0) - { - osd_printf_error("%s: Errors parsing software list:\n%s", filename(), errors_string()); - break; - } - - // Now check if the xml data is valid: - - // Did we lost any description? if (swinfo.longname().empty()) - { - osd_printf_error("%s: %s has no description\n", filename(), shortname.c_str()); - break; - } + osd_printf_error("%s: %s has no description\n", m_filename, shortname); - // Did we lost any year? if (swinfo.year().empty()) - { - osd_printf_error("%s: %s has no year\n", filename(), shortname.c_str()); - break; - } + osd_printf_error("%s: %s has no year\n", m_filename, shortname); - // Did we lost any publisher? if (swinfo.publisher().empty()) - { - osd_printf_error("%s: %s has no publisher\n", filename(), shortname.c_str()); - break; - } + osd_printf_error("%s: %s has no publisher\n", m_filename, shortname); // Did we lost the software parts? if (swinfo.parts().empty()) - { - osd_printf_error("%s: %s has no part\n", filename(), shortname.c_str()); - break; - } + osd_printf_error("%s: %s has no parts\n", m_filename, shortname); // Second, since the xml is fine, run additional checks: // check for duplicate names - if (!names.insert(std::make_pair(shortname, &swinfo)).second) - { - const software_info *match = names.find(shortname)->second; - osd_printf_error("%s: %s is a duplicate name (%s)\n", filename(), shortname.c_str(), match->shortname().c_str()); - } + auto const dupname(names.emplace(shortname, &swinfo)); + if (!dupname.second) + osd_printf_error("%s: %s is a duplicate name (%s)\n", m_filename, shortname, dupname.first->second->shortname()); // check for duplicate descriptions - std::string longname(swinfo.longname()); - if (!descriptions.insert(std::make_pair(strmakelower(longname), &swinfo)).second) - osd_printf_error("%s: %s is a duplicate description (%s)\n", filename(), swinfo.longname().c_str(), shortname.c_str()); + auto const dupdesc(descriptions.emplace(swinfo.longname(), &swinfo)); + if (!dupdesc.second) + osd_printf_error("%s: %s has duplicate description '%s' (%s)\n", m_filename, shortname, swinfo.longname(), dupdesc.first->second->shortname()); bool const is_clone(!swinfo.parentname().empty()); if (is_clone) { if (swinfo.parentname() == shortname) { - osd_printf_error("%s: %s is set as a clone of itself\n", filename(), shortname.c_str()); - break; + osd_printf_error("%s: %s is set as a clone of itself\n", m_filename, shortname); + } + else + { + software_info const *const parent = find(swinfo.parentname()); + if (!parent) + osd_printf_error("%s: %s is a clone of non-existent parent %s\n", m_filename, shortname, swinfo.parentname()); + else if (!parent->parentname().empty()) + osd_printf_error("%s: %s is a clone %s which is a clone of %s\n", m_filename, shortname, swinfo.parentname(), parent->parentname()); } - - // make sure the parent exists - const software_info *swinfo2 = find(swinfo.parentname().c_str()); - - if (swinfo2 == nullptr) - osd_printf_error("%s: parent '%s' software for '%s' not found\n", filename(), swinfo.parentname().c_str(), shortname.c_str()); - else if (!swinfo2->parentname().empty()) - osd_printf_error("%s: %s is a clone of a clone\n", filename(), shortname.c_str()); } // make sure the driver name isn't too long if (shortname.length() > (is_clone ? NAME_LEN_CLONE : NAME_LEN_PARENT)) - osd_printf_error("%s: %s %s software name must be %d characters or less\n", filename(), shortname.c_str(), + { + osd_printf_error( + "%s: %s %s software name must be %d characters or less\n", + m_filename, + shortname, is_clone ? "clone" : "parent", is_clone ? NAME_LEN_CLONE : NAME_LEN_PARENT); + } // make sure the driver name doesn't contain invalid characters - for (char ch : shortname) - if (((ch < '0') || (ch > '9')) && ((ch < 'a') || (ch > 'z')) && (ch != '_')) - { - osd_printf_error("%s: %s contains invalid characters\n", filename(), shortname.c_str()); - break; - } + if (std::find_if_not(shortname.begin(), shortname.end(), valid_name_char) != shortname.end()) + osd_printf_error("%s: %s contains invalid characters\n", m_filename, shortname); // make sure the year is only digits, '?' or '+' - for (char ch : swinfo.year()) - if (!isdigit(u8(ch)) && (ch != '?') && (ch != '+')) - { - osd_printf_error("%s: %s has an invalid year '%s'\n", filename(), shortname.c_str(), swinfo.year().c_str()); - break; - } + if (std::find_if_not(swinfo.year().begin(), swinfo.year().end(), valid_year_char) != swinfo.year().end()) + osd_printf_error("%s: %s has an invalid year '%s'\n", m_filename, shortname, swinfo.year()); - softlist_map part_names; - for (const software_part &part : swinfo.parts()) + std::set<std::string> part_names; + for (software_part const &part : swinfo.parts()) { if (part.interface().empty()) - osd_printf_error("%s: %s has a part (%s) without interface\n", filename(), shortname.c_str(), part.name().c_str()); + osd_printf_error("%s: %s part %s has no interface\n", m_filename, shortname, part.name()); if (part.romdata().empty()) - osd_printf_error("%s: %s has a part (%s) with no data\n", filename(), shortname.c_str(), part.name().c_str()); + osd_printf_error("%s: %s part %s has no data areas\n", m_filename, shortname, part.name()); - if (!part_names.insert(std::make_pair(part.name(), &swinfo)).second) - osd_printf_error("%s: %s has a part (%s) whose name is duplicate\n", filename(), shortname.c_str(), part.name().c_str()); + if (!part_names.emplace(part.name()).second) + osd_printf_error("%s: %s part %s has duplicate name\n", m_filename, shortname, part.name()); + + if (part.name().length() > NAME_LEN_PART) + osd_printf_error("%s: %s part %s name must be %d characters or less\n", m_filename, shortname, part.name(), NAME_LEN_PART); + + if (std::find_if_not(part.name().begin(), part.name().end(), valid_name_char) != part.name().end()) + osd_printf_error("%s: %s part %s contains invalid characters\n", m_filename, shortname, part.name()); + + // validate data areas + // based on ROM validation code from validity.cpp but adapted to work with rom_entry and ignore unavailable features like BIOS + if (!part.romdata().empty()) + { + std::map<std::string, u32> data_area_map; + char const *last_region_name = "???"; + char const *last_name = "???"; + u32 current_length = 0; + int items_since_region = 1; + for (rom_entry const *romp = &part.romdata().front(); romp && !ROMENTRY_ISEND(romp); ++romp) + { + if (ROMENTRY_ISREGION(romp)) // if this is a region, make sure it's valid, and record the length + { + // if we haven't seen any items since the last region, print a warning + if (!items_since_region) + osd_printf_verbose("%s: %s part %s has empty data area '%s' (warning)\n", m_filename, shortname, part.name(), last_region_name); + + // reset our region tracking states + items_since_region = (ROMREGION_ISERASE(romp) || ROMREGION_ISDISKDATA(romp)) ? 1 : 0; + last_region_name = romp->name().c_str(); + + // check for a valid tag + if (romp->name().size() < MIN_TAG_LENGTH) + osd_printf_error("%s: %s part %s data area name '%s' is too short (must be at least %d characters)\n", m_filename, shortname, part.name(), romp->name(), MIN_TAG_LENGTH); + + if (std::find_if_not(romp->name().begin(), romp->name().end(), valid_tag_char) != romp->name().end()) + osd_printf_error("%s: %s part %s data area name '%s' contains invalid characters\n", m_filename, shortname, part.name(), romp->name()); + + // attempt to add it to the map, reporting duplicates as errors + current_length = ROMREGION_GETLENGTH(romp); + if (!data_area_map.emplace(romp->name(), current_length).second) + osd_printf_error("%s: %s part %s data area has duplicate name '%s'\n", m_filename, shortname, part.name(), romp->name()); + if (current_length == 0) + osd_printf_error("%s: %s part %s data area '%s' has zero length\n", m_filename, shortname, part.name(), romp->name()); + } + else if (ROMENTRY_ISFILE(romp)) // if this is a file, make sure it is properly formatted + { + // track the last ROM label we found + last_name = romp->name().c_str(); + + // validate the name + if (romp->name().length() > 127) + osd_printf_error("%s: %s part %s ROM label '%s' exceeds maximum 127 characters\n", m_filename, shortname, part.name(), romp->name()); + if (std::find_if_not(romp->name().begin(), romp->name().end(), valid_label_char) != romp->name().end()) + osd_printf_error("%s: %s part %s ROM label '%s' contains invalid characters\n", m_filename, shortname, part.name(), romp->name()); + + // make sure the hash is valid + util::hash_collection hashes; + if (!hashes.from_internal_string(romp->hashdata())) + osd_printf_error("%s: %s part %s ROM '%s' has invalid hash string '%s'\n", m_filename, shortname, part.name(), romp->name(), romp->hashdata()); + } + + // for any non-region ending entries, make sure they don't extend past the end + if (!ROMENTRY_ISREGIONEND(romp) && current_length > 0) + { + items_since_region++; + if (!ROMENTRY_ISIGNORE(romp) && (ROM_GETOFFSET(romp) + ROM_GETLENGTH(romp) > current_length)) + osd_printf_error("%s: %s part %s ROM '%s' extends past the defined data area\n", m_filename, shortname, part.name(), last_name); + } + } + + // if we haven't seen any items since the last region, print a warning + if (!items_since_region) + osd_printf_verbose("%s: %s part %s has empty data area '%s' (warning)\n", m_filename, shortname, part.name(), last_region_name); + } } } - // release all the memory + // discard parsed info release(); } |