// license:BSD-3-Clause // copyright-holders:Aaron Giles, Paul Priest /*************************************************************************** validity.cpp Validity checks on internal data structures. ***************************************************************************/ #include "emu.h" #include "validity.h" #include "emuopts.h" #include //************************************************************************** // TYPE DEFINITIONS //************************************************************************** //************************************************************************** // INLINE FUNCTIONS //************************************************************************** //------------------------------------------------- // ioport_string_from_index - return an indexed // string from the I/O port system //------------------------------------------------- inline const char *validity_checker::ioport_string_from_index(UINT32 index) { return ioport_configurer::string_from_token((const char *)(FPTR)index); } //------------------------------------------------- // get_defstr_index - return the index of the // string assuming it is one of the default // strings //------------------------------------------------- inline int validity_checker::get_defstr_index(const char *string, bool suppress_error) { // check for strings that should be DEF_STR auto strindex = m_defstr_map.find(string); if (!suppress_error && strindex != m_defstr_map.end() && string != ioport_string_from_index(strindex->second)) osd_printf_error("Must use DEF_STR( %s )\n", string); return (strindex != m_defstr_map.end()) ? strindex->second : 0; } //------------------------------------------------- // validate_tag - ensure that the given tag // meets the general requirements //------------------------------------------------- void validity_checker::validate_tag(const char *tag) { // some common names that are now deprecated if (strcmp(tag, "main") == 0 || strcmp(tag, "audio") == 0 || strcmp(tag, "sound") == 0 || strcmp(tag, "left") == 0 || strcmp(tag, "right") == 0) osd_printf_error("Invalid generic tag '%s' used\n", tag); // scan for invalid characters static const char *validchars = "abcdefghijklmnopqrstuvwxyz0123456789_.:^$"; for (const char *p = tag; *p != 0; p++) { // only lower-case permitted if (*p != tolower((UINT8)*p)) { osd_printf_error("Tag '%s' contains upper-case characters\n", tag); break; } if (*p == ' ') { osd_printf_error("Tag '%s' contains spaces\n", tag); break; } if (strchr(validchars, *p) == nullptr) { osd_printf_error("Tag '%s' contains invalid character '%c'\n", tag, *p); break; } } // find the start of the final tag const char *begin = strrchr(tag, ':'); if (begin == nullptr) begin = tag; else begin += 1; // 0-length = bad if (*begin == 0) osd_printf_error("Found 0-length tag\n"); // too short/too long = bad if (strlen(begin) < MIN_TAG_LENGTH) osd_printf_error("Tag '%s' is too short (must be at least %d characters)\n", tag, MIN_TAG_LENGTH); } //************************************************************************** // VALIDATION FUNCTIONS //************************************************************************** //------------------------------------------------- // validity_checker - constructor //------------------------------------------------- validity_checker::validity_checker(emu_options &options) : m_drivlist(options), m_errors(0), m_warnings(0), m_print_verbose(options.verbose()), m_current_driver(nullptr), m_current_config(nullptr), m_current_device(nullptr), m_current_ioport(nullptr), m_validate_all(false) { // pre-populate the defstr map with all the default strings for (int strnum = 1; strnum < INPUT_STRING_COUNT; strnum++) { const char *string = ioport_string_from_index(strnum); if (string != nullptr) m_defstr_map.insert(std::make_pair(string, strnum)); } } //------------------------------------------------- // validity_checker - destructor //------------------------------------------------- validity_checker::~validity_checker() { validate_end(); } //------------------------------------------------- // check_driver - check a single driver //------------------------------------------------- void validity_checker::check_driver(const game_driver &driver) { // simply validate the one driver validate_begin(); validate_one(driver); validate_end(); } //------------------------------------------------- // check_shared_source - check all drivers that // share the same source file as the given driver //------------------------------------------------- void validity_checker::check_shared_source(const game_driver &driver) { // initialize validate_begin(); // then iterate over all drivers and check the ones that share the same source file m_drivlist.reset(); while (m_drivlist.next()) if (strcmp(driver.source_file, m_drivlist.driver().source_file) == 0) validate_one(m_drivlist.driver()); // cleanup validate_end(); } //------------------------------------------------- // check_all_matching - check all drivers whose // names match the given string //------------------------------------------------- bool validity_checker::check_all_matching(const char *string) { // start by checking core stuff validate_begin(); validate_core(); validate_inlines(); // if we had warnings or errors, output if (m_errors > 0 || m_warnings > 0 || !m_verbose_text.empty()) { output_via_delegate(OSD_OUTPUT_CHANNEL_ERROR, "Core: %d errors, %d warnings\n", m_errors, m_warnings); if (m_errors > 0) output_indented_errors(m_error_text, "Errors"); if (m_warnings > 0) output_indented_errors(m_warning_text, "Warnings"); if (!m_verbose_text.empty()) output_indented_errors(m_verbose_text, "Messages"); output_via_delegate(OSD_OUTPUT_CHANNEL_ERROR, "\n"); } // then iterate over all drivers and check them m_drivlist.reset(); while (m_drivlist.next()) if (m_drivlist.matches(string, m_drivlist.driver().name)) validate_one(m_drivlist.driver()); // cleanup validate_end(); return !(m_errors > 0 || m_warnings > 0); } //------------------------------------------------- // validate_begin - prepare for validation by // taking over the output callbacks and resetting // our internal state //------------------------------------------------- void validity_checker::validate_begin() { // take over error and warning outputs osd_output::push(this); // reset all our maps m_names_map.clear(); m_descriptions_map.clear(); m_roms_map.clear(); m_defstr_map.clear(); m_region_map.clear(); // reset internal state m_errors = 0; m_warnings = 0; m_already_checked.clear(); } //------------------------------------------------- // validate_end - restore output callbacks and // clean up //------------------------------------------------- void validity_checker::validate_end() { // restore the original output callbacks osd_output::pop(this); } //------------------------------------------------- // validate_drivers - master validity checker //------------------------------------------------- void validity_checker::validate_one(const game_driver &driver) { // set the current driver m_current_driver = &driver; m_current_config = nullptr; m_current_device = nullptr; m_current_ioport = nullptr; m_region_map.clear(); // reset error/warning state int start_errors = m_errors; int start_warnings = m_warnings; m_error_text.clear(); m_warning_text.clear(); m_verbose_text.clear(); // wrap in try/except to catch fatalerrors try { machine_config config(driver, m_drivlist.options()); m_current_config = &config; validate_driver(); validate_roms(); validate_inputs(); validate_devices(); m_current_config = nullptr; } catch (emu_fatalerror &err) { osd_printf_error("Fatal error %s", err.string()); } // if we had warnings or errors, output if (m_errors > start_errors || m_warnings > start_warnings || !m_verbose_text.empty()) { output_via_delegate(OSD_OUTPUT_CHANNEL_ERROR, "Driver %s (file %s): %d errors, %d warnings\n", driver.name, core_filename_extract_base(driver.source_file).c_str(), m_errors - start_errors, m_warnings - start_warnings); if (m_errors > start_errors) output_indented_errors(m_error_text, "Errors"); if (m_warnings > start_warnings) output_indented_errors(m_warning_text, "Warnings"); if (!m_verbose_text.empty()) output_indented_errors(m_verbose_text, "Messages"); output_via_delegate(OSD_OUTPUT_CHANNEL_ERROR, "\n"); } // reset the driver/device m_current_driver = nullptr; m_current_config = nullptr; m_current_device = nullptr; m_current_ioport = nullptr; } //------------------------------------------------- // validate_core - validate core internal systems //------------------------------------------------- void validity_checker::validate_core() { // basic system checks if (~0 != -1) osd_printf_error("Machine must be two's complement\n"); UINT8 a = 0xff; UINT8 b = a + 1; if (b > a) osd_printf_error("UINT8 must be 8 bits\n"); // check size of core integer types if (sizeof(INT8) != 1) osd_printf_error("INT8 must be 8 bits\n"); if (sizeof(UINT8) != 1) osd_printf_error("UINT8 must be 8 bits\n"); if (sizeof(INT16) != 2) osd_printf_error("INT16 must be 16 bits\n"); if (sizeof(UINT16) != 2) osd_printf_error("UINT16 must be 16 bits\n"); if (sizeof(INT32) != 4) osd_printf_error("INT32 must be 32 bits\n"); if (sizeof(UINT32) != 4) osd_printf_error("UINT32 must be 32 bits\n"); if (sizeof(INT64) != 8) osd_printf_error("INT64 must be 64 bits\n"); if (sizeof(UINT64) != 8) osd_printf_error("UINT64 must be 64 bits\n"); // check signed right shift INT8 a8 = -3; INT16 a16 = -3; INT32 a32 = -3; INT64 a64 = -3; if (a8 >> 1 != -2) osd_printf_error("INT8 right shift must be arithmetic\n"); if (a16 >> 1 != -2) osd_printf_error("INT16 right shift must be arithmetic\n"); if (a32 >> 1 != -2) osd_printf_error("INT32 right shift must be arithmetic\n"); if (a64 >> 1 != -2) osd_printf_error("INT64 right shift must be arithmetic\n"); // check pointer size #ifdef PTR64 static_assert(sizeof(void *) == 8, "PTR64 flag enabled, but was compiled for 32-bit target\n"); #else static_assert(sizeof(void *) == 4, "PTR64 flag not enabled, but was compiled for 64-bit target\n"); #endif // TODO: check if this is actually working // check endianness definition UINT16 lsbtest = 0; *(UINT8 *)&lsbtest = 0xff; #ifdef LSB_FIRST if (lsbtest == 0xff00) osd_printf_error("LSB_FIRST specified, but running on a big-endian machine\n"); #else if (lsbtest == 0x00ff) osd_printf_error("LSB_FIRST not specified, but running on a little-endian machine\n"); #endif } //------------------------------------------------- // validate_inlines - validate inline function // behaviors //------------------------------------------------- void validity_checker::validate_inlines() { #undef rand volatile UINT64 testu64a = rand() ^ (rand() << 15) ^ ((UINT64)rand() << 30) ^ ((UINT64)rand() << 45); volatile INT64 testi64a = rand() ^ (rand() << 15) ^ ((INT64)rand() << 30) ^ ((INT64)rand() << 45); volatile UINT32 testu32a = rand() ^ (rand() << 15); volatile UINT32 testu32b = rand() ^ (rand() << 15); volatile INT32 testi32a = rand() ^ (rand() << 15); volatile INT32 testi32b = rand() ^ (rand() << 15); INT32 resulti32, expectedi32; UINT32 resultu32, expectedu32; INT64 resulti64, expectedi64; UINT64 resultu64, expectedu64; INT32 remainder, expremainder; UINT32 uremainder, expuremainder, bigu32 = 0xffffffff; // use only non-zero, positive numbers if (testu64a == 0) testu64a++; if (testi64a == 0) testi64a++; else if (testi64a < 0) testi64a = -testi64a; if (testu32a == 0) testu32a++; if (testu32b == 0) testu32b++; if (testi32a == 0) testi32a++; else if (testi32a < 0) testi32a = -testi32a; if (testi32b == 0) testi32b++; else if (testi32b < 0) testi32b = -testi32b; resulti64 = mul_32x32(testi32a, testi32b); expectedi64 = (INT64)testi32a * (INT64)testi32b; if (resulti64 != expectedi64) osd_printf_error("Error testing mul_32x32 (%08X x %08X) = %08X%08X (expected %08X%08X)\n", testi32a, testi32b, (UINT32)(resulti64 >> 32), (UINT32)resulti64, (UINT32)(expectedi64 >> 32), (UINT32)expectedi64); resultu64 = mulu_32x32(testu32a, testu32b); expectedu64 = (UINT64)testu32a * (UINT64)testu32b; if (resultu64 != expectedu64) osd_printf_error("Error testing mulu_32x32 (%08X x %08X) = %08X%08X (expected %08X%08X)\n", testu32a, testu32b, (UINT32)(resultu64 >> 32), (UINT32)resultu64, (UINT32)(expectedu64 >> 32), (UINT32)expectedu64); resulti32 = mul_32x32_hi(testi32a, testi32b); expectedi32 = ((INT64)testi32a * (INT64)testi32b) >> 32; if (resulti32 != expectedi32) osd_printf_error("Error testing mul_32x32_hi (%08X x %08X) = %08X (expected %08X)\n", testi32a, testi32b, resulti32, expectedi32); resultu32 = mulu_32x32_hi(testu32a, testu32b); expectedu32 = ((INT64)testu32a * (INT64)testu32b) >> 32; if (resultu32 != expectedu32) osd_printf_error("Error testing mulu_32x32_hi (%08X x %08X) = %08X (expected %08X)\n", testu32a, testu32b, resultu32, expectedu32); resulti32 = mul_32x32_shift(testi32a, testi32b, 7); expectedi32 = ((INT64)testi32a * (INT64)testi32b) >> 7; if (resulti32 != expectedi32) osd_printf_error("Error testing mul_32x32_shift (%08X x %08X) >> 7 = %08X (expected %08X)\n", testi32a, testi32b, resulti32, expectedi32); resultu32 = mulu_32x32_shift(testu32a, testu32b, 7); expectedu32 = ((INT64)testu32a * (INT64)testu32b) >> 7; if (resultu32 != expectedu32) osd_printf_error("Error testing mulu_32x32_shift (%08X x %08X) >> 7 = %08X (expected %08X)\n", testu32a, testu32b, resultu32, expectedu32); while ((INT64)testi32a * (INT64)0x7fffffff < testi64a) testi64a /= 2; while ((UINT64)testu32a * (UINT64)bigu32 < testu64a) testu64a /= 2; resulti32 = div_64x32(testi64a, testi32a); expectedi32 = testi64a / (INT64)testi32a; if (resulti32 != expectedi32) osd_printf_error("Error testing div_64x32 (%08X%08X / %08X) = %08X (expected %08X)\n", (UINT32)(testi64a >> 32), (UINT32)testi64a, testi32a, resulti32, expectedi32); resultu32 = divu_64x32(testu64a, testu32a); expectedu32 = testu64a / (UINT64)testu32a; if (resultu32 != expectedu32) osd_printf_error("Error testing divu_64x32 (%08X%08X / %08X) = %08X (expected %08X)\n", (UINT32)(testu64a >> 32), (UINT32)testu64a, testu32a, resultu32, expectedu32); resulti32 = div_64x32_rem(testi64a, testi32a, &remainder); expectedi32 = testi64a / (INT64)testi32a; expremainder = testi64a % (INT64)testi32a; if (resulti32 != expectedi32 || remainder != expremainder) osd_printf_error("Error testing div_64x32_rem (%08X%08X / %08X) = %08X,%08X (expected %08X,%08X)\n", (UINT32)(testi64a >> 32), (UINT32)testi64a, testi32a, resulti32, remainder, expectedi32, expremainder); resultu32 = divu_64x32_rem(testu64a, testu32a, &uremainder); expectedu32 = testu64a / (UINT64)testu32a; expuremainder = testu64a % (UINT64)testu32a; if (resultu32 != expectedu32 || uremainder != expuremainder) osd_printf_error("Error testing divu_64x32_rem (%08X%08X / %08X) = %08X,%08X (expected %08X,%08X)\n", (UINT32)(testu64a >> 32), (UINT32)testu64a, testu32a, resultu32, uremainder, expectedu32, expuremainder); resulti32 = mod_64x32(testi64a, testi32a); expectedi32 = testi64a % (INT64)testi32a; if (resulti32 != expectedi32) osd_printf_error("Error testing mod_64x32 (%08X%08X / %08X) = %08X (expected %08X)\n", (UINT32)(testi64a >> 32), (UINT32)testi64a, testi32a, resulti32, expectedi32); resultu32 = modu_64x32(testu64a, testu32a); expectedu32 = testu64a % (UINT64)testu32a; if (resultu32 != expectedu32) osd_printf_error("Error testing modu_64x32 (%08X%08X / %08X) = %08X (expected %08X)\n", (UINT32)(testu64a >> 32), (UINT32)testu64a, testu32a, resultu32, expectedu32); while ((INT64)testi32a * (INT64)0x7fffffff < ((INT32)testi64a << 3)) testi64a /= 2; while ((UINT64)testu32a * (UINT64)0xffffffff < ((UINT32)testu64a << 3)) testu64a /= 2; resulti32 = div_32x32_shift((INT32)testi64a, testi32a, 3); expectedi32 = ((INT64)(INT32)testi64a << 3) / (INT64)testi32a; if (resulti32 != expectedi32) osd_printf_error("Error testing div_32x32_shift (%08X << 3) / %08X = %08X (expected %08X)\n", (INT32)testi64a, testi32a, resulti32, expectedi32); resultu32 = divu_32x32_shift((UINT32)testu64a, testu32a, 3); expectedu32 = ((UINT64)(UINT32)testu64a << 3) / (UINT64)testu32a; if (resultu32 != expectedu32) osd_printf_error("Error testing divu_32x32_shift (%08X << 3) / %08X = %08X (expected %08X)\n", (UINT32)testu64a, testu32a, resultu32, expectedu32); if (fabsf(recip_approx(100.0f) - 0.01f) > 0.0001f) osd_printf_error("Error testing recip_approx\n"); testi32a = (testi32a & 0x0000ffff) | 0x400000; if (count_leading_zeros(testi32a) != 9) osd_printf_error("Error testing count_leading_zeros\n"); testi32a = (testi32a | 0xffff0000) & ~0x400000; if (count_leading_ones(testi32a) != 9) osd_printf_error("Error testing count_leading_ones\n"); } //------------------------------------------------- // validate_driver - validate basic driver // information //------------------------------------------------- void validity_checker::validate_driver() { // check for duplicate names if (!m_names_map.insert(std::make_pair(m_current_driver->name, m_current_driver)).second) { const game_driver *match = m_names_map.find(m_current_driver->name)->second; osd_printf_error("Driver name is a duplicate of %s(%s)\n", core_filename_extract_base(match->source_file).c_str(), match->name); } // check for duplicate descriptions if (!m_descriptions_map.insert(std::make_pair(m_current_driver->description, m_current_driver)).second) { const game_driver *match = m_descriptions_map.find(m_current_driver->description)->second; osd_printf_error("Driver description is a duplicate of %s(%s)\n", core_filename_extract_base(match->source_file).c_str(), match->name); } // determine if we are a clone bool is_clone = (strcmp(m_current_driver->parent, "0") != 0); int clone_of = m_drivlist.clone(*m_current_driver); if (clone_of != -1 && (m_drivlist.driver(clone_of).flags & MACHINE_IS_BIOS_ROOT)) is_clone = false; // if we have at least 100 drivers, validate the clone // (100 is arbitrary, but tries to avoid tiny.mak dependencies) if (driver_list::total() > 100 && clone_of == -1 && is_clone) osd_printf_error("Driver is a clone of nonexistent driver %s\n", m_current_driver->parent); // look for recursive cloning if (clone_of != -1 && &m_drivlist.driver(clone_of) == m_current_driver) osd_printf_error("Driver is a clone of itself\n"); // look for clones that are too deep if (clone_of != -1 && (clone_of = m_drivlist.non_bios_clone(clone_of)) != -1) osd_printf_error("Driver is a clone of a clone\n"); // make sure the driver name is not too long if (!is_clone && strlen(m_current_driver->name) > 8) osd_printf_error("Parent driver name must be 8 characters or less\n"); if (is_clone && strlen(m_current_driver->name) > 16) osd_printf_error("Clone driver name must be 16 characters or less\n"); // make sure the year is only digits, '?' or '+' for (const char *s = m_current_driver->year; *s != 0; s++) if (!isdigit((UINT8)*s) && *s != '?' && *s != '+') { osd_printf_error("Driver has an invalid year '%s'\n", m_current_driver->year); break; } // normalize driver->compatible_with const char *compatible_with = m_current_driver->compatible_with; if (compatible_with != nullptr && strcmp(compatible_with, "0") == 0) compatible_with = nullptr; // check for this driver being compatible with a non-existant driver if (compatible_with != nullptr && m_drivlist.find(m_current_driver->compatible_with) == -1) osd_printf_error("Driver is listed as compatible with nonexistent driver %s\n", m_current_driver->compatible_with); // check for clone_of and compatible_with being specified at the same time if (m_drivlist.clone(*m_current_driver) != -1 && compatible_with != nullptr) osd_printf_error("Driver cannot be both a clone and listed as compatible with another system\n"); // find any recursive dependencies on the current driver for (int other_drv = m_drivlist.compatible_with(*m_current_driver); other_drv != -1; other_drv = m_drivlist.compatible_with(other_drv)) if (m_current_driver == &m_drivlist.driver(other_drv)) { osd_printf_error("Driver is recursively compatible with itself\n"); break; } // make sure sound-less drivers are flagged sound_interface_iterator iter(m_current_config->root_device()); if ((m_current_driver->flags & MACHINE_IS_BIOS_ROOT) == 0 && iter.first() == nullptr && (m_current_driver->flags & MACHINE_NO_SOUND) == 0 && (m_current_driver->flags & MACHINE_NO_SOUND_HW) == 0) osd_printf_error("Driver is missing MACHINE_NO_SOUND flag\n"); } //------------------------------------------------- // validate_roms - validate ROM definitions //------------------------------------------------- void validity_checker::validate_roms() { // iterate, starting with the driver's ROMs and continuing with device ROMs for (device_t &device : device_iterator(m_current_config->root_device())) { // track the current device m_current_device = &device; // scan the ROM entries for this device const char *last_region_name = "???"; const char *last_name = "???"; UINT32 current_length = 0; int items_since_region = 1; int last_bios = 0; int total_files = 0; for (const rom_entry *romp = rom_first_region(device); romp != nullptr && !ROMENTRY_ISEND(romp); romp++) { // if this is a region, make sure it's valid, and record the length if (ROMENTRY_ISREGION(romp)) { // if we haven't seen any items since the last region, print a warning if (items_since_region == 0) osd_printf_warning("Empty ROM region '%s' (warning)\n", last_region_name); // reset our region tracking states const char *basetag = ROMREGION_GETTAG(romp); items_since_region = (ROMREGION_ISERASE(romp) || ROMREGION_ISDISKDATA(romp)) ? 1 : 0; last_region_name = basetag; // check for a valid tag if (basetag == nullptr) { osd_printf_error("ROM_REGION tag with nullptr name\n"); continue; } // validate the base tag validate_tag(basetag); // generate the full tag std::string fulltag = rom_region_name(device, romp); // attempt to add it to the map, reporting duplicates as errors current_length = ROMREGION_GETLENGTH(romp); if (!m_region_map.insert(std::make_pair(fulltag, current_length)).second) osd_printf_error("Multiple ROM_REGIONs with the same tag '%s' defined\n", fulltag.c_str()); } // If this is a system bios, make sure it is using the next available bios number else if (ROMENTRY_ISSYSTEM_BIOS(romp)) { int bios_flags = ROM_GETBIOSFLAGS(romp); if (bios_flags != last_bios + 1) osd_printf_error("Non-sequential bios %s (specified as %d, expected to be %d)\n", ROM_GETNAME(romp), bios_flags, last_bios + 1); last_bios = bios_flags; } // if this is a file, make sure it is properly formatted else if (ROMENTRY_ISFILE(romp)) { // track the last filename we found last_name = ROM_GETNAME(romp); total_files++; // make sure the hash is valid hash_collection hashes; if (!hashes.from_internal_string(ROM_GETHASHDATA(romp))) osd_printf_error("ROM '%s' has an invalid hash string '%s'\n", last_name, ROM_GETHASHDATA(romp)); } // for any non-region ending entries, make sure they don't extend past the end if (!ROMENTRY_ISREGIONEND(romp) && current_length > 0) { items_since_region++; if (ROM_GETOFFSET(romp) + ROM_GETLENGTH(romp) > current_length) osd_printf_error("ROM '%s' extends past the defined memory region\n", last_name); } } // final check for empty regions if (items_since_region == 0) osd_printf_warning("Empty ROM region '%s' (warning)\n", last_region_name); // reset the current device m_current_device = nullptr; } } //------------------------------------------------- // validate_analog_input_field - validate an // analog input field //------------------------------------------------- void validity_checker::validate_analog_input_field(ioport_field &field) { // analog ports must have a valid sensitivity if (field.sensitivity() == 0) osd_printf_error("Analog port with zero sensitivity\n"); // check that the default falls in the bitmask range if (field.defvalue() & ~field.mask()) osd_printf_error("Analog port with a default value (%X) out of the bitmask range (%X)\n", field.defvalue(), field.mask()); // tests for positional devices if (field.type() == IPT_POSITIONAL || field.type() == IPT_POSITIONAL_V) { int shift; for (shift = 0; shift <= 31 && (~field.mask() & (1 << shift)) != 0; shift++) { } // convert the positional max value to be in the bitmask for testing //INT32 analog_max = field.maxval(); //analog_max = (analog_max - 1) << shift; // positional port size must fit in bits used if ((field.mask() >> shift) + 1 < field.maxval()) osd_printf_error("Analog port with a positional port size bigger then the mask size\n"); } // tests for absolute devices else if (field.type() > IPT_ANALOG_ABSOLUTE_FIRST && field.type() < IPT_ANALOG_ABSOLUTE_LAST) { // adjust for signed values INT32 default_value = field.defvalue(); INT32 analog_min = field.minval(); INT32 analog_max = field.maxval(); if (analog_min > analog_max) { analog_min = -analog_min; if (default_value > analog_max) default_value = -default_value; } // check that the default falls in the MINMAX range if (default_value < analog_min || default_value > analog_max) osd_printf_error("Analog port with a default value (%X) out of PORT_MINMAX range (%X-%X)\n", field.defvalue(), field.minval(), field.maxval()); // check that the MINMAX falls in the bitmask range // we use the unadjusted min for testing if (field.minval() & ~field.mask() || analog_max & ~field.mask()) osd_printf_error("Analog port with a PORT_MINMAX (%X-%X) value out of the bitmask range (%X)\n", field.minval(), field.maxval(), field.mask()); // absolute analog ports do not use PORT_RESET if (field.analog_reset()) osd_printf_error("Absolute analog port using PORT_RESET\n"); // absolute analog ports do not use PORT_WRAPS if (field.analog_wraps()) osd_printf_error("Absolute analog port using PORT_WRAPS\n"); } // tests for non IPT_POSITIONAL relative devices else { // relative devices do not use PORT_MINMAX if (field.minval() != 0 || field.maxval() != field.mask()) osd_printf_error("Relative port using PORT_MINMAX\n"); // relative devices do not use a default value // the counter is at 0 on power up if (field.defvalue() != 0) osd_printf_error("Relative port using non-0 default value\n"); // relative analog ports do not use PORT_WRAPS if (field.analog_wraps()) osd_printf_error("Absolute analog port using PORT_WRAPS\n"); } } //------------------------------------------------- // validate_dip_settings - validate a DIP switch // setting //------------------------------------------------- void validity_checker::validate_dip_settings(ioport_field &field) { const char *demo_sounds = ioport_string_from_index(INPUT_STRING_Demo_Sounds); const char *flipscreen = ioport_string_from_index(INPUT_STRING_Flip_Screen); UINT8 coin_list[__input_string_coinage_end + 1 - __input_string_coinage_start] = { 0 }; bool coin_error = false; // iterate through the settings for (ioport_setting &setting : field.settings()) { // note any coinage strings int strindex = get_defstr_index(setting.name()); if (strindex >= __input_string_coinage_start && strindex <= __input_string_coinage_end) coin_list[strindex - __input_string_coinage_start] = 1; // make sure demo sounds default to on if (field.name() == demo_sounds && strindex == INPUT_STRING_On && field.defvalue() != setting.value()) osd_printf_error("Demo Sounds must default to On\n"); // check for bad demo sounds options if (field.name() == demo_sounds && (strindex == INPUT_STRING_Yes || strindex == INPUT_STRING_No)) osd_printf_error("Demo Sounds option must be Off/On, not %s\n", setting.name()); // check for bad flip screen options if (field.name() == flipscreen && (strindex == INPUT_STRING_Yes || strindex == INPUT_STRING_No)) osd_printf_error("Flip Screen option must be Off/On, not %s\n", setting.name()); // if we have a neighbor, compare ourselves to him if (setting.next() != nullptr) { // check for inverted off/on dispswitch order int next_strindex = get_defstr_index(setting.next()->name(), true); if (strindex == INPUT_STRING_On && next_strindex == INPUT_STRING_Off) osd_printf_error("%s option must have Off/On options in the order: Off, On\n", field.name()); // check for inverted yes/no dispswitch order else if (strindex == INPUT_STRING_Yes && next_strindex == INPUT_STRING_No) osd_printf_error("%s option must have Yes/No options in the order: No, Yes\n", field.name()); // check for inverted upright/cocktail dispswitch order else if (strindex == INPUT_STRING_Cocktail && next_strindex == INPUT_STRING_Upright) osd_printf_error("%s option must have Upright/Cocktail options in the order: Upright, Cocktail\n", field.name()); // check for proper coin ordering else if (strindex >= __input_string_coinage_start && strindex <= __input_string_coinage_end && next_strindex >= __input_string_coinage_start && next_strindex <= __input_string_coinage_end && strindex >= next_strindex && setting.condition() == setting.next()->condition()) { osd_printf_error("%s option has unsorted coinage %s > %s\n", field.name(), setting.name(), setting.next()->name()); coin_error = true; } } } // if we have a coin error, demonstrate the correct way if (coin_error) { output_via_delegate(OSD_OUTPUT_CHANNEL_ERROR, " Note proper coin sort order should be:\n"); for (int entry = 0; entry < ARRAY_LENGTH(coin_list); entry++) if (coin_list[entry]) output_via_delegate(OSD_OUTPUT_CHANNEL_ERROR, " %s\n", ioport_string_from_index(__input_string_coinage_start + entry)); } } //------------------------------------------------- // validate_condition - validate a condition // stored within an ioport field or setting //------------------------------------------------- void validity_checker::validate_condition(ioport_condition &condition, device_t &device, std::unordered_set &port_map) { // resolve the tag // then find a matching port if (port_map.find(device.subtag(condition.tag())) == port_map.end()) osd_printf_error("Condition referencing non-existent ioport tag '%s'\n", condition.tag()); } //------------------------------------------------- // validate_inputs - validate input configuration //------------------------------------------------- void validity_checker::validate_inputs() { std::unordered_set port_map; // iterate over devices for (device_t &device : device_iterator(m_current_config->root_device())) { // see if this device has ports; if not continue if (device.input_ports() == nullptr) continue; // track the current device m_current_device = &device; // allocate the input ports ioport_list portlist; std::string errorbuf; portlist.append(device, errorbuf); // report any errors during construction if (!errorbuf.empty()) osd_printf_error("I/O port error during construction:\n%s\n", errorbuf.c_str()); // do a first pass over ports to add their names and find duplicates for (ioport_port &port : portlist) if (!port_map.insert(port.tag()).second) osd_printf_error("Multiple I/O ports with the same tag '%s' defined\n", port.tag()); // iterate over ports for (ioport_port &port : portlist) { m_current_ioport = port.tag(); // iterate through the fields on this port for (ioport_field &field : port.fields()) { // verify analog inputs if (field.is_analog()) validate_analog_input_field(field); // look for invalid (0) types which should be mapped to IPT_OTHER if (field.type() == IPT_INVALID) osd_printf_error("Field has an invalid type (0); use IPT_OTHER instead\n"); // verify dip switches if (field.type() == IPT_DIPSWITCH) { // dip switch fields must have a specific name if (field.specific_name() == nullptr) osd_printf_error("DIP switch has no specific name\n"); // verify the settings list validate_dip_settings(field); } // verify config settings if (field.type() == IPT_CONFIG) { // config fields must have a specific name if (field.specific_name() == nullptr) osd_printf_error("Config switch has no specific name\n"); } // verify names const char *name = field.specific_name(); if (name != nullptr) { // check for empty string if (name[0] == 0) osd_printf_error("Field name is an empty string\n"); // check for trailing spaces if (name[0] != 0 && name[strlen(name) - 1] == ' ') osd_printf_error("Field '%s' has trailing spaces\n", name); // check for invalid UTF-8 if (!utf8_is_valid_string(name)) osd_printf_error("Field '%s' has invalid characters\n", name); // look up the string and print an error if default strings are not used /*strindex =get_defstr_index(defstr_map, name, driver, &error);*/ } // verify conditions on the field if (!field.condition().none()) validate_condition(field.condition(), device, port_map); // verify conditions on the settings for (ioport_setting &setting : field.settings()) if (!setting.condition().none()) validate_condition(setting.condition(), device, port_map); } // done with this port m_current_ioport = nullptr; } // done with this device m_current_device = nullptr; } } //------------------------------------------------- // validate_devices - run per-device validity // checks //------------------------------------------------- void validity_checker::validate_devices() { std::unordered_set device_map; for (device_t &device : device_iterator(m_current_config->root_device())) { // track the current device m_current_device = &device; // validate auto-finders device.findit(true); // validate the device tag validate_tag(device.basetag()); // look for duplicates if (!device_map.insert(device.tag()).second) osd_printf_error("Multiple devices with the same tag '%s' defined\n", device.tag()); // all devices must have a shortname if (strcmp(device.shortname(), "") == 0) osd_printf_error("Device does not have short name defined\n"); // all devices must have a source file defined if (strcmp(device.source(), "") == 0) osd_printf_error("Device does not have source file location defined\n"); // check for device-specific validity check device.validity_check(*this); // done with this device m_current_device = nullptr; } // if device is slot cart device, we must have a shortname std::unordered_set slot_device_map; for (const device_slot_interface &slot : slot_interface_iterator(m_current_config->root_device())) { for (const device_slot_option &option : slot.option_list()) { std::string temptag("_"); temptag.append(option.name()); device_t *dev = const_cast(*m_current_config).device_add(&m_current_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 (strcmp(dev->shortname(), "") == 0) { if (slot_device_map.insert(dev->name()).second) osd_printf_error("Device '%s' is slot cart device but does not have short name defined\n", dev->name()); } const_cast(*m_current_config).device_remove(&m_current_config->root_device(), temptag.c_str()); } } } //------------------------------------------------- // build_output_prefix - create a prefix // indicating the current source file, driver, // and device //------------------------------------------------- void validity_checker::build_output_prefix(std::string &str) { // start empty str.clear(); // if we have a current (non-root) device, indicate that if (m_current_device != nullptr && m_current_device->owner() != nullptr) str.append(m_current_device->name()).append(" device '").append(m_current_device->tag()+1).append("': "); // if we have a current port, indicate that as well if (m_current_ioport != nullptr) str.append("ioport '").append(m_current_ioport).append("': "); } //------------------------------------------------- // error_output - error message output override //------------------------------------------------- void validity_checker::output_callback(osd_output_channel channel, const char *msg, va_list args) { std::string output; switch (channel) { case OSD_OUTPUT_CHANNEL_ERROR: // count the error m_errors++; // output the source(driver) device 'tag' build_output_prefix(output); // generate the string strcatvprintf(output, msg, args); m_error_text.append(output); break; case OSD_OUTPUT_CHANNEL_WARNING: // count the error m_warnings++; // output the source(driver) device 'tag' build_output_prefix(output); // generate the string and output to the original target strcatvprintf(output, msg, args); m_warning_text.append(output); break; case OSD_OUTPUT_CHANNEL_VERBOSE: // if we're not verbose, skip it if (!m_print_verbose) break; // output the source(driver) device 'tag' build_output_prefix(output); // generate the string and output to the original target strcatvprintf(output, msg, args); m_verbose_text.append(output); break; default: chain_output(channel, msg, args); break; } } //------------------------------------------------- // output_via_delegate - helper to output a // message via a varargs string, so the argptr // can be forwarded onto the given delegate //------------------------------------------------- void validity_checker::output_via_delegate(osd_output_channel channel, const char *format, ...) { va_list argptr; // call through to the delegate with the proper parameters va_start(argptr, format); this->chain_output(channel, format, argptr); va_end(argptr); } //------------------------------------------------- // output_indented_errors - helper to output error // and warning messages with header and indents //------------------------------------------------- void validity_checker::output_indented_errors(std::string &text, const char *header) { // remove trailing newline if (text[text.size()-1] == '\n') text.erase(text.size()-1, 1); strreplace(text, "\n", "\n "); output_via_delegate(OSD_OUTPUT_CHANNEL_ERROR, "%s:\n %s\n", header, text.c_str()); }