diff options
Diffstat (limited to 'src/frontend/mame/clifront.cpp')
-rw-r--r-- | src/frontend/mame/clifront.cpp | 2051 |
1 files changed, 2051 insertions, 0 deletions
diff --git a/src/frontend/mame/clifront.cpp b/src/frontend/mame/clifront.cpp new file mode 100644 index 00000000000..9d81306aa60 --- /dev/null +++ b/src/frontend/mame/clifront.cpp @@ -0,0 +1,2051 @@ +// license:BSD-3-Clause +// copyright-holders:Aaron Giles +/*************************************************************************** + + clifront.c + + Command-line interface frontend for MAME. + +***************************************************************************/ + +#include "emu.h" +#include "mame.h" +#include "chd.h" +#include "emuopts.h" +#include "mameopts.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 "drivenum.h" + +#include "osdepend.h" +#include "softlist.h" + +#include "ui/moptions.h" + +#include <new> +#include <ctype.h> + + +//************************************************************************** +// CONSTANTS +//************************************************************************** + +// core commands +#define CLICOMMAND_HELP "help" +#define CLICOMMAND_VALIDATE "validate" + +// configuration commands +#define CLICOMMAND_CREATECONFIG "createconfig" +#define CLICOMMAND_SHOWCONFIG "showconfig" +#define CLICOMMAND_SHOWUSAGE "showusage" + +// frontend commands +#define CLICOMMAND_LISTXML "listxml" +#define CLICOMMAND_LISTFULL "listfull" +#define CLICOMMAND_LISTSOURCE "listsource" +#define CLICOMMAND_LISTCLONES "listclones" +#define CLICOMMAND_LISTBROTHERS "listbrothers" +#define CLICOMMAND_LISTCRC "listcrc" +#define CLICOMMAND_LISTROMS "listroms" +#define CLICOMMAND_LISTSAMPLES "listsamples" +#define CLICOMMAND_VERIFYROMS "verifyroms" +#define CLICOMMAND_VERIFYSAMPLES "verifysamples" +#define CLICOMMAND_ROMIDENT "romident" +#define CLICOMMAND_LISTDEVICES "listdevices" +#define CLICOMMAND_LISTSLOTS "listslots" +#define CLICOMMAND_LISTMEDIA "listmedia" // needed by MESS +#define CLICOMMAND_LISTSOFTWARE "listsoftware" +#define CLICOMMAND_VERIFYSOFTWARE "verifysoftware" +#define CLICOMMAND_GETSOFTLIST "getsoftlist" +#define CLICOMMAND_VERIFYSOFTLIST "verifysoftlist" + + +//************************************************************************** +// COMMAND-LINE OPTIONS +//************************************************************************** + +static const options_entry cli_option_entries[] = +{ + /* core commands */ + { nullptr, nullptr, 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 */ + { nullptr, nullptr, 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 */ + { nullptr, nullptr, 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 ";lr", "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" }, + { CLICOMMAND_VERIFYSOFTWARE ";vsoft", "0", OPTION_COMMAND, "verify known software for the system" }, + { CLICOMMAND_GETSOFTLIST ";glist", "0", OPTION_COMMAND, "retrieve software list by name" }, + { CLICOMMAND_VERIFYSOFTLIST ";vlist", "0", OPTION_COMMAND, "verify software list by name" }, + { nullptr } +}; + + +// media_identifier class identifies media by hash via a search in +// the driver database +class media_identifier +{ +public: + // construction/destruction + media_identifier(emu_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(emu_options &options, osd_interface &osd) + : m_options(options), + m_osd(osd), + m_result(EMU_ERR_NONE) +{ + m_options.add_entries(cli_option_entries); +} + + +//------------------------------------------------- +// ~cli_frontend - destructor +//------------------------------------------------- + +cli_frontend::~cli_frontend() +{ + // nuke any device options since they will leak memory + mame_options::remove_device_options(m_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 = EMU_ERR_NONE; + mame_machine_manager *manager = mame_machine_manager::instance(m_options, m_osd); + + try + { + // first parse options to be able to get software from it + std::string option_errors; + mame_options::parse_command_line(m_options,argc, argv, option_errors); + + mame_options::parse_standard_inis(m_options,option_errors); + + 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(EMU_ERR_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(EMU_ERR_FATALERROR, "Error: unknown option: %s\n", m_options.software_name()); + + bool found = false; + for (software_list_device &swlistdev : iter) + { + software_info *swinfo = swlistdev.find(m_options.software_name()); + if (swinfo != nullptr) + { + // loop through all parts + for (software_part &swpart : swinfo->parts()) + { + 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 + for (device_image_interface &image : image_interface_iterator(config.root_device())) + { + 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 + mame_options::parse_slot_devices(m_options, 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(EMU_ERR_FATALERROR, nullptr); + } + } + + // parse the command line, adding any system-specific options + if (!mame_options::parse_command_line(m_options,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(EMU_ERR_NO_SUCH_GAME, "Unknown system '%s'", m_options.system_name()); + + // otherwise, error on the options + throw emu_fatalerror(EMU_ERR_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); + mame_options::parse_standard_inis(m_options,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(EMU_ERR_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() : EMU_ERR_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 == EMU_ERR_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 = EMU_ERR_FATALERROR; + } + catch (add_exception &aex) + { + osd_printf_error("Tag '%s' already exists in tagged_list\n", aex.tag()); + m_result = EMU_ERR_FATALERROR; + } + catch (std::exception &ex) + { + osd_printf_error("Caught unhandled %s exception: %s\n", typeid(ex).name(), ex.what()); + m_result = EMU_ERR_FATALERROR; + } + catch (...) + { + osd_printf_error("Caught unhandled exception\n"); + m_result = EMU_ERR_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(EMU_ERR_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(EMU_ERR_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(EMU_ERR_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(EMU_ERR_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(EMU_ERR_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(EMU_ERR_NO_SUCH_GAME, "No matching games found for '%s'", gamename); + + // iterate through matches, and then through ROMs + while (drivlist.next()) + { + for (device_t &device : device_iterator(drivlist.config().root_device())) + 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(EMU_ERR_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 + for (device_t &device : device_iterator(drivlist.config().root_device())) + 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(EMU_ERR_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) + { + 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(EMU_ERR_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 + std::vector<device_t *> device_list; + for (device_t &device : device_iterator(drivlist.config().root_device())) + 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 = "<root>"; + 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(EMU_ERR_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 + bool first = true; + for (const device_slot_interface &slot : slot_interface_iterator(drivlist.config().root_device())) + { + 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.option_list()) + { + 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(EMU_ERR_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 + bool first = true; + for (const device_image_interface &imagedev : image_interface_iterator(drivlist.config().root_device())) + { + if (!imagedev.user_loadable()) + continue; + + // 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<std::string> device_map; + while (dummy_drivlist.next()) + { + machine_config &config = dummy_drivlist.config(); + for (device_t &dev : device_iterator(config.root_device())) + { + 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; + } + } + } + } + } + + for (const device_slot_interface &slot : slot_interface_iterator(config.root_device())) + { + for (const device_slot_option &option : slot.option_list()) + { + std::string temptag("_"); + temptag.append(option.name()); + device_t *dev = const_cast<machine_config &>(config).device_add(&config.root_device(), temptag.c_str(), option.devtype(), 0); + + // notify this device and all its subdevices that they are now configured + for (device_t &device : device_iterator(*dev)) + 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) + for (device_t &device : device_iterator(*dev)) + { + for (device_t &subdev : device_iterator(device)) + { + 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<machine_config &>(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(EMU_ERR_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(EMU_ERR_MISSING_FILES, "romset \"%s\" not found!\n", gamename); + else + throw emu_fatalerror(EMU_ERR_MISSING_FILES, "romset \"%s\" has no roms!\n", gamename); + } + + // otherwise, print a summary + else + { + if (incorrect > 0) + throw emu_fatalerror(EMU_ERR_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(EMU_ERR_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(EMU_ERR_MISSING_FILES, "sampleset \"%s\" not found!\n", gamename); + else + throw emu_fatalerror(EMU_ERR_MISSING_FILES, "sampleset \"%s\" not required!\n", gamename); + } + + // otherwise, print a summary + else + { + if (incorrect > 0) + throw emu_fatalerror(EMU_ERR_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 "<?xml version=\"1.0\"?>\n" \ + "<!DOCTYPE softwarelists [\n" \ + "<!ELEMENT softwarelists (softwarelist*)>\n" \ + "\t<!ELEMENT softwarelist (software+)>\n" \ + "\t\t<!ATTLIST softwarelist name CDATA #REQUIRED>\n" \ + "\t\t<!ATTLIST softwarelist description CDATA #IMPLIED>\n" \ + "\t\t<!ELEMENT software (description, year, publisher, info*, sharedfeat*, part*)>\n" \ + "\t\t\t<!ATTLIST software name CDATA #REQUIRED>\n" \ + "\t\t\t<!ATTLIST software cloneof CDATA #IMPLIED>\n" \ + "\t\t\t<!ATTLIST software supported (yes|partial|no) \"yes\">\n" \ + "\t\t\t<!ELEMENT description (#PCDATA)>\n" \ + "\t\t\t<!ELEMENT year (#PCDATA)>\n" \ + "\t\t\t<!ELEMENT publisher (#PCDATA)>\n" \ + "\t\t\t<!ELEMENT info EMPTY>\n" \ + "\t\t\t\t<!ATTLIST info name CDATA #REQUIRED>\n" \ + "\t\t\t\t<!ATTLIST info value CDATA #IMPLIED>\n" \ + "\t\t\t<!ELEMENT sharedfeat EMPTY>\n" \ + "\t\t\t\t<!ATTLIST sharedfeat name CDATA #REQUIRED>\n" \ + "\t\t\t\t<!ATTLIST sharedfeat value CDATA #IMPLIED>\n" \ + "\t\t\t<!ELEMENT part (feature*, dataarea*, diskarea*, dipswitch*)>\n" \ + "\t\t\t\t<!ATTLIST part name CDATA #REQUIRED>\n" \ + "\t\t\t\t<!ATTLIST part interface CDATA #REQUIRED>\n" \ + "\t\t\t\t<!ELEMENT feature EMPTY>\n" \ + "\t\t\t\t\t<!ATTLIST feature name CDATA #REQUIRED>\n" \ + "\t\t\t\t\t<!ATTLIST feature value CDATA #IMPLIED>\n" \ + "\t\t\t\t<!ELEMENT dataarea (rom*)>\n" \ + "\t\t\t\t\t<!ATTLIST dataarea name CDATA #REQUIRED>\n" \ + "\t\t\t\t\t<!ATTLIST dataarea size CDATA #REQUIRED>\n" \ + "\t\t\t\t\t<!ATTLIST dataarea databits (8|16|32|64) \"8\">\n" \ + "\t\t\t\t\t<!ATTLIST dataarea endian (big|little) \"little\">\n" \ + "\t\t\t\t\t<!ELEMENT rom EMPTY>\n" \ + "\t\t\t\t\t\t<!ATTLIST rom name CDATA #IMPLIED>\n" \ + "\t\t\t\t\t\t<!ATTLIST rom size CDATA #IMPLIED>\n" \ + "\t\t\t\t\t\t<!ATTLIST rom length CDATA #IMPLIED>\n" \ + "\t\t\t\t\t\t<!ATTLIST rom crc CDATA #IMPLIED>\n" \ + "\t\t\t\t\t\t<!ATTLIST rom sha1 CDATA #IMPLIED>\n" \ + "\t\t\t\t\t\t<!ATTLIST rom offset CDATA #IMPLIED>\n" \ + "\t\t\t\t\t\t<!ATTLIST rom value CDATA #IMPLIED>\n" \ + "\t\t\t\t\t\t<!ATTLIST rom status (baddump|nodump|good) \"good\">\n" \ + "\t\t\t\t\t\t<!ATTLIST rom loadflag (load16_byte|load16_word|load16_word_swap|load32_byte|load32_word|load32_word_swap|load32_dword|load64_word|load64_word_swap|reload|fill|continue|reload_plain) #IMPLIED>\n" \ + "\t\t\t\t<!ELEMENT diskarea (disk*)>\n" \ + "\t\t\t\t\t<!ATTLIST diskarea name CDATA #REQUIRED>\n" \ + "\t\t\t\t\t<!ELEMENT disk EMPTY>\n" \ + "\t\t\t\t\t\t<!ATTLIST disk name CDATA #REQUIRED>\n" \ + "\t\t\t\t\t\t<!ATTLIST disk sha1 CDATA #IMPLIED>\n" \ + "\t\t\t\t\t\t<!ATTLIST disk status (baddump|nodump|good) \"good\">\n" \ + "\t\t\t\t\t\t<!ATTLIST disk writeable (yes|no) \"no\">\n" \ + "\t\t\t\t<!ELEMENT dipswitch (dipvalue*)>\n" \ + "\t\t\t\t\t<!ATTLIST dipswitch name CDATA #REQUIRED>\n" \ + "\t\t\t\t\t<!ATTLIST dipswitch tag CDATA #REQUIRED>\n" \ + "\t\t\t\t\t<!ATTLIST dipswitch mask CDATA #REQUIRED>\n" \ + "\t\t\t\t\t<!ELEMENT dipvalue EMPTY>\n" \ + "\t\t\t\t\t\t<!ATTLIST dipvalue name CDATA #REQUIRED>\n" \ + "\t\t\t\t\t\t<!ATTLIST dipvalue value CDATA #REQUIRED>\n" \ + "\t\t\t\t\t\t<!ATTLIST dipvalue default (yes|no) \"no\">\n" \ + "]>\n\n" \ + "<softwarelists>\n" + +void cli_frontend::output_single_softlist(FILE *out, software_list_device &swlistdev) +{ + fprintf(out, "\t<softwarelist name=\"%s\" description=\"%s\">\n", swlistdev.list_name(), xml_normalize_string(swlistdev.description())); + for (software_info &swinfo : swlistdev.get_info()) + { + fprintf(out, "\t\t<software name=\"%s\"", swinfo.shortname()); + 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<description>%s</description>\n", xml_normalize_string(swinfo.longname())); + fprintf(out, "\t\t\t<year>%s</year>\n", xml_normalize_string(swinfo.year())); + fprintf(out, "\t\t\t<publisher>%s</publisher>\n", xml_normalize_string(swinfo.publisher())); + + for (feature_list_item &flist : swinfo.other_info()) + fprintf( out, "\t\t\t<info name=\"%s\" value=\"%s\"/>\n", flist.name(), xml_normalize_string( flist.value() ) ); + + for (software_part &part : swinfo.parts()) + { + fprintf(out, "\t\t\t<part name=\"%s\"", part.name()); + if (part.interface() != nullptr) + fprintf(out, " interface=\"%s\"", part.interface()); + + fprintf(out, ">\n"); + + for (feature_list_item &flist : part.featurelist()) + fprintf(out, "\t\t\t\t<feature name=\"%s\" value=\"%s\" />\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<dataarea name=\"%s\" size=\"%d\">\n", ROMREGION_GETTAG(region), ROMREGION_GETLENGTH(region) ); + else + fprintf( out, "\t\t\t\t<diskarea name=\"%s\">\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<rom name=\"%s\" size=\"%d\"", xml_normalize_string(ROM_GETNAME(rom)), rom_file_size(rom) ); + else + fprintf( out, "\t\t\t\t\t<disk name=\"%s\"", xml_normalize_string(ROM_GETNAME(rom)) ); + + /* dump checksum information only if there is a known dump */ + hash_collection hashes(ROM_GETHASHDATA(rom)); + if ( !hashes.flag(hash_collection::FLAG_NO_DUMP) ) + fprintf( out, " %s", hashes.attribute_string().c_str() ); + else + 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<rom size=\"%d\" offset=\"0x%x\" loadflag=\"reload\" />\n", ROM_GETLENGTH(rom), ROM_GETOFFSET(rom) ); + } + else if ( ROMENTRY_ISFILL(rom) ) + { + fprintf( out, "\t\t\t\t\t<rom size=\"%d\" offset=\"0x%x\" loadflag=\"fill\" />\n", ROM_GETLENGTH(rom), ROM_GETOFFSET(rom) ); + } + } + + if (!is_disk) + fprintf( out, "\t\t\t\t</dataarea>\n" ); + else + fprintf( out, "\t\t\t\t</diskarea>\n" ); + } + + fprintf( out, "\t\t\t</part>\n" ); + } + + fprintf( out, "\t\t</software>\n" ); + } + fprintf(out, "\t</softwarelist>\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<std::string> 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(EMU_ERR_NO_SUCH_GAME, "No matching games found for '%s'", gamename); + + while (drivlist.next()) + { + for (software_list_device &swlistdev : software_list_device_iterator(drivlist.config().root_device())) + if (list_map.insert(swlistdev.list_name()).second) + if (!swlistdev.get_info().empty()) + { + if (isfirst) { fprintf(out, SOFTLIST_XML_BEGIN); isfirst = false; } + output_single_softlist(out, swlistdev); + } + } + + if (!isfirst) + fprintf( out, "</softwarelists>\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<std::string> 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(EMU_ERR_NO_SUCH_GAME, "No matching games found for '%s'", gamename); + } + + media_auditor auditor(drivlist); + while (drivlist.next()) + { + matched++; + + for (software_list_device &swlistdev : software_list_device_iterator(drivlist.config().root_device())) + if (swlistdev.list_type() == SOFTWARE_LIST_ORIGINAL_SYSTEM) + if (list_map.insert(swlistdev.list_name()).second) + if (!swlistdev.get_info().empty()) + { + nrlists++; + for (software_info &swinfo : swlistdev.get_info()) + { + 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(EMU_ERR_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(EMU_ERR_MISSING_FILES, "romset \"%s\" has no software entries defined!\n", gamename); + } + // otherwise, print a summary + else + { + if (incorrect > 0) + throw emu_fatalerror(EMU_ERR_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<std::string> list_map; + bool isfirst = TRUE; + + driver_enumerator drivlist(m_options); + while (drivlist.next()) + { + for (software_list_device &swlistdev : software_list_device_iterator(drivlist.config().root_device())) + if (core_strwildcmp(gamename, swlistdev.list_name()) == 0 && list_map.insert(swlistdev.list_name()).second) + if (!swlistdev.get_info().empty()) + { + if (isfirst) { fprintf( out, SOFTLIST_XML_BEGIN); isfirst = FALSE; } + output_single_softlist(out, swlistdev); + } + } + + if (!isfirst) + fprintf( out, "</softwarelists>\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<std::string> 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()) + { + for (software_list_device &swlistdev : software_list_device_iterator(drivlist.config().root_device())) + if (core_strwildcmp(gamename, swlistdev.list_name()) == 0 && list_map.insert(swlistdev.list_name()).second) + if (!swlistdev.get_info().empty()) + { + matched++; + + // Get the actual software list contents + for (software_info &swinfo : swlistdev.get_info()) + { + 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(EMU_ERR_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(EMU_ERR_MISSING_FILES, "no romsets found for software list \"%s\"!\n", gamename); + } + // otherwise, print a summary + else + { + if (incorrect > 0) + throw emu_fatalerror(EMU_ERR_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(EMU_ERR_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(EMU_ERR_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(EMU_ERR_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(exename); + 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); + valid.set_validate_all(true); + const char *sysname = m_options.system_name(); + bool result = valid.check_all_matching((sysname[0] == 0) ? "*" : sysname); + if (!result) + throw emu_fatalerror(EMU_ERR_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; + mame_options::parse_standard_inis(m_options,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()); + + plugin_options plugin_opts; + path_iterator iter(m_options.plugins_path()); + std::string pluginpath; + while (iter.next(pluginpath)) + { + plugin_opts.parse_json(pluginpath); + } + emu_file file_plugin(OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS); + if (file_plugin.open("plugin.ini") != osd_file::error::NONE) + throw emu_fatalerror("Unable to create file plugin.ini\n"); + + // generate the updated INI + file_plugin.puts(plugin_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(EMU_ERR_INVALID_CONFIG, "Unknown command '%s' specified", m_options.command()); +} + + +//------------------------------------------------- +// display_help - display help to standard +// output +//------------------------------------------------- + +void cli_frontend::display_help(const char *exename) +{ + 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]",exename); + 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",exename, + exename,exename,exename,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(emu_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<std::uint8_t> 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<UINT8 *>(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<std::string> listnames; + std::unordered_set<std::string> shortnames; + + // iterate over drivers + m_drivlist.reset(); + while (m_drivlist.next()) + { + // iterate over devices, regions and files within the region + for (device_t &device : device_iterator(m_drivlist.config().root_device())) + { + 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 + for (software_list_device &swlistdev : software_list_device_iterator(m_drivlist.config().root_device())) + { + if (listnames.insert(swlistdev.list_name()).second) + { + for (software_info &swinfo : swlistdev.get_info()) + for (software_part &part : swinfo.parts()) + 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; +} |