frontend/mame/audit.cpp: Better heuristics for matching shared ROMs.

This should address at least some of the remaining issues from MT07410
and GitHub #7314.  If this causes issues like MT07756 to reappear, it
will need to be fixed/reverted before release.
This commit is contained in:
Vas Crabb 2020-10-04 12:52:47 +11:00
parent f53eb6022e
commit aca0aaaa3d
4 changed files with 157 additions and 56 deletions

View File

@ -192,7 +192,11 @@ chd_error do_open_disk(const emu_options &options, std::initializer_list<std::re
const rom_entry *rom_first_region(const device_t &device)
{
const rom_entry *romp = &device.rom_region_vector().front();
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;

View File

@ -479,6 +479,7 @@ private:
/* return pointer to the first ROM region within a source */
const rom_entry *rom_first_region(const device_t &device);
const rom_entry *rom_first_region(const rom_entry *romp);
/* return pointer to the next ROM region within a source */
const rom_entry *rom_next_region(const rom_entry *romp);

View File

@ -22,6 +22,111 @@
#include <algorithm>
//#define VERBOSE 1
#define LOG_OUTPUT_FUNC osd_printf_verbose
#include "logmacro.h"
namespace {
struct parent_rom
{
parent_rom(device_type t, rom_entry const *r) : type(t), name(ROM_GETNAME(r)), hashes(ROM_GETHASHDATA(r)), length(rom_file_size(r)) { }
std::reference_wrapper<std::remove_reference_t<device_type> > type;
std::string name;
util::hash_collection hashes;
uint64_t length;
};
class parent_rom_vector : public std::vector<parent_rom>
{
public:
using std::vector<parent_rom>::vector;
std::add_pointer_t<device_type> find_shared_device(device_t &current, char const *name, util::hash_collection const &hashes, uint64_t length) const
{
// if we're examining a child device, it will always have a perfect match
if (current.owner())
return &current.type();
// scan backwards through parents for a matching definition
bool const dumped(!hashes.flag(util::hash_collection::FLAG_NO_DUMP));
std::add_pointer_t<device_type> best(nullptr);
for (const_reverse_iterator it = crbegin(); crend() != it; ++it)
{
if (it->length == length)
{
if (dumped)
{
if (it->hashes == hashes)
return &it->type.get();
}
else if (it->name == name)
{
if (it->hashes.flag(util::hash_collection::FLAG_NO_DUMP))
return &it->type.get();
else if (!best)
best = &it->type.get();
}
}
}
return best;
}
std::pair<std::add_pointer_t<device_type>, bool> actual_matches_shared(device_t &current, media_auditor::audit_record const &record)
{
// no result if no matching file was found
if ((record.status() != media_auditor::audit_status::GOOD) && (record.status() != media_auditor::audit_status::FOUND_INVALID))
return std::make_pair(nullptr, false);
// if we're examining a child device, scan it first
bool matches_device_undumped(false);
if (current.owner())
{
for (const rom_entry *region = rom_first_region(current); region; region = rom_next_region(region))
{
for (const rom_entry *rom = rom_first_file(region); rom; rom = rom_next_file(rom))
{
if (rom_file_size(rom) == record.actual_length())
{
util::hash_collection const hashes(ROM_GETHASHDATA(rom));
if (hashes == record.actual_hashes())
return std::make_pair(&current.type(), empty());
else if (hashes.flag(util::hash_collection::FLAG_NO_DUMP) && (rom->name() == record.name()))
matches_device_undumped = true;
}
}
}
}
// look for a matching parent ROM
std::add_pointer_t<device_type> closest_bad(nullptr);
for (const_reverse_iterator it = crbegin(); crend() != it; ++it)
{
if (it->length == record.actual_length())
{
if (it->hashes == record.actual_hashes())
return std::make_pair(&it->type.get(), it->type.get() == front().type.get());
else if (it->hashes.flag(util::hash_collection::FLAG_NO_DUMP) && (it->name == record.name()))
closest_bad = &it->type.get();
}
}
// fall back to the nearest bad dump
if (closest_bad)
return std::make_pair(closest_bad, front().type.get() == *closest_bad);
else if (matches_device_undumped)
return std::make_pair(&current.type(), empty());
else
return std::make_pair(nullptr, false);
}
};
} // anonymous namespace
//**************************************************************************
// CORE FUNCTIONS
@ -51,10 +156,29 @@ media_auditor::summary media_auditor::audit_media(const char *validation)
// store validation for later
m_validation = validation;
std::size_t found = 0;
std::size_t required = 0;
std::size_t shared_found = 0;
std::size_t shared_required = 0;
// first walk the parent chain for required ROMs
parent_rom_vector parentroms;
for (auto drvindex = m_enumerator.find(m_enumerator.driver().parent); 0 <= drvindex; drvindex = m_enumerator.find(m_enumerator.driver(drvindex).parent))
{
game_driver const &parent(m_enumerator.driver(drvindex));
LOG("Checking parent %s for ROM files\n", parent.type.shortname());
std::vector<rom_entry> const roms(rom_build_entries(parent.rom));
for (rom_entry const *region = rom_first_region(&roms.front()); region; region = rom_next_region(region))
{
for (rom_entry const *rom = rom_first_file(region); rom; rom = rom_next_file(rom))
{
LOG("Adding parent ROM %s\n", rom->name());
parentroms.emplace_back(parent.type, rom);
}
}
}
// count ROMs required/found
std::size_t found(0);
std::size_t required(0);
std::size_t shared_found(0);
std::size_t shared_required(0);
std::size_t parent_found(0);
// iterate over devices and regions
std::vector<std::string> searchpath;
@ -68,12 +192,20 @@ media_auditor::summary media_auditor::audit_media(const char *validation)
for (const rom_entry *rom = rom_first_file(region); rom; rom = rom_next_file(rom))
{
if (searchpath.empty())
{
LOG("Audit media for device %s(%s)\n", device.shortname(), device.tag());
searchpath = device.searchpath();
}
// look for a matching parent or device ROM
char const *const name(ROM_GETNAME(rom));
util::hash_collection const hashes(ROM_GETHASHDATA(rom));
bool const dumped = !hashes.flag(util::hash_collection::FLAG_NO_DUMP);
std::add_pointer_t<device_type> const shared_device(find_shared_device(device, name, hashes, rom_file_size(rom)));
bool const dumped(!hashes.flag(util::hash_collection::FLAG_NO_DUMP));
std::add_pointer_t<device_type> const shared_device(parentroms.find_shared_device(device, name, hashes, rom_file_size(rom)));
if (shared_device)
LOG("File '%s' %s%sdumped shared with %s\n", name, ROM_ISOPTIONAL(rom) ? "optional " : "", dumped ? "" : "un", shared_device->shortname());
else
LOG("File '%s' %s%sdumped\n", name, ROM_ISOPTIONAL(rom) ? "optional " : "", dumped ? "" : "un");
// count the number of files with hashes
if (dumped && !ROM_ISOPTIONAL(rom))
@ -83,7 +215,7 @@ media_auditor::summary media_auditor::audit_media(const char *validation)
shared_required++;
}
audit_record *record = nullptr;
audit_record *record(nullptr);
if (ROMREGION_ISROMDATA(region))
record = &audit_one_rom(searchpath, rom);
else if (ROMREGION_ISDISKDATA(region))
@ -91,22 +223,32 @@ media_auditor::summary media_auditor::audit_media(const char *validation)
if (record)
{
// see if the actual content found belongs to a parent
auto const matchesshared(parentroms.actual_matches_shared(device, *record));
if (matchesshared.first)
LOG("Actual ROM file shared with %sparent %s\n", matchesshared.second ? "immediate " : "", matchesshared.first->shortname());
// count the number of files that are found.
if ((record->status() == audit_status::GOOD) || ((record->status() == audit_status::FOUND_INVALID) && !find_shared_device(device, name, record->actual_hashes(), record->actual_length())))
if ((record->status() == audit_status::GOOD) || ((record->status() == audit_status::FOUND_INVALID) && !matchesshared.first))
{
found++;
if (shared_device)
shared_found++;
if (matchesshared.second)
parent_found++;
}
record->set_shared_device(shared_device);
}
}
}
if (!searchpath.empty())
LOG("Total required=%u (shared=%u) found=%u (shared=%u parent=%u)\n", required, shared_required, found, shared_found, parent_found);
}
// if we only find files that are in the parent & either the set has no unique files or the parent is not found, then assume we don't have the set at all
if ((found == shared_found) && (required > 0) && ((required != shared_required) || (shared_found == 0)))
if ((found == shared_found) && required && ((required != shared_required) || !parent_found))
{
m_record_list.clear();
return NOTFOUND;
@ -499,51 +641,6 @@ void media_auditor::compute_status(audit_record &record, const rom_entry *rom, b
}
//-------------------------------------------------
// find_shared_device - return the source that
// shares a media entry with the same hashes
//-------------------------------------------------
std::add_pointer_t<device_type> media_auditor::find_shared_device(device_t &device, const char *name, const util::hash_collection &romhashes, uint64_t romlength)
{
bool const dumped = !romhashes.flag(util::hash_collection::FLAG_NO_DUMP);
device_t *highest_device = nullptr;
auto const scan =
[dumped, name, &romhashes, romlength, &highest_device] (device_t &curdev)
{
for (const rom_entry *region = rom_first_region(curdev); region; region = rom_next_region(region))
{
for (const rom_entry *rom = rom_first_file(region); rom; rom = rom_next_file(rom))
{
if (rom_file_size(rom) == romlength)
{
util::hash_collection hashes(ROM_GETHASHDATA(rom));
if ((dumped && hashes == romhashes) || (!dumped && ROM_GETNAME(rom) == name))
highest_device = &curdev;
}
}
}
};
if (device.owner())
{
// special case for non-root devices
scan(device);
}
else
{
// iterate up the parent chain
for (auto drvindex = m_enumerator.find(m_enumerator.driver().parent); drvindex >= 0; drvindex = m_enumerator.find(m_enumerator.driver(drvindex).parent))
{
for (device_t &scandevice : device_iterator(m_enumerator.config(drvindex)->root_device()))
scan(scandevice);
}
}
return highest_device ? &highest_device->type() : nullptr;
}
//-------------------------------------------------
// audit_record - constructor
//-------------------------------------------------

View File

@ -166,7 +166,6 @@ private:
audit_record &audit_one_rom(const std::vector<std::string> &searchpath, const rom_entry *rom);
template <typename... T> audit_record &audit_one_disk(const rom_entry *rom, T &&... args);
void compute_status(audit_record &record, const rom_entry *rom, bool found);
std::add_pointer_t<device_type> find_shared_device(device_t &device, const char *name, const util::hash_collection &romhashes, uint64_t romlength);
// internal state
record_list m_record_list;