// license:BSD-3-Clause // copyright-holders:Nicola Salmoria,Paul Priest,Aaron Giles,Vas Crabb /********************************************************************* romload.cpp ROM loading functions. *********************************************************************/ #include "emu.h" #include "romload.h" #include "emuopts.h" #include "drivenum.h" #include "softlist_dev.h" #include "ui/uimain.h" #include #include #define LOG_LOAD 0 #define LOG(...) do { if (LOG_LOAD) debugload(__VA_ARGS__); } while(0) /*************************************************************************** CONSTANTS ***************************************************************************/ #define TEMPBUFFER_MAX_SIZE (1024 * 1024 * 1024) /*************************************************************************** HELPERS ****************************************************************************/ namespace { auto next_parent_system(game_driver const &system) { return [sys = &system, roms = std::vector()] () mutable -> rom_entry const * { if (!sys) return nullptr; int const parent(driver_list::find(sys->parent)); if (0 > parent) { sys = nullptr; return nullptr; } else { sys = &driver_list::driver(parent); roms = rom_build_entries(sys->rom); return &roms[0]; } }; } auto next_parent_software(std::vector const &parents) { auto part(parents.front()->parts().end()); return [&parents, current = parents.cbegin(), part, end = part] () mutable -> const rom_entry * { if (part == end) { if (parents.end() == current) return nullptr; part = (*current)->parts().cbegin(); end = (*current)->parts().cend(); do { ++current; } while ((parents.cend() != current) && (*current)->parts().empty()); } return &(*part++).romdata()[0]; }; } std::vector make_software_searchpath(software_list_device &swlist, software_info const &swinfo, std::vector &parents) { std::vector result; // search // following parents for (software_info const *i = &swinfo; i; ) { if (std::find(parents.begin(), parents.end(), i) != parents.end()) break; parents.emplace_back(i); result.emplace_back(util::string_format("%s" PATH_SEPARATOR "%s", swlist.list_name(), i->shortname())); i = i->parentname().empty() ? nullptr : swlist.find(i->parentname()); } // search / following parents for (software_info const *i : parents) result.emplace_back(i->shortname()); return result; } chd_error do_open_disk(const emu_options &options, std::initializer_list > > searchpath, const rom_entry *romp, chd_file &chd, std::function next_parent) { // hashes are fixed, but we might need to try multiple filenames std::set tried; const util::hash_collection hashes(ROM_GETHASHDATA(romp)); std::string filename, fullpath; const rom_entry *parent(nullptr); chd_error result(CHDERR_FILE_NOT_FOUND); while (romp && (CHDERR_NONE != result)) { filename = ROM_GETNAME(romp); filename.append(".chd"); if (tried.insert(filename).second) { // piggyback on emu_file to find the disk image file std::unique_ptr imgfile; for (const std::vector &paths : searchpath) { imgfile.reset(new emu_file(options.media_path(), paths, OPEN_FLAG_READ)); imgfile->set_restrict_to_mediapath(1); const osd_file::error filerr(imgfile->open(filename, OPEN_FLAG_READ)); if (osd_file::error::NONE == filerr) break; else imgfile.reset(); } // if we couldn't open a candidate file, report an error; otherwise reopen it as a CHD if (imgfile) { fullpath = imgfile->fullpath(); imgfile.reset(); result = chd.open(fullpath.c_str()); } } // walk the parents looking for a CHD with the same hashes but a different name if (CHDERR_NONE != result) { while (romp) { // find a file in a disk region if (parent) romp = rom_next_file(romp); while (!parent || !romp) { if (!parent) { parent = next_parent(); if (!parent) { romp = nullptr; break; } while (ROMENTRY_ISPARAMETER(parent) || ROMENTRY_ISSYSTEM_BIOS(parent) || ROMENTRY_ISDEFAULT_BIOS(parent)) ++parent; if (ROMENTRY_ISEND(parent)) parent = nullptr; } else { parent = rom_next_region(parent); } while (parent && !ROMREGION_ISDISKDATA(parent)) parent = rom_next_region(parent); if (parent) romp = rom_first_file(parent); } // try it if it matches the hashes if (romp && (util::hash_collection(ROM_GETHASHDATA(romp)) == hashes)) break; } } } return result; } } // anonymous namespace /*************************************************************************** ROM LOADING ***************************************************************************/ /*------------------------------------------------- rom_first_region - return pointer to first ROM region -------------------------------------------------*/ const rom_entry *rom_first_region(const device_t &device) { return rom_first_region(&device.rom_region_vector().front()); } const rom_entry *rom_first_region(const rom_entry *romp) { while (ROMENTRY_ISPARAMETER(romp) || ROMENTRY_ISSYSTEM_BIOS(romp) || ROMENTRY_ISDEFAULT_BIOS(romp)) romp++; return !ROMENTRY_ISEND(romp) ? romp : nullptr; } /*------------------------------------------------- rom_next_region - return pointer to next ROM region -------------------------------------------------*/ const rom_entry *rom_next_region(const rom_entry *romp) { romp++; while (!ROMENTRY_ISREGIONEND(romp)) romp++; while (ROMENTRY_ISPARAMETER(romp)) romp++; return ROMENTRY_ISEND(romp) ? nullptr : romp; } /*------------------------------------------------- rom_first_file - return pointer to first ROM file -------------------------------------------------*/ const rom_entry *rom_first_file(const rom_entry *romp) { romp++; while (!ROMENTRY_ISFILE(romp) && !ROMENTRY_ISREGIONEND(romp)) romp++; return ROMENTRY_ISREGIONEND(romp) ? nullptr : romp; } /*------------------------------------------------- rom_next_file - return pointer to next ROM file -------------------------------------------------*/ const rom_entry *rom_next_file(const rom_entry *romp) { romp++; while (!ROMENTRY_ISFILE(romp) && !ROMENTRY_ISREGIONEND(romp)) romp++; return ROMENTRY_ISREGIONEND(romp) ? nullptr : romp; } /*------------------------------------------------- rom_first_parameter - return pointer to the first per-game parameter -------------------------------------------------*/ const rom_entry *rom_first_parameter(const device_t &device) { const rom_entry *romp = &device.rom_region_vector().front(); while (romp && !ROMENTRY_ISEND(romp) && !ROMENTRY_ISPARAMETER(romp)) romp++; return (romp != nullptr && !ROMENTRY_ISEND(romp)) ? romp : nullptr; } /*------------------------------------------------- rom_next_parameter - return pointer to the next per-game parameter -------------------------------------------------*/ const rom_entry *rom_next_parameter(const rom_entry *romp) { romp++; while (!ROMENTRY_ISREGIONEND(romp) && !ROMENTRY_ISPARAMETER(romp)) romp++; return ROMENTRY_ISEND(romp) ? nullptr : romp; } /*------------------------------------------------- rom_file_size - return the expected size of a file given the ROM description -------------------------------------------------*/ u32 rom_file_size(const rom_entry *romp) { u32 maxlength = 0; /* loop until we run out of reloads */ do { /* loop until we run out of continues/ignores */ u32 curlength = ROM_GETLENGTH(romp++); while (ROMENTRY_ISCONTINUE(romp) || ROMENTRY_ISIGNORE(romp)) curlength += ROM_GETLENGTH(romp++); /* track the maximum length */ maxlength = std::max(maxlength, curlength); } while (ROMENTRY_ISRELOAD(romp)); return maxlength; } /*------------------------------------------------- debugload - log data to a file -------------------------------------------------*/ static void CLIB_DECL ATTR_PRINTF(1,2) debugload(const char *string, ...) { static int opened; va_list arg; FILE *f; f = fopen("romload.log", opened++ ? "a" : "w"); if (f) { va_start(arg, string); vfprintf(f, string, arg); va_end(arg); fclose(f); } } /*************************************************************************** HARD DISK HANDLING ***************************************************************************/ /*------------------------------------------------- get_disk_handle - return a pointer to the CHD file associated with the given region -------------------------------------------------*/ chd_file *rom_load_manager::get_disk_handle(const char *region) { for (auto &curdisk : m_chd_list) if (strcmp(curdisk->region(), region) == 0) return &curdisk->chd(); return nullptr; } /*------------------------------------------------- set_disk_handle - set a pointer to the CHD file associated with the given region -------------------------------------------------*/ int rom_load_manager::set_disk_handle(const char *region, const char *fullpath) { auto chd = std::make_unique(region); auto err = chd->orig_chd().open(fullpath); if (err == CHDERR_NONE) m_chd_list.push_back(std::move(chd)); return err; } /*------------------------------------------------- determine_bios_rom - determine system_bios from SystemBios structure and OPTION_BIOS -------------------------------------------------*/ void rom_load_manager::determine_bios_rom(device_t &device, const char *specbios) { // default is applied by the device at config complete time if (specbios && *specbios && core_stricmp(specbios, "default")) { bool found(false); for (const rom_entry &rom : device.rom_region_vector()) { if (ROMENTRY_ISSYSTEM_BIOS(&rom)) { char const *const biosname = ROM_GETNAME(&rom); int const bios_flags = ROM_GETBIOSFLAGS(&rom); char bios_number[20]; // Allow '-bios n' to still be used sprintf(bios_number, "%d", bios_flags - 1); if (!core_stricmp(bios_number, specbios) || !core_stricmp(biosname, specbios)) { found = true; device.set_system_bios(bios_flags); break; } } } // if we got neither an empty string nor 'default' then warn the user if (!found) { m_errorstring.append(util::string_format("%s: invalid BIOS \"%s\", reverting to default\n", device.tag(), specbios)); m_warnings++; } } // log final result LOG("For \"%s\" using System BIOS: %d\n", device.tag(), device.system_bios()); } /*------------------------------------------------- count_roms - counts the total number of ROMs that will need to be loaded -------------------------------------------------*/ void rom_load_manager::count_roms() { const rom_entry *region, *rom; /* start with 0 */ m_romstotal = 0; m_romstotalsize = 0; /* loop over regions, then over files */ for (device_t &device : device_iterator(machine().config().root_device())) for (region = rom_first_region(device); region != nullptr; region = rom_next_region(region)) for (rom = rom_first_file(region); rom != nullptr; rom = rom_next_file(rom)) if (ROM_GETBIOSFLAGS(rom) == 0 || ROM_GETBIOSFLAGS(rom) == device.system_bios()) { m_romstotal++; m_romstotalsize += rom_file_size(rom); } } /*------------------------------------------------- fill_random - fills an area of memory with random data -------------------------------------------------*/ void rom_load_manager::fill_random(u8 *base, u32 length) { while (length--) *base++ = machine().rand(); } /*------------------------------------------------- handle_missing_file - handles error generation for missing files -------------------------------------------------*/ void rom_load_manager::handle_missing_file(const rom_entry *romp, const std::vector &tried_file_names, chd_error chderr) { std::string tried; if (!tried_file_names.empty()) { tried = " (tried in"; for (const std::string &path : tried_file_names) { tried += ' '; tried += path; } tried += ')'; } std::string name(ROM_GETNAME(romp)); const bool is_chd(chderr != CHDERR_NONE); if (is_chd) name += ".chd"; const bool is_chd_error(is_chd && chderr != CHDERR_FILE_NOT_FOUND); if (is_chd_error) m_errorstring.append(string_format("%s CHD ERROR: %s\n", name.c_str(), chd_file::error_string(chderr))); if (ROM_ISOPTIONAL(romp)) { // optional files are okay if (!is_chd_error) m_errorstring.append(string_format("OPTIONAL %s NOT FOUND%s\n", name, tried)); m_warnings++; } else if (util::hash_collection(ROM_GETHASHDATA(romp)).flag(util::hash_collection::FLAG_NO_DUMP)) { // no good dumps are okay if (!is_chd_error) m_errorstring.append(string_format("%s NOT FOUND (NO GOOD DUMP KNOWN)%s\n", name, tried)); m_knownbad++; } else { // anything else is bad if (!is_chd_error) m_errorstring.append(string_format("%s NOT FOUND%s\n", name, tried)); m_errors++; } } /*------------------------------------------------- dump_wrong_and_correct_checksums - dump an error message containing the wrong and the correct checksums for a given ROM -------------------------------------------------*/ void rom_load_manager::dump_wrong_and_correct_checksums(const util::hash_collection &hashes, const util::hash_collection &acthashes) { m_errorstring.append(string_format(" EXPECTED: %s\n", hashes.macro_string().c_str())); m_errorstring.append(string_format(" FOUND: %s\n", acthashes.macro_string().c_str())); } /*------------------------------------------------- verify_length_and_hash - verify the length and hash signatures of a file -------------------------------------------------*/ void rom_load_manager::verify_length_and_hash(emu_file *file, const char *name, u32 explength, const util::hash_collection &hashes) { // we've already complained if there is no file if (!file) return; // verify length u64 const actlength = file->size(); if (explength != actlength) { m_errorstring.append(string_format("%s WRONG LENGTH (expected: %08x found: %08x)\n", name, explength, actlength)); m_warnings++; } if (hashes.flag(util::hash_collection::FLAG_NO_DUMP)) { // If there is no good dump known, write it m_errorstring.append(string_format("%s NO GOOD DUMP KNOWN\n", name)); m_knownbad++; } else { // verify checksums util::hash_collection const &acthashes = file->hashes(hashes.hash_types().c_str()); if (hashes != acthashes) { // otherwise, it's just bad util::hash_collection const &all_acthashes = (acthashes.hash_types() == util::hash_collection::HASH_TYPES_ALL) ? acthashes : file->hashes(util::hash_collection::HASH_TYPES_ALL); m_errorstring.append(string_format("%s WRONG CHECKSUMS:\n", name)); dump_wrong_and_correct_checksums(hashes, all_acthashes); m_warnings++; } else if (hashes.flag(util::hash_collection::FLAG_BAD_DUMP)) { // If it matches, but it is actually a bad dump, write it m_errorstring.append(string_format("%s ROM NEEDS REDUMP\n", name)); m_knownbad++; } } } /*------------------------------------------------- display_loading_rom_message - display messages about ROM loading to the user -------------------------------------------------*/ void rom_load_manager::display_loading_rom_message(const char *name, bool from_list) { std::string buffer; if (name) buffer = util::string_format("%s (%d%%)", from_list ? "Loading Software" : "Loading Machine", u32(100 * m_romsloadedsize / m_romstotalsize)); else buffer = "Loading Complete"; if (!machine().ui().is_menu_active()) machine().ui().set_startup_text(buffer.c_str(), false); } /*------------------------------------------------- display_rom_load_results - display the final results of ROM loading -------------------------------------------------*/ void rom_load_manager::display_rom_load_results(bool from_list) { /* final status display */ display_loading_rom_message(nullptr, from_list); /* if we had errors, they are fatal */ if (m_errors != 0) { /* create the error message and exit fatally */ osd_printf_error("%s", m_errorstring); throw emu_fatalerror(EMU_ERR_MISSING_FILES, "Required files are missing, the machine cannot be run."); } /* if we had warnings, output them, but continue */ if ((m_warnings) || (m_knownbad)) { m_errorstring.append("WARNING: the machine might not run correctly."); osd_printf_warning("%s\n", m_errorstring); } } /*------------------------------------------------- region_post_process - post-process a region, byte swapping and inverting data as necessary -------------------------------------------------*/ void rom_load_manager::region_post_process(memory_region *region, bool invert) { // do nothing if no region if (region == nullptr) return; LOG("+ datawidth=%dbit endian=%s\n", region->bitwidth(), region->endianness() == ENDIANNESS_LITTLE ? "little" : "big"); /* if the region is inverted, do that now */ if (invert) { LOG("+ Inverting region\n"); u8 *base = region->base(); for (int i = 0; i < region->bytes(); i++) *base++ ^= 0xff; } /* swap the endianness if we need to */ if (region->bytewidth() > 1 && region->endianness() != ENDIANNESS_NATIVE) { LOG("+ Byte swapping region\n"); int datawidth = region->bytewidth(); u8 *base = region->base(); for (int i = 0; i < region->bytes(); i += datawidth) { u8 temp[8]; memcpy(temp, base, datawidth); for (int j = datawidth - 1; j >= 0; j--) *base++ = temp[j]; } } } /*------------------------------------------------- open_rom_file - open a ROM file, searching up the parent and loading by checksum -------------------------------------------------*/ std::unique_ptr rom_load_manager::open_rom_file(std::initializer_list > > searchpath, const rom_entry *romp, std::vector &tried_file_names, bool from_list) { osd_file::error filerr = osd_file::error::NOT_FOUND; u32 const romsize = rom_file_size(romp); tried_file_names.clear(); // update status display display_loading_rom_message(ROM_GETNAME(romp), from_list); // extract CRC to use for searching u32 crc = 0; bool const has_crc = util::hash_collection(ROM_GETHASHDATA(romp)).crc(crc); // attempt reading up the chain through the parents // it also automatically attempts any kind of load by checksum supported by the archives. std::unique_ptr result; for (const std::vector &paths : searchpath) { result = open_rom_file(paths, tried_file_names, has_crc, crc, ROM_GETNAME(romp), filerr); if (result) break; } // update counters m_romsloaded++; m_romsloadedsize += romsize; // return the result if (osd_file::error::NONE != filerr) return nullptr; else return result; } std::unique_ptr rom_load_manager::open_rom_file(const std::vector &paths, std::vector &tried, bool has_crc, u32 crc, const std::string &name, osd_file::error &filerr) { // record the set names we search tried.insert(tried.end(), paths.begin(), paths.end()); // attempt to open the file std::unique_ptr result(new emu_file(machine().options().media_path(), paths, OPEN_FLAG_READ)); result->set_restrict_to_mediapath(1); if (has_crc) filerr = result->open(name, crc); else filerr = result->open(name); // don't return anything if unsuccessful if (osd_file::error::NONE != filerr) return nullptr; else return result; } /*------------------------------------------------- rom_fread - cheesy fread that fills with random data for a nullptr file -------------------------------------------------*/ int rom_load_manager::rom_fread(emu_file *file, u8 *buffer, int length, const rom_entry *parent_region) { if (file) // files just pass through return file->read(buffer, length); if (!ROMREGION_ISERASE(parent_region)) // otherwise, fill with randomness unless it was already specifically erased fill_random(buffer, length); return length; } /*------------------------------------------------- read_rom_data - read ROM data for a single entry -------------------------------------------------*/ int rom_load_manager::read_rom_data(emu_file *file, const rom_entry *parent_region, const rom_entry *romp) { int datashift = ROM_GETBITSHIFT(romp); int datamask = ((1 << ROM_GETBITWIDTH(romp)) - 1) << datashift; int numbytes = ROM_GETLENGTH(romp); int groupsize = ROM_GETGROUPSIZE(romp); int skip = ROM_GETSKIPCOUNT(romp); int reversed = ROM_ISREVERSED(romp); int numgroups = (numbytes + groupsize - 1) / groupsize; u8 *base = m_region->base() + ROM_GETOFFSET(romp); u32 tempbufsize; int i; LOG("Loading ROM data: offs=%X len=%X mask=%02X group=%d skip=%d reverse=%d\n", ROM_GETOFFSET(romp), numbytes, datamask, groupsize, skip, reversed); /* make sure the length was an even multiple of the group size */ if (numbytes % groupsize != 0) osd_printf_warning("Warning in RomModule definition: %s length not an even multiple of group size\n", ROM_GETNAME(romp)); /* make sure we only fill within the region space */ if (ROM_GETOFFSET(romp) + numgroups * groupsize + (numgroups - 1) * skip > m_region->bytes()) throw emu_fatalerror("Error in RomModule definition: %s out of memory region space\n", ROM_GETNAME(romp)); /* make sure the length was valid */ if (numbytes == 0) throw emu_fatalerror("Error in RomModule definition: %s has an invalid length\n", ROM_GETNAME(romp)); /* special case for simple loads */ if (datamask == 0xff && (groupsize == 1 || !reversed) && skip == 0) return rom_fread(file, base, numbytes, parent_region); /* use a temporary buffer for complex loads */ tempbufsize = std::min(TEMPBUFFER_MAX_SIZE, numbytes); std::vector tempbuf(tempbufsize); /* chunky reads for complex loads */ skip += groupsize; while (numbytes > 0) { int evengroupcount = (tempbufsize / groupsize) * groupsize; int bytesleft = (numbytes > evengroupcount) ? evengroupcount : numbytes; u8 *bufptr = &tempbuf[0]; /* read as much as we can */ LOG(" Reading %X bytes into buffer\n", bytesleft); if (rom_fread(file, bufptr, bytesleft, parent_region) != bytesleft) return 0; numbytes -= bytesleft; LOG(" Copying to %p\n", base); /* unmasked cases */ if (datamask == 0xff) { /* non-grouped data */ if (groupsize == 1) for (i = 0; i < bytesleft; i++, base += skip) *base = *bufptr++; /* grouped data -- non-reversed case */ else if (!reversed) while (bytesleft) { for (i = 0; i < groupsize && bytesleft; i++, bytesleft--) base[i] = *bufptr++; base += skip; } /* grouped data -- reversed case */ else while (bytesleft) { for (i = groupsize - 1; i >= 0 && bytesleft; i--, bytesleft--) base[i] = *bufptr++; base += skip; } } /* masked cases */ else { /* non-grouped data */ if (groupsize == 1) for (i = 0; i < bytesleft; i++, base += skip) *base = (*base & ~datamask) | ((*bufptr++ << datashift) & datamask); /* grouped data -- non-reversed case */ else if (!reversed) while (bytesleft) { for (i = 0; i < groupsize && bytesleft; i++, bytesleft--) base[i] = (base[i] & ~datamask) | ((*bufptr++ << datashift) & datamask); base += skip; } /* grouped data -- reversed case */ else while (bytesleft) { for (i = groupsize - 1; i >= 0 && bytesleft; i--, bytesleft--) base[i] = (base[i] & ~datamask) | ((*bufptr++ << datashift) & datamask); base += skip; } } } LOG(" All done\n"); return ROM_GETLENGTH(romp); } /*------------------------------------------------- fill_rom_data - fill a region of ROM space -------------------------------------------------*/ void rom_load_manager::fill_rom_data(const rom_entry *romp) { u32 numbytes = ROM_GETLENGTH(romp); int skip = ROM_GETSKIPCOUNT(romp); u8 *base = m_region->base() + ROM_GETOFFSET(romp); // make sure we fill within the region space if (ROM_GETOFFSET(romp) + numbytes > m_region->bytes()) throw emu_fatalerror("Error in RomModule definition: FILL out of memory region space\n"); // make sure the length was valid if (numbytes == 0) throw emu_fatalerror("Error in RomModule definition: FILL has an invalid length\n"); // for fill bytes, the byte that gets filled is the first byte of the hashdata string u8 fill_byte = u8(strtol(ROM_GETHASHDATA(romp), nullptr, 0)); // fill the data (filling value is stored in place of the hashdata) if(skip != 0) { for (int i = 0; i < numbytes; i+= skip + 1) base[i] = fill_byte; } else memset(base, fill_byte, numbytes); } /*------------------------------------------------- copy_rom_data - copy a region of ROM space -------------------------------------------------*/ void rom_load_manager::copy_rom_data(const rom_entry *romp) { u8 *base = m_region->base() + ROM_GETOFFSET(romp); const char *srcrgntag = ROM_GETNAME(romp); u32 numbytes = ROM_GETLENGTH(romp); u32 srcoffs = u32(strtol(ROM_GETHASHDATA(romp), nullptr, 0)); /* srcoffset in place of hashdata */ /* make sure we copy within the region space */ if (ROM_GETOFFSET(romp) + numbytes > m_region->bytes()) throw emu_fatalerror("Error in RomModule definition: COPY out of target memory region space\n"); /* make sure the length was valid */ if (numbytes == 0) throw emu_fatalerror("Error in RomModule definition: COPY has an invalid length\n"); /* make sure the source was valid */ memory_region *region = machine().root_device().memregion(srcrgntag); if (region == nullptr) throw emu_fatalerror("Error in RomModule definition: COPY from an invalid region\n"); /* make sure we find within the region space */ if (srcoffs + numbytes > region->bytes()) throw emu_fatalerror("Error in RomModule definition: COPY out of source memory region space\n"); /* fill the data */ memcpy(base, region->base() + srcoffs, numbytes); } /*------------------------------------------------- process_rom_entries - process all ROM entries for a region -------------------------------------------------*/ void rom_load_manager::process_rom_entries(std::initializer_list > > searchpath, u8 bios, const rom_entry *parent_region, const rom_entry *romp, bool from_list) { u32 lastflags = 0; std::vector tried_file_names; // loop until we hit the end of this region while (!ROMENTRY_ISREGIONEND(romp)) { tried_file_names.clear(); if (ROMENTRY_ISCONTINUE(romp)) throw emu_fatalerror("Error in RomModule definition: ROM_CONTINUE not preceded by ROM_LOAD\n"); if (ROMENTRY_ISIGNORE(romp)) throw emu_fatalerror("Error in RomModule definition: ROM_IGNORE not preceded by ROM_LOAD\n"); if (ROMENTRY_ISRELOAD(romp)) throw emu_fatalerror("Error in RomModule definition: ROM_RELOAD not preceded by ROM_LOAD\n"); if (ROMENTRY_ISFILL(romp)) { if (!ROM_GETBIOSFLAGS(romp) || (ROM_GETBIOSFLAGS(romp) == bios)) fill_rom_data(romp); romp++; } else if (ROMENTRY_ISCOPY(romp)) { copy_rom_data(romp++); } else if (ROMENTRY_ISFILE(romp)) { // handle files bool const irrelevantbios = (ROM_GETBIOSFLAGS(romp) != 0) && (ROM_GETBIOSFLAGS(romp) != bios); rom_entry const *baserom = romp; int explength = 0; // open the file if it is a non-BIOS or matches the current BIOS LOG("Opening ROM file: %s\n", ROM_GETNAME(romp)); std::unique_ptr file; if (!irrelevantbios) { file = open_rom_file(searchpath, romp, tried_file_names, from_list); if (!file) handle_missing_file(romp, tried_file_names, CHDERR_NONE); } // loop until we run out of reloads do { // loop until we run out of continues/ignores do { rom_entry modified_romp = *romp++; //int readresult; // handle flag inheritance if (!ROM_INHERITSFLAGS(&modified_romp)) lastflags = modified_romp.get_flags(); else modified_romp.set_flags((modified_romp.get_flags() & ~ROM_INHERITEDFLAGS) | lastflags); explength += ROM_GETLENGTH(&modified_romp); // attempt to read using the modified entry if (!ROMENTRY_ISIGNORE(&modified_romp) && !irrelevantbios) /*readresult = */read_rom_data(file.get(), parent_region, &modified_romp); } while (ROMENTRY_ISCONTINUE(romp) || ROMENTRY_ISIGNORE(romp)); // if this was the first use of this file, verify the length and CRC if (baserom) { LOG("Verifying length (%X) and checksums\n", explength); verify_length_and_hash(file.get(), ROM_GETNAME(baserom), explength, util::hash_collection(ROM_GETHASHDATA(baserom))); LOG("Verify finished\n"); } // re-seek to the start and clear the baserom so we don't reverify if (file) file->seek(0, SEEK_SET); baserom = nullptr; explength = 0; } while (ROMENTRY_ISRELOAD(romp)); // close the file if (file) { LOG("Closing ROM file\n"); file.reset(); } } else { romp++; // something else - skip } } } /*------------------------------------------------- open_disk_diff - open a DISK diff file -------------------------------------------------*/ chd_error rom_load_manager::open_disk_diff(emu_options &options, const rom_entry *romp, chd_file &source, chd_file &diff_chd) { // TODO: use system name and/or software list name in the path - the current setup doesn't scale std::string fname = std::string(ROM_GETNAME(romp)).append(".dif"); /* try to open the diff */ LOG("Opening differencing image file: %s\n", fname.c_str()); emu_file diff_file(options.diff_directory(), OPEN_FLAG_READ | OPEN_FLAG_WRITE); osd_file::error filerr = diff_file.open(fname); if (filerr == osd_file::error::NONE) { std::string fullpath(diff_file.fullpath()); diff_file.close(); LOG("Opening differencing image file: %s\n", fullpath.c_str()); return diff_chd.open(fullpath.c_str(), true, &source); } /* didn't work; try creating it instead */ LOG("Creating differencing image: %s\n", fname.c_str()); diff_file.set_openflags(OPEN_FLAG_READ | OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS); filerr = diff_file.open(fname); if (filerr == osd_file::error::NONE) { std::string fullpath(diff_file.fullpath()); diff_file.close(); /* create the CHD */ LOG("Creating differencing image file: %s\n", fullpath.c_str()); chd_codec_type compression[4] = { CHD_CODEC_NONE }; chd_error err = diff_chd.create(fullpath.c_str(), source.logical_bytes(), source.hunk_bytes(), compression, source); if (err != CHDERR_NONE) return err; return diff_chd.clone_all_metadata(source); } return CHDERR_FILE_NOT_FOUND; } /*------------------------------------------------- process_disk_entries - process all disk entries for a region -------------------------------------------------*/ void rom_load_manager::process_disk_entries(std::initializer_list > > searchpath, const char *regiontag, const rom_entry *romp, std::function next_parent) { /* remove existing disk entries for this region */ m_chd_list.erase(std::remove_if(m_chd_list.begin(), m_chd_list.end(), [regiontag] (std::unique_ptr &chd) { return !strcmp(chd->region(), regiontag); }), m_chd_list.end()); /* loop until we hit the end of this region */ for ( ; !ROMENTRY_ISREGIONEND(romp); romp++) { /* handle files */ if (ROMENTRY_ISFILE(romp)) { auto chd = std::make_unique(regiontag); chd_error err; /* make the filename of the source */ const std::string filename = std::string(ROM_GETNAME(romp)).append(".chd"); /* first open the source drive */ // FIXME: we've lost the ability to search parents here LOG("Opening disk image: %s\n", filename.c_str()); err = do_open_disk(machine().options(), searchpath, romp, chd->orig_chd(), next_parent); if (err != CHDERR_NONE) { handle_missing_file(romp, std::vector(), err); chd = nullptr; continue; } /* get the header and extract the SHA1 */ util::hash_collection acthashes; acthashes.add_sha1(chd->orig_chd().sha1()); /* verify the hash */ const util::hash_collection hashes(ROM_GETHASHDATA(romp)); if (hashes != acthashes) { m_errorstring.append(string_format("%s WRONG CHECKSUMS:\n", filename)); dump_wrong_and_correct_checksums(hashes, acthashes); m_warnings++; } else if (hashes.flag(util::hash_collection::FLAG_BAD_DUMP)) { m_errorstring.append(string_format("%s CHD NEEDS REDUMP\n", filename)); m_knownbad++; } /* if not read-only, make the diff file */ if (!DISK_ISREADONLY(romp)) { /* try to open or create the diff */ err = open_disk_diff(machine().options(), romp, chd->orig_chd(), chd->diff_chd()); if (err != CHDERR_NONE) { m_errorstring.append(string_format("%s DIFF CHD ERROR: %s\n", filename, chd_file::error_string(err))); m_errors++; chd = nullptr; continue; } } /* we're okay, add to the list of disks */ LOG("Assigning to handle %d\n", DISK_GETINDEX(romp)); m_chd_list.push_back(std::move(chd)); } } } /*------------------------------------------------- get_software_searchpath - get search path for a software list item -------------------------------------------------*/ std::vector rom_load_manager::get_software_searchpath(software_list_device &swlist, const software_info &swinfo) { std::vector parents; return make_software_searchpath(swlist, swinfo, parents); } /*------------------------------------------------- open_disk_image - open a disk image for a device -------------------------------------------------*/ chd_error rom_load_manager::open_disk_image(const emu_options &options, const device_t &device, const rom_entry *romp, chd_file &image_chd) { const std::vector searchpath(device.searchpath()); driver_device const *const driver(dynamic_cast(&device)); std::function next_parent; if (driver) next_parent = next_parent_system(driver->system()); else next_parent = [] () { return nullptr; }; return do_open_disk(options, { searchpath }, romp, image_chd, std::move(next_parent)); } /*------------------------------------------------- open_disk_image - open a disk image for a software item -------------------------------------------------*/ chd_error rom_load_manager::open_disk_image(const emu_options &options, software_list_device &swlist, const software_info &swinfo, const rom_entry *romp, chd_file &image_chd) { std::vector parents; std::vector searchpath = make_software_searchpath(swlist, swinfo, parents); searchpath.emplace_back(swlist.list_name()); // look for loose disk images in software list directory return do_open_disk(options, { searchpath }, romp, image_chd, next_parent_software(parents)); } /*------------------------------------------------- normalize_flags_for_device - modify the region flags for the given device -------------------------------------------------*/ void rom_load_manager::normalize_flags_for_device(const char *rgntag, u8 &width, endianness_t &endian) { device_t *device = machine().root_device().subdevice(rgntag); device_memory_interface *memory; if (device != nullptr && device->interface(memory)) { const address_space_config *spaceconfig = memory->space_config(); if (spaceconfig != nullptr) { int buswidth; /* set the endianness */ if (spaceconfig->endianness() == ENDIANNESS_LITTLE) endian = ENDIANNESS_LITTLE; else endian = ENDIANNESS_BIG; /* set the width */ buswidth = spaceconfig->data_width(); if (buswidth <= 8) width = 1; else if (buswidth <= 16) width = 2; else if (buswidth <= 32) width = 4; else width = 8; } } } /*------------------------------------------------- load_software_part_region - load a software part This is used by MESS when loading a piece of software. The code should be merged with process_region_list or updated to use a slight more general process_region_list. -------------------------------------------------*/ void rom_load_manager::load_software_part_region(device_t &device, software_list_device &swlist, const char *swname, const rom_entry *start_region) { m_errorstring.clear(); m_softwarningstring.clear(); m_romstotal = 0; m_romstotalsize = 0; m_romsloadedsize = 0; std::vector parents; std::vector swsearch, disksearch, devsearch; const software_info *const swinfo = swlist.find(swname); if (swinfo) { // dispay a warning for unsupported software // TODO: list supported clones like we do for machines? const u32 supported(swinfo->supported()); if (supported == SOFTWARE_SUPPORTED_PARTIAL) { m_errorstring.append(string_format("WARNING: support for software %s (in list %s) is only partial\n", swname, swlist.list_name())); m_softwarningstring.append(string_format("Support for software %s (in list %s) is only partial\n", swname, swlist.list_name())); } if (supported == SOFTWARE_SUPPORTED_NO) { m_errorstring.append(string_format("WARNING: support for software %s (in list %s) is only preliminary\n", swname, swlist.list_name())); m_softwarningstring.append(string_format("Support for software %s (in list %s) is only preliminary\n", swname, swlist.list_name())); } // walk the chain of parents and add them to the search path swsearch = make_software_searchpath(swlist, *swinfo, parents); } else { swsearch.emplace_back(util::string_format("%s" PATH_SEPARATOR "%s", swlist.list_name(), swname)); swsearch.emplace_back(swname); } // this is convenient for CD-only lists so you don't need an extra level of directories containing one file each disksearch.emplace_back(swlist.list_name()); // for historical reasons, add the search path for the software list device's owner const device_t *const listowner = swlist.owner(); if (listowner) devsearch = listowner->searchpath(); // loop until we hit the end std::function next_parent; for (const rom_entry *region = start_region; region != nullptr; region = rom_next_region(region)) { u32 regionlength = ROMREGION_GETLENGTH(region); std::string regiontag = device.subtag(ROMREGION_GETTAG(region)); LOG("Processing region \"%s\" (length=%X)\n", regiontag.c_str(), regionlength); // the first entry must be a region assert(ROMENTRY_ISREGION(region)); // if this is a device region, override with the device width and endianness endianness_t endianness = ROMREGION_ISBIGENDIAN(region) ? ENDIANNESS_BIG : ENDIANNESS_LITTLE; u8 width = ROMREGION_GETWIDTH(region) / 8; memory_region *memregion = machine().root_device().memregion(regiontag); if (memregion != nullptr) { normalize_flags_for_device(regiontag.c_str(), width, endianness); // clear old region (TODO: should be moved to an image unload function) machine().memory().region_free(memregion->name()); } // remember the base and length m_region = machine().memory().region_alloc(regiontag, regionlength, width, endianness); LOG("Allocated %X bytes @ %p\n", m_region->bytes(), m_region->base()); if (ROMREGION_ISERASE(region)) // clear the region if it's requested memset(m_region->base(), ROMREGION_GETERASEVAL(region), m_region->bytes()); else if (m_region->bytes() <= 0x400000) // or if it's sufficiently small (<= 4MB) memset(m_region->base(), 0, m_region->bytes()); #ifdef MAME_DEBUG else // if we're debugging, fill region with random data to catch errors fill_random(m_region->base(), m_region->bytes()); #endif // update total number of roms for (const rom_entry *rom = rom_first_file(region); rom != nullptr; rom = rom_next_file(rom)) { m_romstotal++; m_romstotalsize += rom_file_size(rom); } // now process the entries in the region if (ROMREGION_ISROMDATA(region)) { if (devsearch.empty()) process_rom_entries({ swsearch }, 0U, region, region + 1, true); else process_rom_entries({ swsearch, devsearch }, 0U, region, region + 1, true); } else if (ROMREGION_ISDISKDATA(region)) { if (!next_parent) { if (!parents.empty()) next_parent = next_parent_software(parents); else next_parent = [] () { return nullptr; }; } if (devsearch.empty()) process_disk_entries({ swsearch, disksearch }, regiontag.c_str(), region + 1, next_parent); else process_disk_entries({ swsearch, disksearch, devsearch }, regiontag.c_str(), region + 1, next_parent); } } // now go back and post-process all the regions for (const rom_entry *region = start_region; region != nullptr; region = rom_next_region(region)) region_post_process(device.memregion(ROMREGION_GETTAG(region)), ROMREGION_ISINVERTED(region)); // display the results and exit display_rom_load_results(true); } /*------------------------------------------------- process_region_list - process a region list -------------------------------------------------*/ void rom_load_manager::process_region_list() { // loop until we hit the end device_iterator deviter(machine().root_device()); std::vector searchpath; for (device_t &device : deviter) { searchpath.clear(); std::function next_parent; for (const rom_entry *region = rom_first_region(device); region != nullptr; region = rom_next_region(region)) { u32 regionlength = ROMREGION_GETLENGTH(region); std::string regiontag = device.subtag(ROM_GETNAME(region)); LOG("Processing region \"%s\" (length=%X)\n", regiontag.c_str(), regionlength); // the first entry must be a region assert(ROMENTRY_ISREGION(region)); if (ROMREGION_ISROMDATA(region)) { // if this is a device region, override with the device width and endianness u8 width = ROMREGION_GETWIDTH(region) / 8; endianness_t endianness = ROMREGION_ISBIGENDIAN(region) ? ENDIANNESS_BIG : ENDIANNESS_LITTLE; normalize_flags_for_device(regiontag.c_str(), width, endianness); // remember the base and length m_region = machine().memory().region_alloc(regiontag.c_str(), regionlength, width, endianness); LOG("Allocated %X bytes @ %p\n", m_region->bytes(), m_region->base()); if (ROMREGION_ISERASE(region)) // clear the region if it's requested memset(m_region->base(), ROMREGION_GETERASEVAL(region), m_region->bytes()); else if (m_region->bytes() <= 0x400000) // or if it's sufficiently small (<= 4MB) memset(m_region->base(), 0, m_region->bytes()); #ifdef MAME_DEBUG else // if we're debugging, fill region with random data to catch errors fill_random(m_region->base(), m_region->bytes()); #endif // now process the entries in the region if (searchpath.empty()) searchpath = device.searchpath(); assert(!searchpath.empty()); process_rom_entries({ searchpath }, device.system_bios(), region, region + 1, false); } else if (ROMREGION_ISDISKDATA(region)) { if (searchpath.empty()) searchpath = device.searchpath(); assert(!searchpath.empty()); if (!next_parent) { driver_device const *const driver(dynamic_cast(&device)); if (driver) next_parent = next_parent_system(driver->system()); else next_parent = [] () { return nullptr; }; } process_disk_entries({ searchpath }, regiontag.c_str(), region + 1, next_parent); } } } // now go back and post-process all the regions for (device_t &device : deviter) for (const rom_entry *region = rom_first_region(device); region != nullptr; region = rom_next_region(region)) region_post_process(device.memregion(ROM_GETNAME(region)), ROMREGION_ISINVERTED(region)); // and finally register all per-game parameters for (device_t &device : deviter) { for (const rom_entry *param = rom_first_parameter(device); param != nullptr; param = rom_next_parameter(param)) { std::string regiontag = device.subtag(param->name()); machine().parameters().add(regiontag, param->hashdata()); } } } /*------------------------------------------------- rom_init - load the ROMs and open the disk images associated with the given machine -------------------------------------------------*/ rom_load_manager::rom_load_manager(running_machine &machine) : m_machine(machine) , m_warnings(0) , m_knownbad(0) , m_errors(0) , m_romsloaded(0) , m_romstotal(0) , m_romsloadedsize(0) , m_romstotalsize(0) , m_chd_list() , m_region(nullptr) , m_errorstring() , m_softwarningstring() { // figure out which BIOS we are using std::map card_bios; for (device_t &device : device_iterator(machine.config().root_device())) { device_slot_interface const *const slot(dynamic_cast(&device)); if (slot) { device_t const *const card(slot->get_card_device()); slot_option const &slot_opt(machine.options().slot_option(slot->slot_name())); if (card && !slot_opt.bios().empty()) card_bios.emplace(std::make_pair(std::string(card->tag()), slot_opt.bios())); } if (device.rom_region()) { std::string specbios; if (!device.owner()) { specbios = machine.options().bios(); } else { auto const found(card_bios.find(device.tag())); if (card_bios.end() != found) { specbios = std::move(found->second); card_bios.erase(found); } } determine_bios_rom(device, specbios.c_str()); } } // count the total number of ROMs count_roms(); // reset the disk list m_chd_list.clear(); // process the ROM entries we were passed process_region_list(); // display the results and exit display_rom_load_results(false); } // ------------------------------------------------- // rom_build_entries - builds a rom_entry vector // from a tiny_rom_entry array // ------------------------------------------------- std::vector rom_build_entries(const tiny_rom_entry *tinyentries) { std::vector result; if (tinyentries) { int i = 0; do { result.emplace_back(tinyentries[i]); } while (!ROMENTRY_ISEND(tinyentries[i++])); } else { tiny_rom_entry const end_entry = { nullptr, nullptr, 0, 0, ROMENTRYTYPE_END }; result.emplace_back(end_entry); } return result; }