// license:BSD-3-Clause // copyright-holders:Aaron Giles /*************************************************************************** clifront.c Command-line interface frontend for MAME. ***************************************************************************/ #include "emu.h" #include "chd.h" #include "emuopts.h" #include "jedparse.h" #include "audit.h" #include "info.h" #include "unzip.h" #include "validity.h" #include "sound/samples.h" #include "cliopts.h" #include "clifront.h" #include "xmlfile.h" #include "drivenum.h" #include "osdepend.h" #include "softlist.h" #include "ui/moptions.h" #include #include // media_identifier class identifies media by hash via a search in // the driver database class media_identifier { public: // construction/destruction media_identifier(cli_options &options); // getters int total() const { return m_total; } int matches() const { return m_matches; } int nonroms() const { return m_nonroms; } // operations void reset() { m_total = m_matches = m_nonroms = 0; } void identify(const char *name); void identify_file(const char *name); void identify_data(const char *name, const UINT8 *data, int length); int find_by_hash(const hash_collection &hashes, int length); private: // internal state driver_enumerator m_drivlist; int m_total; int m_matches; int m_nonroms; }; //************************************************************************** // CLI FRONTEND //************************************************************************** //------------------------------------------------- // cli_frontend - constructor //------------------------------------------------- cli_frontend::cli_frontend(cli_options &options, osd_interface &osd) : m_options(options), m_osd(osd), m_result(MAMERR_NONE) { } //------------------------------------------------- // ~cli_frontend - destructor //------------------------------------------------- cli_frontend::~cli_frontend() { // nuke any device options since they will leak memory m_options.remove_device_options(); } //------------------------------------------------- // execute - execute a game via the standard // command line interface //------------------------------------------------- int cli_frontend::execute(int argc, char **argv) { // wrap the core execution in a try/catch to field all fatal errors m_result = MAMERR_NONE; machine_manager *manager = machine_manager::instance(m_options, m_osd); try { // first parse options to be able to get software from it std::string option_errors; m_options.parse_command_line(argc, argv, option_errors); m_options.parse_standard_inis(option_errors); //load_translation(); load_translation(m_options); manager->start_luaengine(); if (*(m_options.software_name()) != 0) { const game_driver *system = m_options.system(); if (system == nullptr && *(m_options.system_name()) != 0) throw emu_fatalerror(MAMERR_NO_SUCH_GAME, "Unknown system '%s'", m_options.system_name()); machine_config config(*system, m_options); software_list_device_iterator iter(config.root_device()); if (iter.count() == 0) throw emu_fatalerror(MAMERR_FATALERROR, "Error: unknown option: %s\n", m_options.software_name()); bool found = false; for (software_list_device *swlistdev = iter.first(); swlistdev != nullptr; swlistdev = iter.next()) { software_info *swinfo = swlistdev->find(m_options.software_name()); if (swinfo != nullptr) { // loop through all parts for (software_part *swpart = swinfo->first_part(); swpart != nullptr; swpart = swpart->next()) { const char *mount = swpart->feature("automount"); if (swpart->is_compatible(*swlistdev)) { if (mount == nullptr || strcmp(mount,"no") != 0) { // search for an image device with the right interface image_interface_iterator imgiter(config.root_device()); for (device_image_interface *image = imgiter.first(); image != nullptr; image = imgiter.next()) { const char *interface = image->image_interface(); if (interface != nullptr) { if (swpart->matches_interface(interface)) { const char *option = m_options.value(image->brief_instance_name()); // mount only if not already mounted if (*option == 0) { std::string val = string_format("%s:%s:%s", swlistdev->list_name(), m_options.software_name(), swpart->name()); // call this in order to set slot devices according to mounting m_options.parse_slot_devices(argc, argv, option_errors, image->instance_name(), val.c_str(), swpart); break; } } } } } found = true; } } } if (found) break; } if (!found) { software_list_device::display_matches(config, nullptr, m_options.software_name()); throw emu_fatalerror(MAMERR_FATALERROR, nullptr); } } // parse the command line, adding any system-specific options if (!m_options.parse_command_line(argc, argv, option_errors)) { // if we failed, check for no command and a system name first; in that case error on the name if (*(m_options.command()) == 0 && m_options.system() == nullptr && *(m_options.system_name()) != 0) throw emu_fatalerror(MAMERR_NO_SUCH_GAME, "Unknown system '%s'", m_options.system_name()); // otherwise, error on the options throw emu_fatalerror(MAMERR_INVALID_CONFIG, "%s", strtrimspace(option_errors).c_str()); } if (!option_errors.empty()) osd_printf_error("Error in command line:\n%s\n", strtrimspace(option_errors).c_str()); // determine the base name of the EXE std::string exename = core_filename_extract_base(argv[0], true); // if we have a command, execute that if (*(m_options.command()) != 0) execute_commands(exename.c_str()); // otherwise, check for a valid system else { // We need to preprocess the config files once to determine the web server's configuration // and file locations if (m_options.read_config()) { m_options.revert(OPTION_PRIORITY_INI); m_options.parse_standard_inis(option_errors); } if (!option_errors.empty()) osd_printf_error("Error in command line:\n%s\n", strtrimspace(option_errors).c_str()); // if we can't find it, give an appropriate error const game_driver *system = m_options.system(); if (system == nullptr && *(m_options.system_name()) != 0) throw emu_fatalerror(MAMERR_NO_SUCH_GAME, "Unknown system '%s'", m_options.system_name()); // otherwise just run the game m_result = manager->execute(); } } // handle exceptions of various types catch (emu_fatalerror &fatal) { std::string str(fatal.string()); strtrimspace(str); osd_printf_error("%s\n", str.c_str()); m_result = (fatal.exitcode() != 0) ? fatal.exitcode() : MAMERR_FATALERROR; // if a game was specified, wasn't a wildcard, and our error indicates this was the // reason for failure, offer some suggestions if (m_result == MAMERR_NO_SUCH_GAME && *(m_options.system_name()) != 0 && strchr(m_options.system_name(), '*') == nullptr && m_options.system() == nullptr) { // get the top 16 approximate matches driver_enumerator drivlist(m_options); int matches[16]; drivlist.find_approximate_matches(m_options.system_name(), ARRAY_LENGTH(matches), matches); // print them out osd_printf_error("\n\"%s\" approximately matches the following\n" "supported machines (best match first):\n\n", m_options.system_name()); for (auto & matche : matches) if (matche != -1) osd_printf_error("%-18s%s\n", drivlist.driver(matche).name, drivlist.driver(matche).description); } } catch (emu_exception &) { osd_printf_error("Caught unhandled emulator exception\n"); m_result = MAMERR_FATALERROR; } catch (add_exception &aex) { osd_printf_error("Tag '%s' already exists in tagged_list\n", aex.tag()); m_result = MAMERR_FATALERROR; } catch (std::exception &ex) { osd_printf_error("Caught unhandled %s exception: %s\n", typeid(ex).name(), ex.what()); m_result = MAMERR_FATALERROR; } catch (...) { osd_printf_error("Caught unhandled exception\n"); m_result = MAMERR_FATALERROR; } util::archive_file::cache_clear(); global_free(manager); return m_result; } //------------------------------------------------- // listxml - output the XML data for one or more // games //------------------------------------------------- void cli_frontend::listxml(const char *gamename) { // determine which drivers to output; return an error if none found driver_enumerator drivlist(m_options, gamename); if (drivlist.count() == 0) throw emu_fatalerror(MAMERR_NO_SUCH_GAME, "No matching games found for '%s'", gamename); // create the XML and print it to stdout info_xml_creator creator(drivlist); creator.output(stdout); } //------------------------------------------------- // listfull - output the name and description of // one or more games //------------------------------------------------- void cli_frontend::listfull(const char *gamename) { // determine which drivers to output; return an error if none found driver_enumerator drivlist(m_options, gamename); if (drivlist.count() == 0) throw emu_fatalerror(MAMERR_NO_SUCH_GAME, "No matching games found for '%s'", gamename); // print the header osd_printf_info("Name: Description:\n"); // iterate through drivers and output the info while (drivlist.next()) if ((drivlist.driver().flags & MACHINE_NO_STANDALONE) == 0) osd_printf_info("%-18s\"%s\"\n", drivlist.driver().name, drivlist.driver().description); } //------------------------------------------------- // listsource - output the name and source // filename of one or more games //------------------------------------------------- void cli_frontend::listsource(const char *gamename) { // determine which drivers to output; return an error if none found driver_enumerator drivlist(m_options, gamename); if (drivlist.count() == 0) throw emu_fatalerror(MAMERR_NO_SUCH_GAME, "No matching games found for '%s'", gamename); // iterate through drivers and output the info while (drivlist.next()) osd_printf_info("%-16s %s\n", drivlist.driver().name, core_filename_extract_base(drivlist.driver().source_file).c_str()); } //------------------------------------------------- // listclones - output the name and parent of all // clones matching the given pattern //------------------------------------------------- void cli_frontend::listclones(const char *gamename) { // start with a filtered list of drivers driver_enumerator drivlist(m_options, gamename); int original_count = drivlist.count(); // iterate through the remaining ones to see if their parent matches while (drivlist.next_excluded()) { // if we have a non-bios clone and it matches, keep it int clone_of = drivlist.clone(); if (clone_of != -1 && (drivlist.driver(clone_of).flags & MACHINE_IS_BIOS_ROOT) == 0) if (drivlist.matches(gamename, drivlist.driver(clone_of).name)) drivlist.include(); } // return an error if none found if (drivlist.count() == 0) { // see if we match but just weren't a clone if (original_count == 0) throw emu_fatalerror(MAMERR_NO_SUCH_GAME, "No matching games found for '%s'", gamename); else osd_printf_info("Found %d matches for '%s' but none were clones\n", drivlist.count(), gamename); return; } // print the header osd_printf_info("Name: Clone of:\n"); // iterate through drivers and output the info drivlist.reset(); while (drivlist.next()) { int clone_of = drivlist.clone(); if (clone_of != -1 && (drivlist.driver(clone_of).flags & MACHINE_IS_BIOS_ROOT) == 0) osd_printf_info("%-16s %-8s\n", drivlist.driver().name, drivlist.driver(clone_of).name); } } //------------------------------------------------- // listbrothers - for each matching game, output // the list of other games that share the same // source file //------------------------------------------------- void cli_frontend::listbrothers(const char *gamename) { // start with a filtered list of drivers; return an error if none found driver_enumerator initial_drivlist(m_options, gamename); if (initial_drivlist.count() == 0) throw emu_fatalerror(MAMERR_NO_SUCH_GAME, "No matching games found for '%s'", gamename); // for the final list, start with an empty driver list driver_enumerator drivlist(m_options); drivlist.exclude_all(); // scan through the initially-selected drivers while (initial_drivlist.next()) { // if we are already marked in the final list, we don't need to do anything if (drivlist.included(initial_drivlist.current())) continue; // otherwise, walk excluded items in the final list and mark any that match drivlist.reset(); while (drivlist.next_excluded()) if (strcmp(drivlist.driver().source_file, initial_drivlist.driver().source_file) == 0) drivlist.include(); } // print the header osd_printf_info("Source file: Name: Parent:\n"); // output the entries found drivlist.reset(); while (drivlist.next()) { int clone_of = drivlist.clone(); osd_printf_info("%-16s %-16s %-16s\n", core_filename_extract_base(drivlist.driver().source_file).c_str(), drivlist.driver().name, (clone_of == -1 ? "" : drivlist.driver(clone_of).name)); } } //------------------------------------------------- // listcrc - output the CRC and name of all ROMs // referenced by the emulator //------------------------------------------------- void cli_frontend::listcrc(const char *gamename) { // determine which drivers to output; return an error if none found driver_enumerator drivlist(m_options, gamename); if (drivlist.count() == 0) throw emu_fatalerror(MAMERR_NO_SUCH_GAME, "No matching games found for '%s'", gamename); // iterate through matches, and then through ROMs while (drivlist.next()) { device_iterator deviter(drivlist.config().root_device()); for (device_t *device = deviter.first(); device != nullptr; device = deviter.next()) 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 we have a CRC, display it UINT32 crc; if (hash_collection(ROM_GETHASHDATA(rom)).crc(crc)) osd_printf_info("%08x %-16s \t %-8s \t %s\n", crc, ROM_GETNAME(rom), device->shortname(), device->name()); } } } //------------------------------------------------- // listroms - output the list of ROMs referenced // by a given game or set of games //------------------------------------------------- void cli_frontend::listroms(const char *gamename) { // determine which drivers to output; return an error if none found driver_enumerator drivlist(m_options, gamename); if (drivlist.count() == 0) throw emu_fatalerror(MAMERR_NO_SUCH_GAME, "No matching games found for '%s'", gamename); // iterate through matches bool first = true; while (drivlist.next()) { // print a header if (!first) osd_printf_info("\n"); first = false; osd_printf_info("ROMs required for driver \"%s\".\n" "Name Size Checksum\n", drivlist.driver().name); // iterate through roms device_iterator deviter(drivlist.config().root_device()); for (device_t *device = deviter.first(); device != nullptr; device = deviter.next()) 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)) { // accumulate the total length of all chunks int length = -1; if (ROMREGION_ISROMDATA(region)) length = rom_file_size(rom); // start with the name const char *name = ROM_GETNAME(rom); osd_printf_info("%-20s ", name); // output the length next if (length >= 0) osd_printf_info("%7d", length); else osd_printf_info(" "); // output the hash data hash_collection hashes(ROM_GETHASHDATA(rom)); if (!hashes.flag(hash_collection::FLAG_NO_DUMP)) { if (hashes.flag(hash_collection::FLAG_BAD_DUMP)) osd_printf_info(" BAD"); osd_printf_info(" %s", hashes.macro_string().c_str()); } else osd_printf_info(" NO GOOD DUMP KNOWN"); // end with a CR osd_printf_info("\n"); } } } //------------------------------------------------- // listsamples - output the list of samples // referenced by a given game or set of games //------------------------------------------------- void cli_frontend::listsamples(const char *gamename) { // determine which drivers to output; return an error if none found driver_enumerator drivlist(m_options, gamename); if (drivlist.count() == 0) throw emu_fatalerror(MAMERR_NO_SUCH_GAME, "No matching games found for '%s'", gamename); // iterate over drivers, looking for SAMPLES devices bool first = true; while (drivlist.next()) { // see if we have samples samples_device_iterator iter(drivlist.config().root_device()); if (iter.count() == 0) continue; // print a header if (!first) osd_printf_info("\n"); first = false; osd_printf_info("Samples required for driver \"%s\".\n", drivlist.driver().name); // iterate over samples devices and print the samples from each one for (samples_device *device = iter.first(); device != nullptr; device = iter.next()) { samples_iterator sampiter(*device); for (const char *samplename = sampiter.first(); samplename != nullptr; samplename = sampiter.next()) osd_printf_info("%s\n", samplename); } } } //------------------------------------------------- // listdevices - output the list of devices // referenced by a given game or set of games //------------------------------------------------- int cli_frontend::compare_devices(const void *i1, const void *i2) { device_t *dev1 = *(device_t **)i1; device_t *dev2 = *(device_t **)i2; return strcmp(dev1->tag(), dev2->tag()); } void cli_frontend::listdevices(const char *gamename) { // determine which drivers to output; return an error if none found driver_enumerator drivlist(m_options, gamename); if (drivlist.count() == 0) throw emu_fatalerror(MAMERR_NO_SUCH_GAME, "No matching games found for '%s'", gamename); // iterate over drivers, looking for SAMPLES devices bool first = true; while (drivlist.next()) { // print a header if (!first) printf("\n"); first = false; printf("Driver %s (%s):\n", drivlist.driver().name, drivlist.driver().description); // build a list of devices device_iterator iter(drivlist.config().root_device()); std::vector device_list; for (device_t *device = iter.first(); device != nullptr; device = iter.next()) device_list.push_back(device); // sort them by tag qsort(&device_list[0], device_list.size(), sizeof(device_list[0]), compare_devices); // dump the results for (auto device : device_list) { // extract the tag, stripping the leading colon const char *tag = device->tag(); if (*tag == ':') tag++; // determine the depth int depth = 1; if (*tag == 0) { tag = ""; depth = 0; } else { for (const char *c = tag; *c != 0; c++) if (*c == ':') { tag = c + 1; depth++; } } printf(" %*s%-*s %s", depth * 2, "", 30 - depth * 2, tag, device->name()); // add more information UINT32 clock = device->clock(); if (clock >= 1000000000) printf(" @ %d.%02d GHz\n", clock / 1000000000, (clock / 10000000) % 100); else if (clock >= 1000000) printf(" @ %d.%02d MHz\n", clock / 1000000, (clock / 10000) % 100); else if (clock >= 1000) printf(" @ %d.%02d kHz\n", clock / 1000, (clock / 10) % 100); else if (clock > 0) printf(" @ %d Hz\n", clock); else printf("\n"); } } } //------------------------------------------------- // listslots - output the list of slot devices // referenced by a given game or set of games //------------------------------------------------- void cli_frontend::listslots(const char *gamename) { // determine which drivers to output; return an error if none found driver_enumerator drivlist(m_options, gamename); if (drivlist.count() == 0) throw emu_fatalerror(MAMERR_NO_SUCH_GAME, "No matching games found for '%s'", gamename); // print header printf(" SYSTEM SLOT NAME SLOT OPTIONS SLOT DEVICE NAME \n"); printf("---------- ----------- -------------- ----------------------\n"); // iterate over drivers while (drivlist.next()) { // iterate slot_interface_iterator iter(drivlist.config().root_device()); bool first = true; for (const device_slot_interface *slot = iter.first(); slot != nullptr; slot = iter.next()) { if (slot->fixed()) continue; // output the line, up to the list of extensions printf("%-13s%-10s ", first ? drivlist.driver().name : "", slot->device().tag()+1); bool first_option = true; // get the options and print them for (const device_slot_option *option = slot->first_option(); option != nullptr; option = option->next()) { if (option->selectable()) { device_t *dev = (*option->devtype())(drivlist.config(), "dummy", &drivlist.config().root_device(), 0); dev->config_complete(); if (first_option) { printf("%-15s %s\n", option->name(),dev->name()); } else { printf("%-23s %-15s %s\n", "",option->name(),dev->name()); } global_free(dev); first_option = false; } } if (first_option) printf("%-15s %s\n", "[none]","No options available"); // end the line printf("\n"); first = false; } // if we didn't get any at all, just print a none line if (first) printf("%-13s(none)\n", drivlist.driver().name); } } //------------------------------------------------- // listmedia - output the list of image devices // referenced by a given game or set of games //------------------------------------------------- void cli_frontend::listmedia(const char *gamename) { // determine which drivers to output; return an error if none found driver_enumerator drivlist(m_options, gamename); if (drivlist.count() == 0) throw emu_fatalerror(MAMERR_NO_SUCH_GAME, "No matching games found for '%s'", gamename); // print header printf(" SYSTEM MEDIA NAME (brief) IMAGE FILE EXTENSIONS SUPPORTED \n"); printf("---------- -------------------- ------------------------------------\n"); // iterate over drivers while (drivlist.next()) { // iterate image_interface_iterator iter(drivlist.config().root_device()); bool first = true; for (const device_image_interface *imagedev = iter.first(); imagedev != nullptr; imagedev = iter.next()) { // extract the shortname with parentheses std::string paren_shortname = string_format("(%s)", imagedev->brief_instance_name()); // output the line, up to the list of extensions printf("%-13s%-12s%-8s ", first ? drivlist.driver().name : "", imagedev->instance_name(), paren_shortname.c_str()); // get the extensions and print them std::string extensions(imagedev->file_extensions()); for (int start = 0, end = extensions.find_first_of(',');; start = end + 1, end = extensions.find_first_of(',', start)) { std::string curext(extensions, start, (end == -1) ? extensions.length() - start : end - start); printf(".%-5s", curext.c_str()); if (end == -1) break; } // end the line printf("\n"); first = false; } // if we didn't get any at all, just print a none line if (first) printf("%-13s(none)\n", drivlist.driver().name); } } //------------------------------------------------- // verifyroms - verify the ROM sets of one or // more games //------------------------------------------------- void cli_frontend::verifyroms(const char *gamename) { // determine which drivers to output; driver_enumerator drivlist(m_options, gamename); int correct = 0; int incorrect = 0; int notfound = 0; int matched = 0; // iterate over drivers media_auditor auditor(drivlist); while (drivlist.next()) { matched++; // audit the ROMs in this set media_auditor::summary summary = auditor.audit_media(AUDIT_VALIDATE_FAST); // if not found, count that and leave it at that if (summary == media_auditor::NOTFOUND) notfound++; // else display information about what we discovered else { // output the summary of the audit std::string summary_string; auditor.summarize(drivlist.driver().name,&summary_string); osd_printf_info("%s", summary_string.c_str()); // output the name of the driver and its clone osd_printf_info("romset %s ", drivlist.driver().name); int clone_of = drivlist.clone(); if (clone_of != -1) osd_printf_info("[%s] ", drivlist.driver(clone_of).name); // switch off of the result switch (summary) { case media_auditor::INCORRECT: osd_printf_info("is bad\n"); incorrect++; break; case media_auditor::CORRECT: osd_printf_info("is good\n"); correct++; break; case media_auditor::BEST_AVAILABLE: case media_auditor::NONE_NEEDED: osd_printf_info("is best available\n"); correct++; break; default: break; } } } if (!matched || strchr(gamename, '*') || strchr(gamename, '?')) { driver_enumerator dummy_drivlist(m_options); std::unordered_set device_map; while (dummy_drivlist.next()) { machine_config &config = dummy_drivlist.config(); device_iterator iter(config.root_device()); for (device_t *dev = iter.first(); dev != nullptr; dev = iter.next()) { if (dev->owner() != nullptr && (*(dev->shortname()) != 0) && dev->rom_region() != nullptr && (device_map.insert(dev->shortname()).second)) { if (core_strwildcmp(gamename, dev->shortname()) == 0) { matched++; // audit the ROMs in this set media_auditor::summary summary = auditor.audit_device(dev, AUDIT_VALIDATE_FAST); // if not found, count that and leave it at that if (summary == media_auditor::NOTFOUND) notfound++; // else display information about what we discovered else if (summary != media_auditor::NONE_NEEDED) { // output the summary of the audit std::string summary_string; auditor.summarize(dev->shortname(),&summary_string); osd_printf_info("%s", summary_string.c_str()); // display information about what we discovered osd_printf_info("romset %s ", dev->shortname()); // switch off of the result switch (summary) { case media_auditor::INCORRECT: osd_printf_info("is bad\n"); incorrect++; break; case media_auditor::CORRECT: osd_printf_info("is good\n"); correct++; break; case media_auditor::BEST_AVAILABLE: osd_printf_info("is best available\n"); correct++; break; default: break; } } } } } slot_interface_iterator slotiter(config.root_device()); for (const device_slot_interface *slot = slotiter.first(); slot != nullptr; slot = slotiter.next()) { for (const device_slot_option *option = slot->first_option(); option != nullptr; option = option->next()) { std::string temptag("_"); temptag.append(option->name()); device_t *dev = const_cast(config).device_add(&config.root_device(), temptag.c_str(), option->devtype(), 0); // notify this device and all its subdevices that they are now configured device_iterator subiter(*dev); for (device_t *device = subiter.first(); device != nullptr; device = subiter.next()) if (!device->configured()) device->config_complete(); if (device_map.insert(dev->shortname()).second) { if (core_strwildcmp(gamename, dev->shortname()) == 0) { matched++; if (dev->rom_region() != nullptr) { // audit the ROMs in this set media_auditor::summary summary = auditor.audit_device(dev, AUDIT_VALIDATE_FAST); // if not found, count that and leave it at that if (summary == media_auditor::NOTFOUND) notfound++; // else display information about what we discovered else if(summary != media_auditor::NONE_NEEDED) { // output the summary of the audit std::string summary_string; auditor.summarize(dev->shortname(),&summary_string); osd_printf_info("%s", summary_string.c_str()); // display information about what we discovered osd_printf_info("romset %s ", dev->shortname()); // switch off of the result switch (summary) { case media_auditor::INCORRECT: osd_printf_info("is bad\n"); incorrect++; break; case media_auditor::CORRECT: osd_printf_info("is good\n"); correct++; break; case media_auditor::BEST_AVAILABLE: osd_printf_info("is best available\n"); correct++; break; default: break; } } } } } else { // check for subdevices with ROMs (a few devices are missed otherwise, e.g. MPU401) device_iterator subiter(*dev); for (device_t *device = subiter.first(); device != nullptr; device = subiter.next()) { device_iterator subsubiter(*device); for (device_t *subdev = subsubiter.first(); subdev != nullptr; subdev = subsubiter.next()) { if (subdev->owner() == device && subdev->rom_region() != nullptr && subdev->shortname() != nullptr && subdev->shortname()[0] != '\0') { if (device_map.insert(subdev->shortname()).second) { if (core_strwildcmp(gamename, subdev->shortname()) == 0) { matched++; // audit the ROMs in this set media_auditor::summary summary = auditor.audit_device(subdev, AUDIT_VALIDATE_FAST); // if not found, count that and leave it at that if (summary == media_auditor::NOTFOUND) notfound++; // else display information about what we discovered else if (summary != media_auditor::NONE_NEEDED) { // output the summary of the audit std::string summary_string; auditor.summarize(subdev->shortname(),&summary_string); osd_printf_info("%s", summary_string.c_str()); // display information about what we discovered osd_printf_info("romset %s ", subdev->shortname()); // switch off of the result switch (summary) { case media_auditor::INCORRECT: osd_printf_info("is bad\n"); incorrect++; break; case media_auditor::CORRECT: osd_printf_info("is good\n"); correct++; break; case media_auditor::BEST_AVAILABLE: osd_printf_info("is best available\n"); correct++; break; default: break; } } } } } } } } const_cast(config).device_remove(&config.root_device(), temptag.c_str()); } } } } // clear out any cached files util::archive_file::cache_clear(); // return an error if none found if (matched == 0) throw emu_fatalerror(MAMERR_NO_SUCH_GAME, "No matching games found for '%s'", gamename); // if we didn't get anything at all, display a generic end message if (matched > 0 && correct == 0 && incorrect == 0) { if (notfound > 0) throw emu_fatalerror(MAMERR_MISSING_FILES, "romset \"%s\" not found!\n", gamename); else throw emu_fatalerror(MAMERR_MISSING_FILES, "romset \"%s\" has no roms!\n", gamename); } // otherwise, print a summary else { if (incorrect > 0) throw emu_fatalerror(MAMERR_MISSING_FILES, "%d romsets found, %d were OK.\n", correct + incorrect, correct); osd_printf_info("%d romsets found, %d were OK.\n", correct, correct); } } //------------------------------------------------- // info_verifysamples - verify the sample sets of // one or more games //------------------------------------------------- void cli_frontend::verifysamples(const char *gamename) { // determine which drivers to output; return an error if none found driver_enumerator drivlist(m_options, gamename); int correct = 0; int incorrect = 0; int notfound = 0; int matched = 0; // iterate over drivers media_auditor auditor(drivlist); while (drivlist.next()) { matched++; // audit the samples in this set media_auditor::summary summary = auditor.audit_samples(); // if not found, count that and leave it at that if (summary == media_auditor::NOTFOUND) notfound++; // else display information about what we discovered else if (summary != media_auditor::NONE_NEEDED) { // output the summary of the audit std::string summary_string; auditor.summarize(drivlist.driver().name,&summary_string); osd_printf_info("%s", summary_string.c_str()); // output the name of the driver and its clone osd_printf_info("sampleset %s ", drivlist.driver().name); int clone_of = drivlist.clone(); if (clone_of != -1) osd_printf_info("[%s] ", drivlist.driver(clone_of).name); // switch off of the result switch (summary) { case media_auditor::INCORRECT: osd_printf_info("is bad\n"); incorrect++; break; case media_auditor::CORRECT: osd_printf_info("is good\n"); correct++; break; case media_auditor::BEST_AVAILABLE: osd_printf_info("is best available\n"); correct++; break; default: break; } } } // clear out any cached files util::archive_file::cache_clear(); // return an error if none found if (matched == 0) throw emu_fatalerror(MAMERR_NO_SUCH_GAME, "No matching games found for '%s'", gamename); // if we didn't get anything at all, display a generic end message if (matched > 0 && correct == 0 && incorrect == 0) { if (notfound > 0) throw emu_fatalerror(MAMERR_MISSING_FILES, "sampleset \"%s\" not found!\n", gamename); else throw emu_fatalerror(MAMERR_MISSING_FILES, "sampleset \"%s\" not required!\n", gamename); } // otherwise, print a summary else { if (incorrect > 0) throw emu_fatalerror(MAMERR_MISSING_FILES, "%d samplesets found, %d were OK.\n", correct + incorrect, correct); osd_printf_info("%d samplesets found, %d were OK.\n", correct, correct); } } #define SOFTLIST_XML_BEGIN "\n" \ "\n" \ "\t\n" \ "\t\t\n" \ "\t\t\n" \ "\t\t\n" \ "\t\t\t\n" \ "\t\t\t\n" \ "\t\t\t\n" \ "\t\t\t\n" \ "\t\t\t\n" \ "\t\t\t\n" \ "\t\t\t\n" \ "\t\t\t\t\n" \ "\t\t\t\t\n" \ "\t\t\t\n" \ "\t\t\t\t\n" \ "\t\t\t\t\n" \ "\t\t\t\n" \ "\t\t\t\t\n" \ "\t\t\t\t\n" \ "\t\t\t\t\n" \ "\t\t\t\t\t\n" \ "\t\t\t\t\t\n" \ "\t\t\t\t\n" \ "\t\t\t\t\t\n" \ "\t\t\t\t\t\n" \ "\t\t\t\t\t\n" \ "\t\t\t\t\t\n" \ "\t\t\t\t\t\n" \ "\t\t\t\t\t\t\n" \ "\t\t\t\t\t\t\n" \ "\t\t\t\t\t\t\n" \ "\t\t\t\t\t\t\n" \ "\t\t\t\t\t\t\n" \ "\t\t\t\t\t\t\n" \ "\t\t\t\t\t\t\n" \ "\t\t\t\t\t\t\n" \ "\t\t\t\t\t\t\n" \ "\t\t\t\t\n" \ "\t\t\t\t\t\n" \ "\t\t\t\t\t\n" \ "\t\t\t\t\t\t\n" \ "\t\t\t\t\t\t\n" \ "\t\t\t\t\t\t\n" \ "\t\t\t\t\t\t\n" \ "\t\t\t\t\n" \ "\t\t\t\t\t\n" \ "\t\t\t\t\t\n" \ "\t\t\t\t\t\n" \ "\t\t\t\t\t\n" \ "\t\t\t\t\t\t\n" \ "\t\t\t\t\t\t\n" \ "\t\t\t\t\t\t\n" \ "]>\n\n" \ "\n" void cli_frontend::output_single_softlist(FILE *out, software_list_device &swlistdev) { fprintf(out, "\t\n", swlistdev.list_name(), xml_normalize_string(swlistdev.description())); for (software_info *swinfo = swlistdev.first_software_info(); swinfo != nullptr; swinfo = swinfo->next()) { fprintf( out, "\t\tshortname() ); if ( swinfo->parentname() != nullptr ) fprintf( out, " cloneof=\"%s\"", swinfo->parentname() ); if ( swinfo->supported() == SOFTWARE_SUPPORTED_PARTIAL ) fprintf( out, " supported=\"partial\"" ); if ( swinfo->supported() == SOFTWARE_SUPPORTED_NO ) fprintf( out, " supported=\"no\"" ); fprintf( out, ">\n" ); fprintf( out, "\t\t\t%s\n", xml_normalize_string(swinfo->longname()) ); fprintf( out, "\t\t\t%s\n", xml_normalize_string( swinfo->year() ) ); fprintf( out, "\t\t\t%s\n", xml_normalize_string( swinfo->publisher() ) ); for (feature_list_item *flist = swinfo->other_info(); flist != nullptr; flist = flist->next()) fprintf( out, "\t\t\t\n", flist->name(), xml_normalize_string( flist->value() ) ); for ( software_part *part = swinfo->first_part(); part != nullptr; part = part->next() ) { fprintf( out, "\t\t\tname() ); if ( part->interface() != nullptr ) fprintf( out, " interface=\"%s\"", part->interface() ); fprintf( out, ">\n"); for (feature_list_item *flist = part->featurelist(); flist != nullptr; flist = flist->next()) fprintf( out, "\t\t\t\t\n", flist->name(), xml_normalize_string(flist->value()) ); /* TODO: display rom region information */ for ( const rom_entry *region = part->romdata(); region; region = rom_next_region( region ) ) { int is_disk = ROMREGION_ISDISKDATA(region); if (!is_disk) fprintf( out, "\t\t\t\t\n", ROMREGION_GETTAG(region), ROMREGION_GETLENGTH(region) ); else fprintf( out, "\t\t\t\t\n", ROMREGION_GETTAG(region) ); for ( const rom_entry *rom = rom_first_file( region ); rom && !ROMENTRY_ISREGIONEND(rom); rom++ ) { if ( ROMENTRY_ISFILE(rom) ) { if (!is_disk) fprintf( out, "\t\t\t\t\t\n" ); } else if ( ROMENTRY_ISRELOAD(rom) ) { fprintf( out, "\t\t\t\t\t\n", ROM_GETLENGTH(rom), ROM_GETOFFSET(rom) ); } else if ( ROMENTRY_ISFILL(rom) ) { fprintf( out, "\t\t\t\t\t\n", ROM_GETLENGTH(rom), ROM_GETOFFSET(rom) ); } } if (!is_disk) fprintf( out, "\t\t\t\t\n" ); else fprintf( out, "\t\t\t\t\n" ); } fprintf( out, "\t\t\t\n" ); } fprintf( out, "\t\t\n" ); } fprintf(out, "\t\n" ); } /*------------------------------------------------- info_listsoftware - output the list of software supported by a given game or set of games TODO: Add all information read from the source files Possible improvement: use a sorted list for identifying duplicate lists. -------------------------------------------------*/ void cli_frontend::listsoftware(const char *gamename) { FILE *out = stdout; std::unordered_set list_map; bool isfirst = true; // determine which drivers to output; return an error if none found driver_enumerator drivlist(m_options, gamename); if (drivlist.count() == 0) throw emu_fatalerror(MAMERR_NO_SUCH_GAME, "No matching games found for '%s'", gamename); while (drivlist.next()) { software_list_device_iterator iter(drivlist.config().root_device()); for (software_list_device *swlistdev = iter.first(); swlistdev != nullptr; swlistdev = iter.next()) if (list_map.insert(swlistdev->list_name()).second) if (swlistdev->first_software_info() != nullptr) { if (isfirst) { fprintf(out, SOFTLIST_XML_BEGIN); isfirst = false; } output_single_softlist(out, *swlistdev); } } if (!isfirst) fprintf( out, "\n" ); else fprintf( out, "No software lists found for this system\n" ); } /*------------------------------------------------- verifysoftware - verify roms from the software list of the specified driver(s) -------------------------------------------------*/ void cli_frontend::verifysoftware(const char *gamename) { std::unordered_set list_map; int correct = 0; int incorrect = 0; int notfound = 0; int matched = 0; int nrlists = 0; // determine which drivers to process; return an error if none found driver_enumerator drivlist(m_options, gamename); if (drivlist.count() == 0) { throw emu_fatalerror(MAMERR_NO_SUCH_GAME, "No matching games found for '%s'", gamename); } media_auditor auditor(drivlist); while (drivlist.next()) { matched++; software_list_device_iterator iter(drivlist.config().root_device()); for (software_list_device *swlistdev = iter.first(); swlistdev != nullptr; swlistdev = iter.next()) if (swlistdev->list_type() == SOFTWARE_LIST_ORIGINAL_SYSTEM) if (list_map.insert(swlistdev->list_name()).second) if (swlistdev->first_software_info() != nullptr) { nrlists++; for (software_info *swinfo = swlistdev->first_software_info(); swinfo != nullptr; swinfo = swinfo->next()) { media_auditor::summary summary = auditor.audit_software(swlistdev->list_name(), swinfo, AUDIT_VALIDATE_FAST); // if not found, count that and leave it at that if (summary == media_auditor::NOTFOUND) { notfound++; } // else display information about what we discovered else if(summary != media_auditor::NONE_NEEDED) { // output the summary of the audit std::string summary_string; auditor.summarize(swinfo->shortname(), &summary_string); osd_printf_info("%s", summary_string.c_str()); // display information about what we discovered osd_printf_info("romset %s:%s ", swlistdev->list_name(), swinfo->shortname()); // switch off of the result switch (summary) { case media_auditor::INCORRECT: osd_printf_info("is bad\n"); incorrect++; break; case media_auditor::CORRECT: osd_printf_info("is good\n"); correct++; break; case media_auditor::BEST_AVAILABLE: osd_printf_info("is best available\n"); correct++; break; default: break; } } } } } // clear out any cached files util::archive_file::cache_clear(); // return an error if none found if (matched == 0) throw emu_fatalerror(MAMERR_NO_SUCH_GAME, "No matching games found for '%s'", gamename); // if we didn't get anything at all, display a generic end message if (matched > 0 && correct == 0 && incorrect == 0) { throw emu_fatalerror(MAMERR_MISSING_FILES, "romset \"%s\" has no software entries defined!\n", gamename); } // otherwise, print a summary else { if (incorrect > 0) throw emu_fatalerror(MAMERR_MISSING_FILES, "%d romsets found in %d software lists, %d were OK.\n", correct + incorrect, nrlists, correct); osd_printf_info("%d romsets found in %d software lists, %d romsets were OK.\n", correct, nrlists, correct); } } /*------------------------------------------------- getsoftlist - retrieve software list by name -------------------------------------------------*/ void cli_frontend::getsoftlist(const char *gamename) { FILE *out = stdout; std::unordered_set list_map; bool isfirst = TRUE; driver_enumerator drivlist(m_options); while (drivlist.next()) { software_list_device_iterator iter(drivlist.config().root_device()); for (software_list_device *swlistdev = iter.first(); swlistdev != nullptr; swlistdev = iter.next()) if (core_strwildcmp(gamename, swlistdev->list_name()) == 0 && list_map.insert(swlistdev->list_name()).second) if (swlistdev->first_software_info() != nullptr) { if (isfirst) { fprintf( out, SOFTLIST_XML_BEGIN); isfirst = FALSE; } output_single_softlist(out, *swlistdev); } } if (!isfirst) fprintf( out, "\n" ); else fprintf( out, "No such software lists found\n" ); } /*------------------------------------------------- verifysoftlist - verify software list by name -------------------------------------------------*/ void cli_frontend::verifysoftlist(const char *gamename) { std::unordered_set list_map; int correct = 0; int incorrect = 0; int notfound = 0; int matched = 0; driver_enumerator drivlist(m_options); media_auditor auditor(drivlist); while (drivlist.next()) { software_list_device_iterator iter(drivlist.config().root_device()); for (software_list_device *swlistdev = iter.first(); swlistdev != nullptr; swlistdev = iter.next()) if (core_strwildcmp(gamename, swlistdev->list_name()) == 0 && list_map.insert(swlistdev->list_name()).second) if (swlistdev->first_software_info() != nullptr) { matched++; // Get the actual software list contents for (software_info *swinfo = swlistdev->first_software_info(); swinfo != nullptr; swinfo = swinfo->next()) { media_auditor::summary summary = auditor.audit_software(swlistdev->list_name(), swinfo, AUDIT_VALIDATE_FAST); // if not found, count that and leave it at that if (summary == media_auditor::NOTFOUND) { notfound++; } // else display information about what we discovered else if (summary != media_auditor::NONE_NEEDED) { // output the summary of the audit std::string summary_string; auditor.summarize(swinfo->shortname(), &summary_string); osd_printf_info("%s", summary_string.c_str()); // display information about what we discovered osd_printf_info("romset %s:%s ", swlistdev->list_name(), swinfo->shortname()); // switch off of the result switch (summary) { case media_auditor::INCORRECT: osd_printf_info("is bad\n"); incorrect++; break; case media_auditor::CORRECT: osd_printf_info("is good\n"); correct++; break; case media_auditor::BEST_AVAILABLE: osd_printf_info("is best available\n"); correct++; break; default: break; } } } } } // clear out any cached files util::archive_file::cache_clear(); // return an error if none found if (matched == 0) throw emu_fatalerror(MAMERR_NO_SUCH_GAME, "No matching software lists found for '%s'", gamename); // if we didn't get anything at all, display a generic end message if (matched > 0 && correct == 0 && incorrect == 0) { throw emu_fatalerror(MAMERR_MISSING_FILES, "no romsets found for software list \"%s\"!\n", gamename); } // otherwise, print a summary else { if (incorrect > 0) throw emu_fatalerror(MAMERR_MISSING_FILES, "%d romsets found in %d software lists, %d were OK.\n", correct + incorrect, matched, correct); osd_printf_info("%d romsets found in %d software lists, %d romsets were OK.\n", correct, matched, correct); } } //------------------------------------------------- // romident - identify ROMs by looking for // matches in our internal database //------------------------------------------------- void cli_frontend::romident(const char *filename) { media_identifier ident(m_options); // identify the file, then output results osd_printf_info("Identifying %s....\n", filename); ident.identify(filename); // return the appropriate error code if (ident.matches() == ident.total()) return; else if (ident.matches() == ident.total() - ident.nonroms()) throw emu_fatalerror(MAMERR_IDENT_NONROMS, "Out of %d files, %d matched, %d are not roms.\n",ident.total(),ident.matches(),ident.nonroms()); else if (ident.matches() > 0) throw emu_fatalerror(MAMERR_IDENT_PARTIAL, "Out of %d files, %d matched, %d did not match.\n",ident.total(),ident.matches(),ident.total()-ident.matches()); else throw emu_fatalerror(MAMERR_IDENT_NONE, "No roms matched.\n"); } //------------------------------------------------- // execute_commands - execute various frontend // commands //------------------------------------------------- void cli_frontend::execute_commands(const char *exename) { // help? if (strcmp(m_options.command(), CLICOMMAND_HELP) == 0) { display_help(); return; } // showusage? if (strcmp(m_options.command(), CLICOMMAND_SHOWUSAGE) == 0) { osd_printf_info("Usage: %s [machine] [media] [software] [options]",exename); osd_printf_info("\n\nOptions:\n%s", m_options.output_help().c_str()); return; } // validate? if (strcmp(m_options.command(), CLICOMMAND_VALIDATE) == 0) { validity_checker valid(m_options); const char *sysname = m_options.system_name(); bool result = valid.check_all_matching((sysname[0] == 0) ? "*" : sysname); if (!result) throw emu_fatalerror(MAMERR_FAILED_VALIDITY, "Validity check failed (%d errors, %d warnings in total)\n", valid.errors(), valid.warnings()); return; } // other commands need the INIs parsed std::string option_errors; m_options.parse_standard_inis(option_errors); if (!option_errors.empty()) osd_printf_error("%s\n", option_errors.c_str()); // createconfig? if (strcmp(m_options.command(), CLICOMMAND_CREATECONFIG) == 0) { // attempt to open the output file emu_file file(OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS); if (file.open(emulator_info::get_configname(), ".ini") != osd_file::error::NONE) throw emu_fatalerror("Unable to create file %s.ini\n",emulator_info::get_configname()); // generate the updated INI file.puts(m_options.output_ini().c_str()); ui_options ui_opts; emu_file file_ui(OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS); if (file_ui.open("ui.ini") != osd_file::error::NONE) throw emu_fatalerror("Unable to create file ui.ini\n"); // generate the updated INI file_ui.puts(ui_opts.output_ini().c_str()); return; } // showconfig? if (strcmp(m_options.command(), CLICOMMAND_SHOWCONFIG) == 0) { // print the INI text printf("%s\n", m_options.output_ini().c_str()); return; } // all other commands call out to one of these helpers static const struct { const char *option; void (cli_frontend::*function)(const char *gamename); } info_commands[] = { { CLICOMMAND_LISTXML, &cli_frontend::listxml }, { CLICOMMAND_LISTFULL, &cli_frontend::listfull }, { CLICOMMAND_LISTSOURCE, &cli_frontend::listsource }, { CLICOMMAND_LISTCLONES, &cli_frontend::listclones }, { CLICOMMAND_LISTBROTHERS, &cli_frontend::listbrothers }, { CLICOMMAND_LISTCRC, &cli_frontend::listcrc }, { CLICOMMAND_LISTDEVICES, &cli_frontend::listdevices }, { CLICOMMAND_LISTSLOTS, &cli_frontend::listslots }, { CLICOMMAND_LISTROMS, &cli_frontend::listroms }, { CLICOMMAND_LISTSAMPLES, &cli_frontend::listsamples }, { CLICOMMAND_VERIFYROMS, &cli_frontend::verifyroms }, { CLICOMMAND_VERIFYSAMPLES, &cli_frontend::verifysamples }, { CLICOMMAND_LISTMEDIA, &cli_frontend::listmedia }, { CLICOMMAND_LISTSOFTWARE, &cli_frontend::listsoftware }, { CLICOMMAND_VERIFYSOFTWARE,&cli_frontend::verifysoftware }, { CLICOMMAND_ROMIDENT, &cli_frontend::romident }, { CLICOMMAND_GETSOFTLIST, &cli_frontend::getsoftlist }, { CLICOMMAND_VERIFYSOFTLIST,&cli_frontend::verifysoftlist }, }; // find the command for (auto & info_command : info_commands) if (strcmp(m_options.command(), info_command.option) == 0) { // parse any relevant INI files before proceeding const char *sysname = m_options.system_name(); (this->*info_command.function)((sysname[0] == 0) ? "*" : sysname); return; } if (!m_osd.execute_command(m_options.command())) // if we get here, we don't know what has been requested throw emu_fatalerror(MAMERR_INVALID_CONFIG, "Unknown command '%s' specified", m_options.command()); } //------------------------------------------------- // display_help - display help to standard // output //------------------------------------------------- void cli_frontend::display_help() { osd_printf_info("%s v%s\n%s\n\n", emulator_info::get_appname(),build_version,emulator_info::get_copyright_info()); osd_printf_info("This software reproduces, more or less faithfully, the behaviour of a wide range\n" "of machines. But hardware is useless without software, so images of the ROMs and\n" "other media which run on that hardware are also required.\n\n"); osd_printf_info("Usage: %s [machine] [media] [software] [options]",emulator_info::get_appname()); osd_printf_info("\n\n" " %s -showusage for a brief list of options\n" " %s -showconfig for a list of configuration options\n" " %s -listmedia for a full list of supported media\n" " %s -createconfig to create a %s.ini\n\n" "For usage instructions, please consult the files config.txt and windows.txt.\n",emulator_info::get_appname(), emulator_info::get_appname(),emulator_info::get_appname(),emulator_info::get_appname(),emulator_info::get_configname()); } //------------------------------------------------- // display_suggestions - display 10 possible // matches for a given invalid gamename //------------------------------------------------- void cli_frontend::display_suggestions(const char *gamename) { } //************************************************************************** // MEDIA IDENTIFIER //************************************************************************** //------------------------------------------------- // media_identifier - constructor //------------------------------------------------- media_identifier::media_identifier(cli_options &options) : m_drivlist(options), m_total(0), m_matches(0), m_nonroms(0) { } //------------------------------------------------- // identify - identify a directory, ZIP file, // or raw file //------------------------------------------------- void media_identifier::identify(const char *filename) { // first try to open as a directory osd_directory *directory = osd_opendir(filename); if (directory != nullptr) { // iterate over all files in the directory for (const osd_directory_entry *entry = osd_readdir(directory); entry != nullptr; entry = osd_readdir(directory)) if (entry->type == ENTTYPE_FILE) { std::string curfile = std::string(filename).append(PATH_SEPARATOR).append(entry->name); identify(curfile.c_str()); } // close the directory and be done osd_closedir(directory); } // if that failed, and the filename ends with .zip, identify as a ZIP file if (core_filename_ends_with(filename, ".7z") || core_filename_ends_with(filename, ".zip")) { // first attempt to examine it as a valid _7Z file util::archive_file::ptr archive; util::archive_file::error err; if (core_filename_ends_with(filename, ".7z")) err = util::archive_file::open_7z(filename, archive); else err = util::archive_file::open_zip(filename, archive); if ((err == util::archive_file::error::NONE) && archive) { std::vector data; // loop over entries in the .7z, skipping empty files and directories for (int i = archive->first_file(); i >= 0; i = archive->next_file()) { const std::uint64_t length(archive->current_uncompressed_length()); if (!archive->current_is_directory() && (length != 0) && (std::uint32_t(length) == length)) { // decompress data into RAM and identify it try { data.resize(std::size_t(length)); err = archive->decompress(&data[0], std::uint32_t(length)); if (err == util::archive_file::error::NONE) identify_data(archive->current_name().c_str(), &data[0], length); } catch (...) { // resizing the buffer could cause a bad_alloc if archive contains large files } data.clear(); } } } // clear out any cached files archive.reset(); util::archive_file::cache_clear(); } // otherwise, identify as a raw file else identify_file(filename); } //------------------------------------------------- // identify_file - identify a file //------------------------------------------------- void media_identifier::identify_file(const char *name) { // CHD files need to be parsed and their hashes extracted from the header if (core_filename_ends_with(name, ".chd")) { // output the name osd_printf_info("%-20s", core_filename_extract_base(name).c_str()); m_total++; // attempt to open as a CHD; fail if not chd_file chd; chd_error err = chd.open(name); if (err != CHDERR_NONE) { osd_printf_info("NOT A CHD\n"); m_nonroms++; return; } // error on writable CHDs if (!chd.compressed()) { osd_printf_info("is a writeable CHD\n"); return; } // otherwise, get the hash collection for this CHD hash_collection hashes; if (chd.sha1() != sha1_t::null) hashes.add_sha1(chd.sha1()); // determine whether this file exists int found = find_by_hash(hashes, chd.logical_bytes()); if (found == 0) osd_printf_info("NO MATCH\n"); else m_matches++; } // all other files have their hashes computed directly else { // load the file and process if it opens and has a valid length UINT32 length; void *data; const osd_file::error filerr = util::core_file::load(name, &data, length); if (filerr == osd_file::error::NONE && length > 0) { identify_data(name, reinterpret_cast(data), length); osd_free(data); } } } //------------------------------------------------- // identify_data - identify a buffer full of // data; if it comes from a .JED file, parse the // fusemap into raw data first //------------------------------------------------- void media_identifier::identify_data(const char *name, const UINT8 *data, int length) { // if this is a '.jed' file, process it into raw bits first dynamic_buffer tempjed; jed_data jed; if (core_filename_ends_with(name, ".jed") && jed_parse(data, length, &jed) == JEDERR_NONE) { // now determine the new data length and allocate temporary memory for it length = jedbin_output(&jed, nullptr, 0); tempjed.resize(length); jedbin_output(&jed, &tempjed[0], length); data = &tempjed[0]; } // compute the hash of the data hash_collection hashes; hashes.compute(data, length, hash_collection::HASH_TYPES_CRC_SHA1); // output the name m_total++; osd_printf_info("%-20s", core_filename_extract_base(name).c_str()); // see if we can find a match in the ROMs int found = find_by_hash(hashes, length); // if we didn't find it, try to guess what it might be if (found == 0) osd_printf_info("NO MATCH\n"); // if we did find it, count it as a match else m_matches++; } //------------------------------------------------- // find_by_hash - scan for a file in the list // of drivers by hash //------------------------------------------------- int media_identifier::find_by_hash(const hash_collection &hashes, int length) { int found = 0; std::unordered_set listnames; std::unordered_set shortnames; // iterate over drivers m_drivlist.reset(); while (m_drivlist.next()) { // iterate over devices, regions and files within the region */ device_iterator deviter(m_drivlist.config().root_device()); for (device_t *device = deviter.first(); device != nullptr; device = deviter.next()) { if (shortnames.insert(device->shortname()).second) { for (const rom_entry *region = rom_first_region(*device); region != nullptr; region = rom_next_region(region)) for (const rom_entry *rom = rom_first_file(region); rom != nullptr; rom = rom_next_file(rom)) { hash_collection romhashes(ROM_GETHASHDATA(rom)); if (!romhashes.flag(hash_collection::FLAG_NO_DUMP) && hashes == romhashes) { bool baddump = romhashes.flag(hash_collection::FLAG_BAD_DUMP); // output information about the match if (found) osd_printf_info(" "); osd_printf_info("= %s%-20s %-10s %s\n", baddump ? "(BAD) " : "", ROM_GETNAME(rom), m_drivlist.driver().name, m_drivlist.driver().description); found++; } } } } // next iterate over softlists software_list_device_iterator iter(m_drivlist.config().root_device()); for (software_list_device *swlistdev = iter.first(); swlistdev != nullptr; swlistdev = iter.next()) { if (listnames.insert(swlistdev->list_name()).second) { for (software_info *swinfo = swlistdev->first_software_info(); swinfo != nullptr; swinfo = swinfo->next()) for (software_part *part = swinfo->first_part(); part != nullptr; part = part->next()) for (const rom_entry *region = part->romdata(); region != nullptr; region = rom_next_region(region)) for (const rom_entry *rom = rom_first_file(region); rom != nullptr; rom = rom_next_file(rom)) { hash_collection romhashes(ROM_GETHASHDATA(rom)); if (hashes == romhashes) { bool baddump = romhashes.flag(hash_collection::FLAG_BAD_DUMP); // output information about the match if (found) osd_printf_info(" "); osd_printf_info("= %s%-20s %s:%s %s\n", baddump ? "(BAD) " : "", ROM_GETNAME(rom), swlistdev->list_name(), swinfo->shortname(), swinfo->longname()); found++; } } } } } return found; }