// license:BSD-3-Clause // copyright-holders:Wilbert Pol /*************************************************************************** softlist.c Software list construction helpers. ***************************************************************************/ #include "emu.h" #include "emuopts.h" #include "softlist.h" #include "validity.h" #include "expat.h" #include //************************************************************************** // TYPE DEFINITIONS //************************************************************************** typedef std::unordered_map softlist_map; // ======================> softlist_parser class softlist_parser { public: // construction (== execution) softlist_parser(software_list_device &list, std::ostringstream &errors); private: enum parse_position { POS_ROOT, POS_MAIN, POS_SOFT, POS_PART, POS_DATA }; // internal parsing helpers const char *filename() const { return m_list.filename(); } const char *infoname() const { return (m_current_info != nullptr) ? m_current_info->shortname() : "???"; } int line() const { return XML_GetCurrentLineNumber(m_parser); } int column() const { return XML_GetCurrentColumnNumber(m_parser); } const char *parser_error() const { return XML_ErrorString(XML_GetErrorCode(m_parser)); } // internal error helpers template void parse_error(Format &&fmt, Params &&... args); void unknown_tag(const char *tagname) { parse_error("Unknown tag: %s", tagname); } void unknown_attribute(const char *attrname) { parse_error("Unknown attribute: %s", attrname); } // internal helpers void parse_attributes(const char **attributes, int numattrs, const char *attrlist[], const char *outlist[]); void add_rom_entry(const char *name, const char *hashdata, UINT32 offset, UINT32 length, UINT32 flags); // expat callbacks static void start_handler(void *data, const char *tagname, const char **attributes); static void data_handler(void *data, const XML_Char *s, int len); static void end_handler(void *data, const char *name); // internal parsing void parse_root_start(const char *tagname, const char **attributes); void parse_main_start(const char *tagname, const char **attributes); void parse_soft_start(const char *tagname, const char **attributes); void parse_part_start(const char *tagname, const char **attributes); void parse_data_start(const char *tagname, const char **attributes); void parse_soft_end(const char *name); // internal parsing state software_list_device & m_list; std::ostringstream & m_errors; XML_Parser m_parser; bool m_done; bool m_data_accum_expected; std::string m_data_accum; software_info * m_current_info; software_part * m_current_part; parse_position m_pos; }; //************************************************************************** // GLOBAL VARIABLES //************************************************************************** // device type definition const device_type SOFTWARE_LIST = &device_creator; //************************************************************************** // SOFTWARE PART //************************************************************************** //------------------------------------------------- // software_part - constructor //------------------------------------------------- software_part::software_part(software_info &info, const char *name, const char *interface) : m_next(nullptr), m_info(info), m_name(name), m_interface(interface) { // ensure strings we are passed are in the string pool assert(info.list().string_pool_contains(name)); assert(info.list().string_pool_contains(interface)); } //------------------------------------------------- // feature - return the value of the given // feature, if specified //------------------------------------------------- const char *software_part::feature(const char *feature_name) const { assert(feature_name != nullptr); // scan the feature list for an entry matching feature_name and return the value for (const feature_list_item &feature : m_featurelist) if (strcmp(feature.name(), feature_name) == 0) return feature.value(); return nullptr; } //------------------------------------------------- // is_compatible - determine if we are compatible // with the given software_list_device //------------------------------------------------- software_compatibility software_part::is_compatible(const software_list_device &swlistdev) const { // get the softlist filter; if null, assume compatible const char *filter = swlistdev.filter(); if (filter == nullptr) return SOFTWARE_IS_COMPATIBLE; // copy the comma-delimited string and ensure it ends with a final comma std::string filt = std::string(filter).append(","); // get the incompatibility filter and test against it first if it exists const char *incompatibility = feature("incompatibility"); if (incompatibility != nullptr) { // copy the comma-delimited string and ensure it ends with a final comma std::string incomp = std::string(incompatibility).append(","); // iterate over filter items and see if they exist in the list; if so, it's incompatible for (int start = 0, end = filt.find_first_of(',',start); end != -1; start = end + 1, end = filt.find_first_of(',', start)) { std::string token(filt, start, end - start + 1); if (incomp.find(token) != -1) return SOFTWARE_IS_INCOMPATIBLE; } } // get the compatibility feature; if null, assume compatible const char *compatibility = feature("compatibility"); if (compatibility == nullptr) return SOFTWARE_IS_COMPATIBLE; // copy the comma-delimited string and ensure it ends with a final comma std::string comp = std::string(compatibility).append(","); // iterate over filter items and see if they exist in the compatibility list; if so, it's compatible for (int start = 0, end = filt.find_first_of(',',start); end != -1; start = end + 1, end = filt.find_first_of(',', start)) { std::string token(filt, start, end - start + 1); if (comp.find(token) != -1) return SOFTWARE_IS_COMPATIBLE; } return SOFTWARE_NOT_COMPATIBLE; } //------------------------------------------------- // matches_interface - determine if we match // an interface in the provided list //------------------------------------------------- bool software_part::matches_interface(const char *interface_list) const { // if we have no interface, then we match by default if (m_interface == nullptr) return true; // copy the comma-delimited interface list and ensure it ends with a final comma std::string interfaces = std::string(interface_list).append(","); // then add a comma to the end of our interface and return true if we find it in the list string std::string our_interface = std::string(m_interface).append(","); return (interfaces.find(our_interface) != -1); } //------------------------------------------------- // find_mountable_image - find an image interface // that can automatically mount this software part //------------------------------------------------- device_image_interface *software_part::find_mountable_image(const machine_config &mconfig) const { // if automount="no", don't bother const char *mount = feature("automount"); if (mount != nullptr && strcmp(mount, "no") == 0) return nullptr; for (device_image_interface &image : image_interface_iterator(mconfig.root_device())) { const char *interface = image.image_interface(); if (interface != nullptr && matches_interface(interface)) { // mount only if not already mounted const char *option = mconfig.options().value(image.brief_instance_name()); if (*option == '\0' && !image.filename()) return ℑ } } return nullptr; } //************************************************************************** // SOFTWARE INFO //************************************************************************** //------------------------------------------------- // software_info - constructor //------------------------------------------------- software_info::software_info(software_list_device &list, const char *name, const char *parent, const char *supported) : m_next(nullptr), m_list(list), m_supported(SOFTWARE_SUPPORTED_YES), m_shortname(name), m_longname(nullptr), m_parentname(parent), m_year(nullptr), m_publisher(nullptr) { // ensure strings we are passed are in the string pool assert(list.string_pool_contains(name)); assert(list.string_pool_contains(parent)); // handle the supported flag if provided if (supported != nullptr) { if (strcmp(supported, "partial") == 0) m_supported = SOFTWARE_SUPPORTED_PARTIAL; else if (strcmp(supported, "no") == 0) m_supported = SOFTWARE_SUPPORTED_NO; } } //------------------------------------------------- // find_part - find a part by name with an // optional interface match //------------------------------------------------- software_part *software_info::find_part(const char *partname, const char *interface) { // if neither partname nor interface supplied, then we just return the first entry if (partname != nullptr && strlen(partname)==0) partname = nullptr; if (partname == nullptr && interface == nullptr) return m_partdata.first(); // look for the part by name and match against the interface if provided for (software_part &part : m_partdata) if (partname != nullptr && strcmp(partname, part.name()) == 0) { if (interface == nullptr || part.matches_interface(interface)) return ∂ } else if (partname == nullptr && part.matches_interface(interface)) return ∂ return nullptr; } //------------------------------------------------- // has_multiple_parts - return true if we have // more than one part matching the given // interface //------------------------------------------------- bool software_info::has_multiple_parts(const char *interface) const { int count = 0; // increment the count for each match and stop if we hit more than 1 for (software_part &part : m_partdata) if (part.matches_interface(interface)) if (++count > 1) return true; return false; } //************************************************************************** // SOFTWARE LIST DEVICE //************************************************************************** //------------------------------------------------- // software_list_device - constructor //------------------------------------------------- software_list_device::software_list_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) : device_t(mconfig, SOFTWARE_LIST, "Software list", tag, owner, clock, "software_list", __FILE__), m_list_type(SOFTWARE_LIST_ORIGINAL_SYSTEM), m_filter(nullptr), m_parsed(false), m_file(mconfig.options().hash_path(), OPEN_FLAG_READ), m_description(nullptr) { } //------------------------------------------------- // static_set_type - configuration helper // to set the list type //------------------------------------------------- void software_list_device::static_set_type(device_t &device, const char *list, softlist_type list_type) { software_list_device &swlistdev = downcast(device); swlistdev.m_list_name.assign(list); swlistdev.m_list_type = list_type; } //------------------------------------------------- // static_set_custom_handler - configuration // helper to set a custom callback //------------------------------------------------- void software_list_device::static_set_filter(device_t &device, const char *filter) { downcast(device).m_filter = filter; } //------------------------------------------------- // device_start - device-specific startup //------------------------------------------------- void software_list_device::device_start() { } //------------------------------------------------- // find_approx_matches - search ourselves for // a list of possible matches of the given name // and optional interface //------------------------------------------------- void software_list_device::find_approx_matches(const char *name, int matches, software_info **list, const char *interface) { // if no name, return if (name == nullptr || name[0] == 0) return; // initialize everyone's states std::vector penalty(matches); for (int matchnum = 0; matchnum < matches; matchnum++) { penalty[matchnum] = 9999; list[matchnum] = nullptr; } // iterate over our info (will cause a parse if needed) for (software_info &swinfo : get_info()) { software_part *part = swinfo.first_part(); if ((interface == nullptr || part->matches_interface(interface)) && part->is_compatible(*this) == SOFTWARE_IS_COMPATIBLE) { // pick the best match between driver name and description int longpenalty = driver_list::penalty_compare(name, swinfo.longname()); int shortpenalty = driver_list::penalty_compare(name, swinfo.shortname()); int curpenalty = MIN(longpenalty, shortpenalty); // insert into the sorted table of matches for (int matchnum = matches - 1; matchnum >= 0; matchnum--) { // stop if we're worse than the current entry if (curpenalty >= penalty[matchnum]) break; // as long as this isn't the last entry, bump this one down if (matchnum < matches - 1) { penalty[matchnum + 1] = penalty[matchnum]; list[matchnum + 1] = list[matchnum]; } list[matchnum] = &swinfo; penalty[matchnum] = curpenalty; } } } } //------------------------------------------------- // release - reset to a pre-parsed state //------------------------------------------------- void software_list_device::release() { osd_printf_verbose("Resetting %s\n", m_file.filename()); m_parsed = false; m_description = nullptr; m_errors.clear(); m_infolist.reset(); m_stringpool.reset(); } //------------------------------------------------- // find_by_name - find a software list by name // across all software list devices //------------------------------------------------- software_list_device *software_list_device::find_by_name(const machine_config &config, const char *name) { // iterate over each device in the system and find a match for (software_list_device &swlistdev : software_list_device_iterator(config.root_device())) if (strcmp(swlistdev.list_name(), name) == 0) return &swlistdev; return nullptr; } //------------------------------------------------- // software_display_matches - display a list of // possible matches in the system to the given // name, across all software list devices //------------------------------------------------- void software_list_device::display_matches(const machine_config &config, const char *interface, const char *name) { // check if there is at least one software list software_list_device_iterator 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); // 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.) software_info *matches[16] = { nullptr }; swlistdev.find_approx_matches(name, ARRAY_LENGTH(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(), swlistdev.description()); else osd_printf_error("* Compatible software list \"%s\" (%s) matches: \n", swlistdev.list_name(), swlistdev.description()); // print them out for (auto & matche : matches) if (matche != nullptr) osd_printf_error("%-18s%s\n", matche->shortname(), matche->longname()); osd_printf_error("\n"); } } } //------------------------------------------------- // find - find an item by name in the software // list, using wildcards and optionally starting // from an intermediate point //------------------------------------------------- software_info *software_list_device::find(const char *look_for, software_info *prev) { // nullptr search returns nothing if (look_for == nullptr) return nullptr; bool iswild = strchr(look_for, '*') != nullptr || strchr(look_for, '?'); // find a match (will cause a parse if needed when calling get_info) for (prev = (prev != nullptr) ? prev->next() : get_info().first(); prev != nullptr; prev = prev->next()) if ((iswild && core_strwildcmp(look_for, prev->shortname()) == 0) || core_stricmp(look_for, prev->shortname()) == 0) break; return prev; } //------------------------------------------------- // parse - parse our softlist file //------------------------------------------------- void software_list_device::parse() { // skip if done if (m_parsed) return; // reset the errors 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) { // parse if no error std::ostringstream errs; softlist_parser parser(*this, errs); m_file.close(); m_errors = errs.str(); } else m_errors = string_format("Error opening file: %s\n", filename()); // indicate that we've been parsed m_parsed = true; } //------------------------------------------------- // device_validity_check - validate the device // configuration //------------------------------------------------- void software_list_device::device_validity_check(validity_checker &valid) const { // add to the global map whenever we check a list so we don't re-check // it in the future if (valid.already_checked(std::string("softlist/").append(m_list_name.c_str()).c_str())) return; // do device validation only in case of validate command if (!valid.validate_all()) return; // actually do the validate const_cast(this)->internal_validity_check(valid); } //------------------------------------------------- // internal_validity_check - internal helper to // check the list //------------------------------------------------- void software_list_device::internal_validity_check(validity_checker &valid) { enum { NAME_LEN_PARENT = 8, NAME_LEN_CLONE = 16 }; softlist_map names; softlist_map descriptions; for (software_info &swinfo : get_info()) { // 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() == nullptr) { osd_printf_error("%s: %s has no description\n", filename(), swinfo.shortname()); break; } // Did we lost any year? if (swinfo.year() == nullptr) { osd_printf_error("%s: %s has no year\n", filename(), swinfo.shortname()); break; } // Did we lost any publisher? if (swinfo.publisher() == nullptr) { osd_printf_error("%s: %s has no publisher\n", filename(), swinfo.shortname()); break; } // Did we lost the software parts? if (swinfo.parts().empty()) { osd_printf_error("%s: %s has no part\n", filename(), swinfo.shortname()); break; } // Second, since the xml is fine, run additional checks: // check for duplicate names if (!names.insert(std::make_pair(swinfo.shortname(), &swinfo)).second) { software_info *match = names.find(swinfo.shortname())->second; osd_printf_error("%s: %s is a duplicate name (%s)\n", filename(), swinfo.shortname(), match->shortname()); } // check for duplicate descriptions std::string longname = std::string(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(), swinfo.shortname()); bool is_clone = false; if (swinfo.parentname() != nullptr) { is_clone = true; if (strcmp(swinfo.parentname(), swinfo.shortname()) == 0) { osd_printf_error("%s: %s is set as a clone of itself\n", filename(), swinfo.shortname()); break; } // make sure the parent exists software_info *swinfo2 = find(swinfo.parentname()); if (swinfo2 == nullptr) osd_printf_error("%s: parent '%s' software for '%s' not found\n", filename(), swinfo.parentname(), swinfo.shortname()); else if (swinfo2->parentname() != nullptr) osd_printf_error("%s: %s is a clone of a clone\n", filename(), swinfo.shortname()); } // make sure the driver name is 8 chars or less if ((is_clone && strlen(swinfo.shortname()) > NAME_LEN_CLONE) || (!is_clone && strlen(swinfo.shortname()) > NAME_LEN_PARENT)) osd_printf_error("%s: %s %s driver name must be %d characters or less\n", filename(), swinfo.shortname(), is_clone ? "clone" : "parent", is_clone ? NAME_LEN_CLONE : NAME_LEN_PARENT); // make sure the year is only digits, '?' or '+' for (const char *s = swinfo.year(); *s != 0; s++) if (!isdigit((UINT8)*s) && *s != '?' && *s != '+') { osd_printf_error("%s: %s has an invalid year '%s'\n", filename(), swinfo.shortname(), swinfo.year()); break; } softlist_map part_names; for (software_part &part : swinfo.parts()) { if (part.interface() == nullptr) osd_printf_error("%s: %s has a part (%s) without interface\n", filename(), swinfo.shortname(), part.name()); if (part.romdata() == nullptr) osd_printf_error("%s: %s has a part (%s) with no data\n", filename(), swinfo.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(), swinfo.shortname(), part.name()); for (const rom_entry *data = part.romdata(); data->_name != nullptr; data++) if (data->_hashdata != nullptr) { // make sure the hash is valid hash_collection hashes; if (!hashes.from_internal_string(data->_hashdata)) osd_printf_error("%s: %s has rom '%s' with an invalid hash string '%s'\n", filename(), swinfo.shortname(), data->_name, data->_hashdata); } } } // release all the memory release(); } //************************************************************************** // SOFTWARE LIST PARSER //************************************************************************** //------------------------------------------------- // softlist_parser - constructor //------------------------------------------------- softlist_parser::softlist_parser(software_list_device &list, std::ostringstream &errors) : m_list(list), m_errors(errors), m_done(false), m_data_accum_expected(false), m_current_info(nullptr), m_current_part(nullptr), m_pos(POS_ROOT) { osd_printf_verbose("Parsing %s\n", m_list.m_file.filename()); // create the parser m_parser = XML_ParserCreate_MM(nullptr, nullptr, nullptr); if (m_parser == nullptr) throw std::bad_alloc(); // set the handlers XML_SetUserData(m_parser, this); XML_SetElementHandler(m_parser, &softlist_parser::start_handler, &softlist_parser::end_handler); XML_SetCharacterDataHandler(m_parser, &softlist_parser::data_handler); // parse the file contents m_list.m_file.seek(0, SEEK_SET); char buffer[1024]; while (!m_done) { UINT32 length = m_list.m_file.read(buffer, sizeof(buffer)); m_done = m_list.m_file.eof(); if (XML_Parse(m_parser, buffer, length, m_done) == XML_STATUS_ERROR) { parse_error("%s", parser_error()); break; } } // free the parser XML_ParserFree(m_parser); osd_printf_verbose("Parsing complete\n"); } //------------------------------------------------- // parse_error - append a parsing error with // filename, line and column information //------------------------------------------------- template inline void softlist_parser::parse_error(Format &&fmt, Params &&... args) { // always start with filename(line.column): util::stream_format(m_errors, "%s(%d.%d): ", filename(), line(), column()); // append the remainder of the string util::stream_format(m_errors, std::forward(fmt), std::forward(args)...); // append a newline at the end m_errors.put('\n'); } //------------------------------------------------- // parse_attributes - helper to parse a set of // attributes into a list of strings //------------------------------------------------- void softlist_parser::parse_attributes(const char **attributes, int numattrs, const char *attrlist[], const char *outlist[]) { // iterate over attribute/value pairs for( ; attributes[0]; attributes += 2) { int index; // look for a match among the attributes provided for (index = 0; index < numattrs; index++) if (strcmp(attributes[0], attrlist[index]) == 0) { // if found, set the corresponding output entry to the value outlist[index] = attributes[1]; break; } // if not found, report an unknown attribute if (index == numattrs) unknown_attribute(attributes[0]); } } //------------------------------------------------- // add_rom_entry - append a new ROM entry to the // current part's list //------------------------------------------------- void softlist_parser::add_rom_entry(const char *name, const char *hashdata, UINT32 offset, UINT32 length, UINT32 flags) { // get the current part if (m_current_part == nullptr) { parse_error("ROM entry added in invalid context"); return; } // make sure we don't add duplicate regions if (name != nullptr && (flags & ROMENTRY_TYPEMASK) == ROMENTRYTYPE_REGION) for (auto & elem : m_current_part->m_romdata) if (elem._name != nullptr && strcmp(elem._name, name) == 0) parse_error("Duplicated dataarea %s in software %s", name, infoname()); // create the new entry and append it rom_entry entry; entry._name = m_list.add_string(name); entry._hashdata = m_list.add_string(hashdata); entry._offset = offset; entry._length = length; entry._flags = flags; m_current_part->m_romdata.push_back(entry); } //------------------------------------------------- // start_handler - expat handler for tag start //------------------------------------------------- void softlist_parser::start_handler(void *data, const char *tagname, const char **attributes) { // switch off the current state softlist_parser *state = reinterpret_cast(data); switch (state->m_pos) { case POS_ROOT: state->parse_root_start(tagname, attributes); break; case POS_MAIN: state->parse_main_start(tagname, attributes); break; case POS_SOFT: state->parse_soft_start(tagname, attributes); break; case POS_PART: state->parse_part_start(tagname, attributes); break; case POS_DATA: state->parse_data_start(tagname, attributes); break; } // increment the state since this is a tag start state->m_pos = parse_position(state->m_pos + 1); } //------------------------------------------------- // end_handler - handle end-of-tag post-processing //------------------------------------------------- void softlist_parser::end_handler(void *data, const char *name) { // reset the text destination and bump the position down softlist_parser *state = reinterpret_cast(data); state->m_pos = parse_position(state->m_pos - 1); // switch off of the new position switch (state->m_pos) { case POS_ROOT: break; case POS_MAIN: state->m_current_info = nullptr; break; case POS_SOFT: state->parse_soft_end(name); state->m_current_part = nullptr; break; case POS_PART: break; case POS_DATA: break; } // stop accumulating state->m_data_accum_expected = false; state->m_data_accum.clear(); } //------------------------------------------------- // data_handler - expat data handler //------------------------------------------------- void softlist_parser::data_handler(void *data, const XML_Char *s, int len) { softlist_parser *state = reinterpret_cast(data); // if we have an std::string to accumulate data in, do it if (state->m_data_accum_expected) state->m_data_accum.append(s, len); // otherwise, report an error if the data is non-blank else for (int i = 0; i < len; i++) if (!isspace(s[i])) { state->parse_error("Unexpected content"); break; } } //------------------------------------------------- // parse_root_start - handle tag start at the root //------------------------------------------------- void softlist_parser::parse_root_start(const char *tagname, const char **attributes) { // if (strcmp(tagname, "softwarelist") == 0) { static const char *attrnames[] = { "name", "description" }; const char *attrvalues[ARRAY_LENGTH(attrnames)] = { nullptr }; parse_attributes(attributes, ARRAY_LENGTH(attrnames), attrnames, attrvalues); if (attrvalues[1] != nullptr) m_list.m_description = m_list.add_string(attrvalues[1]); } else unknown_tag(tagname); } //------------------------------------------------- // parse_main_start - handle tag start within // a softwarelist tag //------------------------------------------------- void softlist_parser::parse_main_start(const char *tagname, const char **attributes) { // if (strcmp(tagname, "software") == 0) { static const char *attrnames[] = { "name", "cloneof", "supported" }; const char *attrvalues[ARRAY_LENGTH(attrnames)] = { nullptr }; parse_attributes(attributes, ARRAY_LENGTH(attrnames), attrnames, attrvalues); if (attrvalues[0] != nullptr) m_current_info = &m_list.m_infolist.append(*global_alloc(software_info(m_list, m_list.add_string(attrvalues[0]), m_list.add_string(attrvalues[1]), attrvalues[2]))); else parse_error("No name defined for item"); } else unknown_tag(tagname); } //------------------------------------------------- // parse_main_start - handle tag start within // a software tag //------------------------------------------------- void softlist_parser::parse_soft_start(const char *tagname, const char **attributes) { // get the current info; error if none if (m_current_info == nullptr) { parse_error("Tag %s found outside of software context", tagname); return; } // if (strcmp(tagname, "description") == 0) m_data_accum_expected = true; // else if (strcmp(tagname, "year") == 0) m_data_accum_expected = true; // else if (strcmp(tagname, "publisher") == 0) m_data_accum_expected = true; // else if (strcmp(tagname, "info") == 0) { static const char *attrnames[] = { "name", "value" }; const char *attrvalues[ARRAY_LENGTH(attrnames)] = { nullptr }; parse_attributes(attributes, ARRAY_LENGTH(attrnames), attrnames, attrvalues); if (attrvalues[0] != nullptr && attrvalues[1] != nullptr) m_current_info->m_other_info.append(*global_alloc(feature_list_item(m_list.add_string(attrvalues[0]), m_list.add_string(attrvalues[1])))); else parse_error("Incomplete other_info definition"); } // else if (strcmp(tagname, "sharedfeat") == 0) { static const char *attrnames[] = { "name", "value" }; const char *attrvalues[ARRAY_LENGTH(attrnames)] = { nullptr }; parse_attributes(attributes, ARRAY_LENGTH(attrnames), attrnames, attrvalues); if (attrvalues[0] != nullptr && attrvalues[1] != nullptr) m_current_info->m_shared_info.append(*global_alloc(feature_list_item(m_list.add_string(attrvalues[0]), m_list.add_string(attrvalues[1])))); else parse_error("Incomplete sharedfeat definition"); } // else if (strcmp(tagname, "part" ) == 0) { static const char *attrnames[] = { "name", "interface" }; const char *attrvalues[ARRAY_LENGTH(attrnames)] = { nullptr }; parse_attributes(attributes, ARRAY_LENGTH(attrnames), attrnames, attrvalues); if (attrvalues[0] != nullptr && attrvalues[1] != nullptr && strcmp(attrvalues[0], "") != 0 && strcmp(attrvalues[1], "") != 0) m_current_part = &m_current_info->m_partdata.append(*global_alloc(software_part(*m_current_info, m_list.add_string(attrvalues[0]), m_list.add_string(attrvalues[1])))); else parse_error("Incomplete part definition"); } else unknown_tag(tagname); } //------------------------------------------------- // parse_part_start - handle tag start within // a part tag //------------------------------------------------- void softlist_parser::parse_part_start(const char *tagname, const char **attributes) { // get the current part; error if none if (m_current_part == nullptr) { parse_error("Tag %s found outside of part context", tagname); return; } // if (strcmp(tagname, "dataarea") == 0) { static const char *attrnames[] = { "name", "size", "width", "endianness" }; const char *attrvalues[ARRAY_LENGTH(attrnames)] = { nullptr }; parse_attributes(attributes, ARRAY_LENGTH(attrnames), attrnames, attrvalues); if (attrvalues[0] != nullptr && attrvalues[1] != nullptr && strcmp(attrvalues[0], "") != 0 && strcmp(attrvalues[1], "") != 0) { // handle region attributes const char *width = attrvalues[2]; const char *endianness = attrvalues[3]; UINT32 regionflags = ROMENTRYTYPE_REGION; if (width != nullptr) { if (strcmp(width, "8") == 0) regionflags |= ROMREGION_8BIT; else if (strcmp(width, "16") == 0) regionflags |= ROMREGION_16BIT; else if (strcmp(width, "32") == 0) regionflags |= ROMREGION_32BIT; else if (strcmp(width, "64") == 0) regionflags |= ROMREGION_64BIT; else parse_error("Invalid dataarea width"); } if (endianness != nullptr) { if (strcmp(endianness, "little") == 0) regionflags |= ROMREGION_LE; else if (strcmp(endianness, "big") == 0) regionflags |= ROMREGION_BE; else parse_error("Invalid dataarea endianness"); } add_rom_entry(attrvalues[0], nullptr, 0, strtol(attrvalues[1], nullptr, 0), regionflags); } else parse_error("Incomplete dataarea definition"); } // else if (strcmp(tagname, "diskarea") == 0) { static const char *attrnames[] = { "name" }; const char *attrvalues[ARRAY_LENGTH(attrnames)] = { nullptr }; parse_attributes(attributes, ARRAY_LENGTH(attrnames), attrnames, attrvalues); if (attrvalues[0] != nullptr) add_rom_entry(attrvalues[0], nullptr, 0, 1, ROMENTRYTYPE_REGION | ROMREGION_DATATYPEDISK); else parse_error("Incomplete diskarea definition"); } // else if (strcmp(tagname, "feature") == 0) { static const char *attrnames[] = { "name", "value" }; const char *attrvalues[ARRAY_LENGTH(attrnames)] = { nullptr }; parse_attributes(attributes, ARRAY_LENGTH(attrnames), attrnames, attrvalues); if (attrvalues[0] != nullptr) m_current_part->m_featurelist.append(*global_alloc(feature_list_item(m_list.add_string(attrvalues[0]), m_list.add_string(attrvalues[1])))); else parse_error("Incomplete feature definition"); } // else if (strcmp(tagname, "dipswitch") == 0) ; else unknown_tag(tagname); } //------------------------------------------------- // parse_data_start - handle tag start within a // dataarea or diskarea tag //------------------------------------------------- void softlist_parser::parse_data_start(const char *tagname, const char **attributes) { // get the current part; error if none if (m_current_part == nullptr) { parse_error("Tag %s found outside of part context", tagname); return; } // if (strcmp(tagname, "rom") == 0) { static const char *attrnames[] = { "name", "size", "crc", "sha1", "offset", "value", "status", "loadflag" }; const char *attrvalues[ARRAY_LENGTH(attrnames)] = { nullptr }; parse_attributes(attributes, ARRAY_LENGTH(attrnames), attrnames, attrvalues); const char *name = attrvalues[0]; const char *sizestr = attrvalues[1]; const char *crc = attrvalues[2]; const char *sha1 = attrvalues[3]; const char *offsetstr = attrvalues[4]; const char *value = attrvalues[5]; const char *status = attrvalues[6]; const char *loadflag = attrvalues[7]; if (sizestr != nullptr && offsetstr != nullptr) { UINT32 length = strtol(sizestr, nullptr, 0); UINT32 offset = strtol(offsetstr, nullptr, 0); if (loadflag != nullptr && strcmp(loadflag, "reload") == 0) add_rom_entry(nullptr, nullptr, offset, length, ROMENTRYTYPE_RELOAD | ROM_INHERITFLAGS); else if (loadflag != nullptr && strcmp(loadflag, "reload_plain") == 0) add_rom_entry(nullptr, nullptr, offset, length, ROMENTRYTYPE_RELOAD); else if (loadflag != nullptr && strcmp(loadflag, "continue") == 0) add_rom_entry(nullptr, nullptr, offset, length, ROMENTRYTYPE_CONTINUE | ROM_INHERITFLAGS); else if (loadflag != nullptr && strcmp(loadflag, "fill") == 0) add_rom_entry(nullptr, (const char *)(FPTR)(strtol(value, nullptr, 0) & 0xff), offset, length, ROMENTRYTYPE_FILL); else if (name != nullptr) { bool baddump = (status != nullptr && strcmp(status, "baddump") == 0); bool nodump = (status != nullptr && strcmp(status, "nodump") == 0); std::string hashdata; if (nodump) { hashdata = string_format("%s", NO_DUMP); if (crc != nullptr && sha1 != nullptr) parse_error("No need for hash definition"); } else { if (crc != nullptr && sha1 != nullptr) hashdata = string_format("%c%s%c%s%s", hash_collection::HASH_CRC, crc, hash_collection::HASH_SHA1, sha1, (baddump ? BAD_DUMP : "")); else parse_error("Incomplete rom hash definition"); } // Handle loadflag attribute int romflags = 0; if (loadflag != nullptr && strcmp(loadflag, "load16_word_swap") == 0) romflags = ROM_GROUPWORD | ROM_REVERSE; else if (loadflag != nullptr && strcmp(loadflag, "load16_byte") == 0) romflags = ROM_SKIP(1); else if (loadflag != nullptr && strcmp(loadflag, "load32_word_swap") == 0) romflags = ROM_GROUPWORD | ROM_REVERSE | ROM_SKIP(2); else if (loadflag != nullptr && strcmp(loadflag, "load32_word") == 0) romflags = ROM_GROUPWORD | ROM_SKIP(2); else if (loadflag != nullptr && strcmp(loadflag, "load32_byte") == 0) romflags = ROM_SKIP(3); add_rom_entry(name, hashdata.c_str(), offset, length, ROMENTRYTYPE_ROM | romflags); } else parse_error("Rom name missing"); } else if (sizestr != nullptr && loadflag != nullptr && strcmp(loadflag, "ignore") == 0) { UINT32 length = strtol(sizestr, nullptr, 0); add_rom_entry(nullptr, nullptr, 0, length, ROMENTRYTYPE_IGNORE | ROM_INHERITFLAGS); } else parse_error("Incomplete rom definition"); } // else if (strcmp(tagname, "disk") == 0) { static const char *attrnames[] = { "name", "sha1", "status", "writeable" }; const char *attrvalues[ARRAY_LENGTH(attrnames)] = { nullptr }; parse_attributes(attributes, ARRAY_LENGTH(attrnames), attrnames, attrvalues); const char *name = attrvalues[0]; const char *sha1 = attrvalues[1]; const char *status = attrvalues[2]; const char *writeablestr = attrvalues[3]; if (name != nullptr && sha1 != nullptr) { bool baddump = (status != nullptr && strcmp(status, "baddump") == 0); bool nodump = (status != nullptr && strcmp(status, "nodump" ) == 0); bool writeable = (writeablestr != nullptr && strcmp(writeablestr, "yes") == 0); std::string hashdata = string_format("%c%s%s", hash_collection::HASH_SHA1, sha1, (nodump ? NO_DUMP : (baddump ? BAD_DUMP : ""))); add_rom_entry(name, hashdata.c_str(), 0, 0, ROMENTRYTYPE_ROM | (writeable ? DISK_READWRITE : DISK_READONLY)); } else if (status == nullptr || !strcmp(status, "nodump")) // a no_dump chd is not an incomplete entry parse_error("Incomplete disk definition"); } // else if (strcmp(tagname, "dipvalue") == 0) ; else unknown_tag(tagname); } //------------------------------------------------- // parse_soft_end - handle end-of-tag post- // processing within the tag //------------------------------------------------- void softlist_parser::parse_soft_end(const char *tagname) { assert(m_current_info != nullptr); // if (strcmp(tagname, "description") == 0) m_current_info->m_longname = m_list.add_string(m_data_accum.c_str()); // else if (strcmp(tagname, "year") == 0) m_current_info->m_year = m_list.add_string(m_data_accum.c_str()); // else if (strcmp(tagname, "publisher") == 0) m_current_info->m_publisher = m_list.add_string(m_data_accum.c_str()); // else if (strcmp(tagname, "part") == 0) { // get the last part assert(m_current_part != nullptr); if (m_current_part == nullptr) return; // was any dataarea/rom information encountered? if so, add a terminator if (m_current_part->romdata() != nullptr) add_rom_entry(nullptr, nullptr, 0, 0, ROMENTRYTYPE_END); // get the info; if present, copy shared data (we assume name/value strings live // in the string pool and don't need to be reallocated) if (m_current_info != nullptr) for (feature_list_item &item : m_current_info->shared_info()) m_current_part->m_featurelist.append(*global_alloc(feature_list_item(item.name(), item.value()))); } }