/*************************************************************************** clifront.c Command-line interface frontend for MAME. **************************************************************************** Copyright Aaron Giles All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name 'MAME' nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY AARON GILES ''AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL AARON GILES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***************************************************************************/ #include "emu.h" #include "emuopts.h" #include "hash.h" #include "jedparse.h" #include "audit.h" #include "info.h" #include "unzip.h" #include "validity.h" #include "sound/samples.h" #include "clifront.h" #include "xmlfile.h" #include #include //************************************************************************** // COMMAND-LINE OPTIONS //************************************************************************** const options_entry cli_options::s_option_entries[] = { /* core commands */ { NULL, NULL, OPTION_HEADER, "CORE COMMANDS" }, { CLICOMMAND_HELP ";h;?", "0", OPTION_COMMAND, "show help message" }, { CLICOMMAND_VALIDATE ";valid", "0", OPTION_COMMAND, "perform driver validation on all game drivers" }, /* configuration commands */ { NULL, NULL, OPTION_HEADER, "CONFIGURATION COMMANDS" }, { CLICOMMAND_CREATECONFIG ";cc", "0", OPTION_COMMAND, "create the default configuration file" }, { CLICOMMAND_SHOWCONFIG ";sc", "0", OPTION_COMMAND, "display running parameters" }, { CLICOMMAND_SHOWUSAGE ";su", "0", OPTION_COMMAND, "show this help" }, /* frontend commands */ { NULL, NULL, OPTION_HEADER, "FRONTEND COMMANDS" }, { CLICOMMAND_LISTXML ";lx", "0", OPTION_COMMAND, "all available info on driver in XML format" }, { CLICOMMAND_LISTFULL ";ll", "0", OPTION_COMMAND, "short name, full name" }, { CLICOMMAND_LISTSOURCE ";ls", "0", OPTION_COMMAND, "driver sourcefile" }, { CLICOMMAND_LISTCLONES ";lc", "0", OPTION_COMMAND, "show clones" }, { CLICOMMAND_LISTBROTHERS ";lb", "0", OPTION_COMMAND, "show \"brothers\", or other drivers from same sourcefile" }, { CLICOMMAND_LISTCRC, "0", OPTION_COMMAND, "CRC-32s" }, { CLICOMMAND_LISTROMS, "0", OPTION_COMMAND, "list required roms for a driver" }, { CLICOMMAND_LISTSAMPLES, "0", OPTION_COMMAND, "list optional samples for a driver" }, { CLICOMMAND_VERIFYROMS, "0", OPTION_COMMAND, "report romsets that have problems" }, { CLICOMMAND_VERIFYSAMPLES, "0", OPTION_COMMAND, "report samplesets that have problems" }, { CLICOMMAND_ROMIDENT, "0", OPTION_COMMAND, "compare files with known MAME roms" }, { CLICOMMAND_LISTDEVICES ";ld", "0", OPTION_COMMAND, "list available devices" }, { CLICOMMAND_LISTSLOTS ";lslot", "0", OPTION_COMMAND, "list available slots and slot devices" }, { CLICOMMAND_LISTMEDIA ";lm", "0", OPTION_COMMAND, "list available media for the system" }, { CLICOMMAND_LISTSOFTWARE ";lsoft", "0", OPTION_COMMAND, "list known software for the system" }, { NULL } }; //************************************************************************** // CLI OPTIONS //************************************************************************** //------------------------------------------------- // cli_options - constructor //------------------------------------------------- cli_options::cli_options() { add_entries(s_option_entries); } //************************************************************************** // 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) { // begin tracking memory track_memory(true); } //------------------------------------------------- // ~cli_frontend - destructor //------------------------------------------------- cli_frontend::~cli_frontend() { // report any unfreed memory on clean exits track_memory(false); if (m_result == MAMERR_NONE) dump_unfreed_mem(); } //------------------------------------------------- // 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; try { // parse the command line, adding any system-specific options astring option_errors; 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 (strlen(m_options.command()) == 0 && m_options.system() == NULL && strlen(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", option_errors.trimspace().cstr()); } if (option_errors) printf("Error in command line:\n%s\n", option_errors.trimspace().cstr()); // determine the base name of the EXE astring exename; core_filename_extract_base(&exename, argv[0], TRUE); // if we have a command, execute that if (strlen(m_options.command()) != 0) execute_commands(exename); // otherwise, check for a valid system else { // if we can't find it, give an appropriate error const game_driver *system = m_options.system(); if (system == NULL && strlen(m_options.system_name()) > 0) throw emu_fatalerror(MAMERR_NO_SUCH_GAME, "Unknown system '%s'", m_options.system_name()); if (strlen(m_options.software_name()) > 0) { machine_config config(*system, m_options); if (!config.devicelist().first(SOFTWARE_LIST)) throw emu_fatalerror(MAMERR_FATALERROR, "Error: unknown option: %s\n", m_options.software_name()); bool found = FALSE; for (device_t *swlists = config.devicelist().first(SOFTWARE_LIST); swlists != NULL; swlists = swlists->typenext()) { software_list_config *swlist = (software_list_config *)downcast(swlists)->inline_config(); software_list *list = software_list_open(m_options, swlist->list_name, FALSE, NULL); if (list) { software_info *swinfo = software_list_find(list, m_options.software_name(), NULL); if (swinfo!=NULL) { for (software_part *swpart = software_find_part(swinfo, NULL, NULL); swpart != NULL; swpart = software_part_next(swpart)) { const char *mount = software_part_get_feature(swpart, "automount"); if (is_software_compatible(swpart, swlist)) { if (mount==NULL || strcmp(mount,"no")!=0) { // loop trough all parts // search for a device with the right interface const device_image_interface *image = NULL; for (bool gotone = config.devicelist().first(image); gotone; gotone = image->next(image)) { const char *interface = image->image_interface(); if (interface != NULL) { if (!strcmp(interface, swpart->interface_)) { const char *option = m_options.value(image->brief_instance_name()); // mount only if not already mounted if (strlen(option)==0) { astring val; val.printf("%s:%s:%s",swlist->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.cstr()); } break; } } } } found = TRUE; } } software_list_close(list); break; } } software_list_close(list); if (found) break; } if (!found) { software_display_matches(config.devicelist(),m_options, NULL,m_options.software_name()); throw emu_fatalerror(MAMERR_FATALERROR, ""); } } // otherwise just run the game m_result = mame_execute(m_options, m_osd); } } // handle exceptions of various types catch (emu_fatalerror &fatal) { astring string(fatal.string()); fprintf(stderr, "%s\n", string.trimspace().cstr()); 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 && strlen(m_options.system_name()) > 0 && strchr(m_options.system_name(), '*') == NULL && m_options.system() == NULL) { // get the top 10 approximate matches driver_enumerator drivlist(m_options); int matches[10]; drivlist.find_approximate_matches(m_options.system_name(), ARRAY_LENGTH(matches), matches); // print them out fprintf(stderr, "\n\"%s\" approximately matches the following\n" "supported %s (best match first):\n\n", m_options.system_name(),emulator_info::get_gamesnoun()); for (int matchnum = 0; matchnum < ARRAY_LENGTH(matches); matchnum++) if (matches[matchnum] != -1) fprintf(stderr, "%-18s%s\n", drivlist.driver(matches[matchnum]).name, drivlist.driver(matches[matchnum]).description); } } catch (emu_exception &) { fprintf(stderr, "Caught unhandled emulator exception\n"); m_result = MAMERR_FATALERROR; } catch (std::bad_alloc &) { fprintf(stderr, "Out of memory!\n"); m_result = MAMERR_FATALERROR; } // handle any other exceptions catch (...) { fprintf(stderr, "Caught unhandled exception\n"); m_result = MAMERR_FATALERROR; } 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 mame_printf_info("Name: Description:\n"); // iterate through drivers and output the info while (drivlist.next()) if ((drivlist.driver().flags & GAME_NO_STANDALONE) == 0) mame_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 astring filename; while (drivlist.next()) mame_printf_info("%-16s %s\n", drivlist.driver().name, core_filename_extract_base(&filename, drivlist.driver().source_file, FALSE)->cstr()); } //------------------------------------------------- // 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 & GAME_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 mame_printf_info("Found %d matches for '%s' but none were clones\n", drivlist.count(), gamename); return; } // print the header mame_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 & GAME_IS_BIOS_ROOT) == 0) mame_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 mame_printf_info("Source file: Name: Parent:\n"); // output the entries found drivlist.reset(); astring filename; while (drivlist.next()) { int clone_of = drivlist.clone(); mame_printf_info("%-16s %-16s %-16s\n", core_filename_extract_base(&filename, drivlist.driver().source_file, FALSE)->cstr(), 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()) for (const rom_source *source = rom_first_source(drivlist.config()); source != NULL; source = rom_next_source(*source)) { bool isdriver = (source == rom_first_source(drivlist.config())); for (const rom_entry *region = rom_first_region(*source); 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)) mame_printf_info("%08x %-16s \t %-8s \t %s\n", crc, ROM_GETNAME(rom), source->shortname(), isdriver ? drivlist.driver().description : source->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 astring tempstr; bool first = true; while (drivlist.next()) { // print a header if (!first) mame_printf_info("\n"); first = false; mame_printf_info("ROMs required for driver \"%s\".\n" "Name Size Checksum\n", drivlist.driver().name); // iterate through roms for (const rom_source *source = rom_first_source(drivlist.config()); source != NULL; source = rom_next_source(*source)) for (const rom_entry *region = rom_first_region(*source); 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); mame_printf_info("%-20s ", name); // output the length next if (length >= 0) mame_printf_info("%7d", length); else mame_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)) mame_printf_info(" BAD"); mame_printf_info(" %s", hashes.macro_string(tempstr)); } else mame_printf_info(" NO GOOD DUMP KNOWN"); // end with a CR mame_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 const device_t *device; for (device = drivlist.config().first_device(); device != NULL; device = device->next()) if (device->type() == SAMPLES) break; if (device == NULL) continue; // print a header if (!first) mame_printf_info("\n"); first = false; mame_printf_info("Samples required for driver \"%s\".\n", drivlist.driver().name); // iterate over samples devices for ( ; device != NULL; device = device->next()) if (device->type() == SAMPLES) { // if the list is legit, walk it and print the sample info const char *const *samplenames = reinterpret_cast(device->static_config())->samplenames; if (samplenames != NULL) for (int sampnum = 0; samplenames[sampnum] != NULL; sampnum++) if (samplenames[sampnum][0] != '*') mame_printf_info("%s\n", samplenames[sampnum]); } } } //------------------------------------------------- // listdevices - output the list of devices // referenced by a given game or set of games //------------------------------------------------- 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); // iterate through devices for (const device_t *device = drivlist.config().first_device(); device != NULL; device = device->next()) { printf(" %s ('%s')", device->name(), device->tag()); 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 const device_slot_interface *slot = NULL; bool first = true; for (bool gotone = drivlist.config().devicelist().first(slot); gotone; gotone = slot->next(slot)) { // output the line, up to the list of extensions printf("%-13s%-10s ", first ? drivlist.driver().name : "", slot->device().tag()); // get the options and print them const slot_interface* intf = slot->get_slot_interfaces(); for (int i = 0; intf[i].name != NULL; i++) { device_t *dev = (*intf[i].devtype)(drivlist.config(), "dummy", drivlist.config().devicelist().first(), 0); dev->config_complete(); if (i==0) { printf("%-15s %s\n", intf[i].name,dev->name()); } else { printf("%-23s %-15s %s\n", "",intf[i].name,dev->name()); } global_free(dev); } // 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 const device_image_interface *imagedev = NULL; bool first = true; for (bool gotone = drivlist.config().devicelist().first(imagedev); gotone; gotone = imagedev->next(imagedev)) { // extract the shortname with parentheses astring paren_shortname; paren_shortname.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.cstr()); // get the extensions and print them astring extensions(imagedev->file_extensions()); for (int start = 0, end = extensions.chr(0, ','); ; start = end + 1, end = extensions.chr(start, ',')) { astring curext(extensions, start, (end == -1) ? extensions.len() - start : end - start); printf(".%-5s", curext.cstr()); 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 //------------------------------------------------- extern int m_device_count; extern const device_type *s_devices_sorted[]; 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 if (summary != media_auditor::NONE_NEEDED) { // output the summary of the audit astring summary_string; auditor.summarize(drivlist.driver().name,&summary_string); mame_printf_info("%s", summary_string.cstr()); // output the name of the driver and its clone mame_printf_info("romset %s ", drivlist.driver().name); int clone_of = drivlist.clone(); if (clone_of != -1) mame_printf_info("[%s] ", drivlist.driver(clone_of).name); // switch off of the result switch (summary) { case media_auditor::INCORRECT: mame_printf_info("is bad\n"); incorrect++; break; case media_auditor::CORRECT: mame_printf_info("is good\n"); correct++; break; case media_auditor::BEST_AVAILABLE: mame_printf_info("is best available\n"); correct++; break; default: break; } } } driver_enumerator dummy_drivlist(m_options); dummy_drivlist.next(); machine_config &config = dummy_drivlist.config(); device_t *owner = config.devicelist().first(); // check if all are listed, note that empty one is included for (int i = 0; i < m_device_count; i++) { device_type type = *s_devices_sorted[i]; device_t *dev = (*type)(config, "dummy", owner, 0); dev->config_complete(); if (mame_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 { // output the summary of the audit astring summary_string; auditor.summarize(dev->shortname(),&summary_string); mame_printf_info("%s", summary_string.cstr()); // display information about what we discovered mame_printf_info("romset %s ", dev->shortname()); // switch off of the result switch (summary) { case media_auditor::INCORRECT: mame_printf_info("is bad\n"); incorrect++; break; case media_auditor::CORRECT: mame_printf_info("is good\n"); correct++; break; case media_auditor::BEST_AVAILABLE: mame_printf_info("is best available\n"); correct++; break; default: break; } } } global_free(dev); } // clear out any cached files zip_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 == 1 && 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); mame_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 astring summary_string; auditor.summarize(drivlist.driver().name,&summary_string); mame_printf_info("%s", summary_string.cstr()); // output the name of the driver and its clone mame_printf_info("sampleset %s ", drivlist.driver().name); int clone_of = drivlist.clone(); if (clone_of != -1) mame_printf_info("[%s] ", drivlist.driver(clone_of).name); // switch off of the result switch (summary) { case media_auditor::INCORRECT: mame_printf_info("is bad\n"); incorrect++; break; case media_auditor::CORRECT: mame_printf_info("is good\n"); correct++; break; case media_auditor::BEST_AVAILABLE: mame_printf_info("is best available\n"); correct++; break; default: break; } } } // clear out any cached files zip_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 == 1 && 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); mame_printf_info("%d samplesets found, %d were OK.\n", correct, correct); } } /*------------------------------------------------- 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; // 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); // first determine the maximum number of lists we might encounter int list_count = 0; while (drivlist.next()) for (const device_t *dev = drivlist.config().devicelist().first(SOFTWARE_LIST); dev != NULL; dev = dev->typenext()) { software_list_config *swlist = (software_list_config *)downcast(dev)->inline_config(); if (swlist->list_type == SOFTWARE_LIST_ORIGINAL_SYSTEM) list_count++; } // allocate a list astring *lists = global_alloc_array(astring, list_count); if (list_count) { fprintf( out, "\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" // we still do not store the info strings internally, so there is no output here // TODO: add parsing info in softlist.c and then add output here! "\t\t\t\n" "\t\t\t\t\n" "\t\t\t\t\n" // shared features get stored in the part->feature below and are output there // this means that we don't output any and that -lsoft output will // be different from the list in hash/ when the list uses sharedfeat. But this // is by design: sharedfeat is only available to simplify the life to list creators, // to e.g. avoid manually adding the same feature to each disk of a 9 floppies game! "\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" // we still do not store the dipswitch values internally, so there is no output here // TODO: add parsing dipsw in softlist.c and then add output here! "\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" ); } drivlist.reset(); list_count = 0; while (drivlist.next()) for (const device_t *dev = drivlist.config().devicelist().first(SOFTWARE_LIST); dev != NULL; dev = dev->typenext()) { software_list_config *swlist = (software_list_config *)downcast(dev)->inline_config(); if (swlist->list_type == SOFTWARE_LIST_ORIGINAL_SYSTEM) { software_list *list = software_list_open(m_options, swlist->list_name, FALSE, NULL); if ( list ) { /* Verify if we have encountered this list before */ bool seen_before = false; for (int seen_index = 0; seen_index < list_count && !seen_before; seen_index++) if (lists[seen_index] == swlist->list_name) seen_before = true; if (!seen_before) { lists[list_count++] = swlist->list_name; software_list_parse( list, NULL, NULL ); fprintf(out, "\t\n", swlist->list_name, xml_normalize_string(software_list_get_description(list)) ); for ( software_info *swinfo = software_list_find( list, "*", NULL ); swinfo != NULL; swinfo = software_list_find( list, "*", swinfo ) ) { fprintf( out, "\t\tshortname ); if ( swinfo->parentname != NULL ) 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 ( software_part *part = software_find_part( swinfo, NULL, NULL ); part != NULL; part = software_part_next( part ) ) { fprintf( out, "\t\t\tname ); if ( part->interface_ ) fprintf( out, " interface=\"%s\"", part->interface_ ); fprintf( out, ">\n"); if ( part->featurelist ) { feature_list *flist = part->featurelist; while( flist ) { fprintf( out, "\t\t\t\t\n", flist->name, flist->value ); flist = flist->next; } } /* 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\tnext()) fprintf(out, " %s=\"%s\"", hash->name(), hash->string(tempstr)); } if (!is_disk) fprintf( out, " offset=\"0x%x\"", ROM_GETOFFSET(rom) ); if ( hashes.flag(hash_collection::FLAG_BAD_DUMP) ) fprintf( out, " status=\"baddump\"" ); if ( hashes.flag(hash_collection::FLAG_NO_DUMP) ) fprintf( out, " status=\"nodump\"" ); if (is_disk) fprintf( out, " writeable=\"%s\"", (ROM_GETFLAGS(rom) & DISK_READONLYMASK) ? "no" : "yes"); if ((ROM_GETFLAGS(rom) & ROM_SKIPMASK) == ROM_SKIP(1)) fprintf( out, " loadflag=\"load16_byte\"" ); if ((ROM_GETFLAGS(rom) & ROM_SKIPMASK) == ROM_SKIP(3)) fprintf( out, " loadflag=\"load32_byte\"" ); if (((ROM_GETFLAGS(rom) & ROM_SKIPMASK) == ROM_SKIP(2)) && ((ROM_GETFLAGS(rom) & ROM_GROUPMASK) == ROM_GROUPWORD)) { if (!(ROM_GETFLAGS(rom) & ROM_REVERSEMASK)) fprintf( out, " loadflag=\"load32_word\"" ); else fprintf( out, " loadflag=\"load32_word_swap\"" ); } if (((ROM_GETFLAGS(rom) & ROM_SKIPMASK) == ROM_SKIP(6)) && ((ROM_GETFLAGS(rom) & ROM_GROUPMASK) == ROM_GROUPWORD)) { if (!(ROM_GETFLAGS(rom) & ROM_REVERSEMASK)) fprintf( out, " loadflag=\"load64_word\"" ); else fprintf( out, " loadflag=\"load64_word_swap\"" ); } if (((ROM_GETFLAGS(rom) & ROM_SKIPMASK) == ROM_NOSKIP) && ((ROM_GETFLAGS(rom) & ROM_GROUPMASK) == ROM_GROUPWORD)) { if (!(ROM_GETFLAGS(rom) & ROM_REVERSEMASK)) fprintf( out, " loadflag=\"load32_dword\"" ); else fprintf( out, " loadflag=\"load16_word_swap\"" ); } fprintf( out, "/>\n" ); } else if ( ROMENTRY_ISRELOAD(rom) ) { fprintf( out, "\t\t\t\t\t\n", ROM_GETLENGTH(rom), ROM_GETOFFSET(rom) ); } else if ( ROMENTRY_ISCONTINUE(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" ); } software_list_close( list ); } } } if (list_count > 0) fprintf( out, "\n" ); else fprintf( out, "No software lists found for this system\n" ); global_free( lists ); } //------------------------------------------------- // 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 mame_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, ""); else if (ident.matches() > 0) throw emu_fatalerror(MAMERR_IDENT_PARTIAL, ""); else throw emu_fatalerror(MAMERR_IDENT_NONE, ""); } //------------------------------------------------- // 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) { astring helpstring; emulator_info::printf_usage(exename, emulator_info::get_gamenoun()); mame_printf_info("\n\nOptions:\n%s", m_options.output_help(helpstring)); return; } // validate? if (strcmp(m_options.command(), CLICOMMAND_VALIDATE) == 0) { validate_drivers(m_options); validate_softlists(m_options); return; } // other commands need the INIs parsed astring option_errors; m_options.parse_standard_inis(option_errors); if (option_errors) printf("%s\n", option_errors.cstr()); // 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") != FILERR_NONE) throw emu_fatalerror("Unable to create file %s.ini\n",emulator_info::get_configname()); // generate the updated INI astring initext; file.puts(m_options.output_ini(initext)); return; } // showconfig? if (strcmp(m_options.command(), CLICOMMAND_SHOWCONFIG) == 0) { // print the INI text astring initext; printf("%s\n", m_options.output_ini(initext)); 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_ROMIDENT, &cli_frontend::romident } }; // find the command for (int cmdindex = 0; cmdindex < ARRAY_LENGTH(info_commands); cmdindex++) if (strcmp(m_options.command(), info_commands[cmdindex].option) == 0) { // parse any relevant INI files before proceeding const char *sysname = m_options.system_name(); (this->*info_commands[cmdindex].function)((sysname[0] == 0) ? "*" : sysname); return; } // 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() { mame_printf_info("%s v%s - %s\n%s\n\n", emulator_info::get_applongname(),build_version,emulator_info::get_fulllongname(),emulator_info::get_copyright_info()); mame_printf_info("%s\n", emulator_info::get_disclaimer()); emulator_info::printf_usage(emulator_info::get_appname(),emulator_info::get_gamenoun()); mame_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 != NULL) { // iterate over all files in the directory for (const osd_directory_entry *entry = osd_readdir(directory); entry != NULL; entry = osd_readdir(directory)) if (entry->type == ENTTYPE_FILE) { astring curfile(filename, PATH_SEPARATOR, entry->name); identify_file(curfile); } // close the directory and be done osd_closedir(directory); } // if that failed, and the filename ends with .zip, identify as a ZIP file else if (core_filename_ends_with(filename, ".zip")) { // first attempt to examine it as a valid ZIP file zip_file *zip = NULL; zip_error ziperr = zip_file_open(filename, &zip); if (ziperr == ZIPERR_NONE && zip != NULL) { // loop over entries in the ZIP, skipping empty files and directories for (const zip_file_header *entry = zip_file_first_file(zip); entry != NULL; entry = zip_file_next_file(zip)) if (entry->uncompressed_length != 0) { UINT8 *data = global_alloc_array(UINT8, entry->uncompressed_length); if (data != NULL) { // decompress data into RAM and identify it ziperr = zip_file_decompress(zip, data, entry->uncompressed_length); if (ziperr == ZIPERR_NONE) identify_data(entry->filename, data, entry->uncompressed_length); global_free(data); } } // close up zip_file_close(zip); } // clear out any cached files zip_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 astring basename; mame_printf_info("%-20s", core_filename_extract_base(&basename, name, FALSE)->cstr()); m_total++; // attempt to open as a CHD; fail if not chd_file *chd; chd_error err = chd_open(name, CHD_OPEN_READ, NULL, &chd); if (err != CHDERR_NONE) { mame_printf_info("NOT A CHD\n"); m_nonroms++; return; } // fetch the header and close the file chd_header header = *chd_get_header(chd); chd_close(chd); // error on writable CHDs if (header.flags & CHDFLAGS_IS_WRITEABLE) { mame_printf_info("is a writeable CHD\n"); return; } // otherwise, get the hash collection for this CHD static const UINT8 nullhash[20] = { 0 }; hash_collection hashes; if (memcmp(nullhash, header.sha1, sizeof(header.sha1)) != 0) hashes.add_from_buffer(hash_collection::HASH_SHA1, header.sha1, sizeof(header.sha1)); // determine whether this file exists int found = find_by_hash(hashes, header.logicalbytes); if (found == 0) mame_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; file_error filerr = core_fload(name, &data, &length); if (filerr == FILERR_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 UINT8 *tempjed = NULL; 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, NULL, 0); tempjed = global_alloc_array(UINT8, length); jedbin_output(&jed, tempjed, length); data = tempjed; } // compute the hash of the data hash_collection hashes; hashes.compute(data, length, hash_collection::HASH_TYPES_CRC_SHA1); // output the name m_total++; astring basename; mame_printf_info("%-20s", core_filename_extract_base(&basename, name, FALSE)->cstr()); // 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) { // if not a power of 2, assume it is a non-ROM file if ((length & (length - 1)) != 0) { mame_printf_info("NOT A ROM\n"); m_nonroms++; } // otherwise, it's just not a match else mame_printf_info("NO MATCH\n"); } // if we did find it, count it as a match else m_matches++; // free any temporary JED data global_free(tempjed); } //------------------------------------------------- // 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; // iterate over drivers m_drivlist.reset(); while (m_drivlist.next()) { // iterate over sources, regions and files within the region */ for (const rom_source *source = rom_first_source(m_drivlist.config()); source != NULL; source = rom_next_source(*source)) for (const rom_entry *region = rom_first_region(*source); region != NULL; region = rom_next_region(region)) for (const rom_entry *rom = rom_first_file(region); rom != NULL; 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) mame_printf_info(" "); mame_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 for (const device_t *dev = m_drivlist.config().devicelist().first(SOFTWARE_LIST); dev != NULL; dev = dev->typenext()) { software_list_config *swlist = (software_list_config *)downcast(dev)->inline_config(); software_list *list = software_list_open(m_drivlist.options(), swlist->list_name, FALSE, NULL); for (software_info *swinfo = software_list_find(list, "*", NULL); swinfo != NULL; swinfo = software_list_find(list, "*", swinfo)) for (software_part *part = software_find_part(swinfo, NULL, NULL); part != NULL; part = software_part_next(part)) for (const rom_entry *region = part->romdata; region != NULL; region = rom_next_region(region)) for (const rom_entry *rom = rom_first_file(region); rom != NULL; 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) mame_printf_info(" "); mame_printf_info("= %s%-20s %s:%s %s\n", baddump ? "(BAD) " : "", ROM_GETNAME(rom), swlist->list_name, swinfo->shortname, swinfo->longname); found++; } } software_list_close(list); } } return found; }