summaryrefslogtreecommitdiffstatshomepage
path: root/trunk/src/emu/audit.c
diff options
context:
space:
mode:
Diffstat (limited to 'trunk/src/emu/audit.c')
-rw-r--r--trunk/src/emu/audit.c551
1 files changed, 551 insertions, 0 deletions
diff --git a/trunk/src/emu/audit.c b/trunk/src/emu/audit.c
new file mode 100644
index 00000000000..03318ac0ce0
--- /dev/null
+++ b/trunk/src/emu/audit.c
@@ -0,0 +1,551 @@
+/***************************************************************************
+
+ audit.c
+
+ ROM set auditing functions.
+
+****************************************************************************
+
+ Copyright Aaron Giles
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are
+ met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the
+ distribution.
+ * Neither the name 'MAME' nor the names of its contributors may be
+ used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY AARON GILES ''AS IS'' AND ANY EXPRESS OR
+ IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL AARON GILES BE LIABLE FOR ANY DIRECT,
+ INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ POSSIBILITY OF SUCH DAMAGE.
+
+***************************************************************************/
+
+#include "emu.h"
+#include "emuopts.h"
+#include "hash.h"
+#include "audit.h"
+#include "harddisk.h"
+#include "sound/samples.h"
+
+
+//**************************************************************************
+// CORE FUNCTIONS
+//**************************************************************************
+
+//-------------------------------------------------
+// media_auditor - constructor
+//-------------------------------------------------
+
+media_auditor::media_auditor(const driver_enumerator &enumerator)
+ : m_enumerator(enumerator),
+ m_validation(AUDIT_VALIDATE_FULL),
+ m_searchpath(NULL)
+{
+}
+
+
+//-------------------------------------------------
+// audit_media - audit the media described by the
+// currently-enumerated driver
+//-------------------------------------------------
+
+media_auditor::summary media_auditor::audit_media(const char *validation)
+{
+ // start fresh
+ m_record_list.reset();
+
+ // store validation for later
+ m_validation = validation;
+
+// temporary hack until romload is update: get the driver path and support it for
+// all searches
+const char *driverpath = m_enumerator.config().devicelist().find("root")->searchpath();
+
+ // iterate over ROM sources and regions
+ int found = 0;
+ int required = 0;
+ int sharedFound = 0;
+ int sharedRequired = 0;
+
+ for (const rom_source *source = rom_first_source(m_enumerator.config()); source != NULL; source = rom_next_source(*source))
+ {
+ // determine the search path for this source and iterate through the regions
+ m_searchpath = source->searchpath();
+
+ // now iterate over regions and ROMs within
+ for (const rom_entry *region = rom_first_region(*source); region != NULL; region = rom_next_region(region))
+ {
+// temporary hack: add the driver path & region name
+astring combinedpath(source->searchpath(), ";", driverpath);
+if(source->shortname())
+{
+ combinedpath=combinedpath.cat(";");
+ combinedpath=combinedpath.cat(source->shortname());
+}
+m_searchpath = combinedpath;
+
+ for (const rom_entry *rom = rom_first_file(region); rom; rom = rom_next_file(rom))
+ {
+ hash_collection hashes(ROM_GETHASHDATA(rom));
+ const rom_source *shared_source = find_shared_source(source, hashes, ROM_GETLENGTH(rom));
+
+ // count the number of files with hashes
+ if (!hashes.flag(hash_collection::FLAG_NO_DUMP) && !ROM_ISOPTIONAL(rom))
+ {
+ required++;
+ if (shared_source != NULL)
+ {
+ sharedRequired++;
+ }
+ }
+
+ // audit a file
+ audit_record *record = NULL;
+ if (ROMREGION_ISROMDATA(region))
+ record = audit_one_rom(rom);
+
+ // audit a disk
+ else if (ROMREGION_ISDISKDATA(region))
+ record = audit_one_disk(rom);
+
+ if (record != NULL)
+ {
+ // count the number of files that are found.
+ if (record->status() == audit_record::STATUS_GOOD || (record->status() == audit_record::STATUS_FOUND_INVALID && find_shared_source(source,record->actual_hashes(), record->actual_length()) != NULL))
+ {
+ found++;
+ if (shared_source != NULL)
+ {
+ sharedFound++;
+ }
+ }
+
+ record->set_shared_source(shared_source);
+ }
+ }
+ }
+ }
+
+ // 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 == sharedFound && required > 0 && (required != sharedRequired || sharedFound == 0))
+ {
+ m_record_list.reset();
+ return NOTFOUND;
+ }
+
+ // return a summary
+ return summarize(m_enumerator.driver().name);
+}
+
+
+//-------------------------------------------------
+// audit_device - audit the device
+//-------------------------------------------------
+
+media_auditor::summary media_auditor::audit_device(device_t *device, const char *validation)
+{
+ // start fresh
+ m_record_list.reset();
+
+ // store validation for later
+ m_validation = validation;
+ m_searchpath = device->shortname();
+
+ int found = 0;
+ int required = 0;
+
+ // now iterate over regions and ROMs within
+ for (const rom_entry *region = rom_first_region(*device); region != NULL; region = rom_next_region(region))
+ {
+ for (const rom_entry *rom = rom_first_file(region); rom; rom = rom_next_file(rom))
+ {
+ hash_collection hashes(ROM_GETHASHDATA(rom));
+
+ // count the number of files with hashes
+ if (!hashes.flag(hash_collection::FLAG_NO_DUMP) && !ROM_ISOPTIONAL(rom))
+ {
+ required++;
+ }
+
+ // audit a file
+ audit_record *record = NULL;
+ if (ROMREGION_ISROMDATA(region))
+ record = audit_one_rom(rom);
+
+ // audit a disk
+ else if (ROMREGION_ISDISKDATA(region))
+ record = audit_one_disk(rom);
+
+ // count the number of files that are found.
+ if (record != NULL && (record->status() == audit_record::STATUS_GOOD || record->status() == audit_record::STATUS_FOUND_INVALID))
+ {
+ found++;
+ }
+ }
+ }
+
+ if (found == 0 && required > 0)
+ {
+ m_record_list.reset();
+ return NOTFOUND;
+ }
+
+ // return a summary
+ return summarize(device->shortname());
+}
+
+
+//-------------------------------------------------
+// audit_samples - validate the samples for the
+// currently-enumerated driver
+//-------------------------------------------------
+
+media_auditor::summary media_auditor::audit_samples()
+{
+ // start fresh
+ m_record_list.reset();
+
+ // iterate over sample entries
+ for (const device_t *device = m_enumerator.config().first_device(); device != NULL; device = device->next())
+ if (device->type() == SAMPLES)
+ {
+ const samples_interface *intf = reinterpret_cast<const samples_interface *>(device->static_config());
+ if (intf->samplenames != NULL)
+ {
+ // by default we just search using the driver name
+ astring searchpath(m_enumerator.driver().name);
+
+ // iterate over samples in this entry
+ for (int sampnum = 0; intf->samplenames[sampnum] != NULL; sampnum++)
+ {
+ // starred entries indicate an additional searchpath
+ if (intf->samplenames[sampnum][0] == '*')
+ {
+ searchpath.cat(";").cat(&intf->samplenames[sampnum][1]);
+ continue;
+ }
+
+ // create a new record
+ audit_record &record = m_record_list.append(*global_alloc(audit_record(intf->samplenames[sampnum], audit_record::MEDIA_SAMPLE)));
+
+ // look for the files
+ emu_file file(m_enumerator.options().sample_path(), OPEN_FLAG_READ | OPEN_FLAG_NO_PRELOAD);
+ path_iterator path(searchpath);
+ astring curpath;
+ while (path.next(curpath, intf->samplenames[sampnum]))
+ {
+ // attempt to access the file
+ file_error filerr = file.open(curpath);
+ if (filerr == FILERR_NONE)
+ record.set_status(audit_record::STATUS_GOOD, audit_record::SUBSTATUS_GOOD);
+ else
+ record.set_status(audit_record::STATUS_NOT_FOUND, audit_record::SUBSTATUS_NOT_FOUND);
+ }
+ }
+ }
+ }
+
+ // no count AND no records means not found
+ if (m_record_list.count() == 0)
+ return NOTFOUND;
+
+ // return a summary
+ return summarize(m_enumerator.driver().name);
+}
+
+
+//-------------------------------------------------
+// summary - generate a summary, with an optional
+// string format
+//-------------------------------------------------
+
+media_auditor::summary media_auditor::summarize(const char *name, astring *string)
+{
+ // loop over records
+ summary overall_status = CORRECT;
+ for (audit_record *record = m_record_list.first(); record != NULL; record = record->next())
+ {
+ summary best_new_status = INCORRECT;
+
+ // skip anything that's fine
+ if (record->substatus() == audit_record::SUBSTATUS_GOOD)
+ continue;
+
+ // output the game name, file name, and length (if applicable)
+ if (string != NULL)
+ {
+ string->catprintf("%-12s: %s", name, record->name());
+ if (record->expected_length() > 0)
+ string->catprintf(" (%d bytes)", record->expected_length());
+ string->catprintf(" - ");
+ }
+
+ // use the substatus for finer details
+ switch (record->substatus())
+ {
+ case audit_record::SUBSTATUS_GOOD_NEEDS_REDUMP:
+ if (string != NULL) string->catprintf("NEEDS REDUMP\n");
+ best_new_status = BEST_AVAILABLE;
+ break;
+
+ case audit_record::SUBSTATUS_FOUND_NODUMP:
+ if (string != NULL) string->catprintf("NO GOOD DUMP KNOWN\n");
+ best_new_status = BEST_AVAILABLE;
+ break;
+
+ case audit_record::SUBSTATUS_FOUND_BAD_CHECKSUM:
+ if (string != NULL)
+ {
+ astring tempstr;
+ string->catprintf("INCORRECT CHECKSUM:\n");
+ string->catprintf("EXPECTED: %s\n", record->expected_hashes().macro_string(tempstr));
+ string->catprintf(" FOUND: %s\n", record->actual_hashes().macro_string(tempstr));
+ }
+ break;
+
+ case audit_record::SUBSTATUS_FOUND_WRONG_LENGTH:
+ if (string != NULL) string->catprintf("INCORRECT LENGTH: %d bytes\n", record->actual_length());
+ break;
+
+ case audit_record::SUBSTATUS_NOT_FOUND:
+ if (string != NULL)
+ {
+ const rom_source *shared_source = record->shared_source();
+ if (shared_source == NULL) string->catprintf("NOT FOUND\n");
+ else string->catprintf("NOT FOUND (%s)\n", shared_source->shortname());
+ }
+ break;
+
+ case audit_record::SUBSTATUS_NOT_FOUND_NODUMP:
+ if (string != NULL) string->catprintf("NOT FOUND - NO GOOD DUMP KNOWN\n");
+ best_new_status = BEST_AVAILABLE;
+ break;
+
+ case audit_record::SUBSTATUS_NOT_FOUND_OPTIONAL:
+ if (string != NULL) string->catprintf("NOT FOUND BUT OPTIONAL\n");
+ best_new_status = BEST_AVAILABLE;
+ break;
+
+ default:
+ assert(false);
+ }
+
+ // downgrade the overall status if necessary
+ overall_status = MAX(overall_status, best_new_status);
+ }
+ return overall_status;
+}
+
+
+//-------------------------------------------------
+// audit_one_rom - validate a single ROM entry
+//-------------------------------------------------
+
+audit_record *media_auditor::audit_one_rom(const rom_entry *rom)
+{
+ // allocate and append a new record
+ audit_record &record = m_record_list.append(*global_alloc(audit_record(*rom, audit_record::MEDIA_ROM)));
+
+ // see if we have a CRC and extract it if so
+ UINT32 crc = 0;
+ bool has_crc = record.expected_hashes().crc(crc);
+
+ // find the file and checksum it, getting the file length along the way
+ emu_file file(m_enumerator.options().media_path(), OPEN_FLAG_READ | OPEN_FLAG_NO_PRELOAD);
+ path_iterator path(m_searchpath);
+ astring curpath;
+ while (path.next(curpath, record.name()))
+ {
+ // open the file if we can
+ file_error filerr;
+ if (has_crc)
+ filerr = file.open(curpath, crc);
+ else
+ filerr = file.open(curpath);
+
+ // if it worked, get the actual length and hashes, then stop
+ if (filerr == FILERR_NONE)
+ {
+ record.set_actual(file.hashes(m_validation), file.size());
+ break;
+ }
+ }
+
+ // compute the final status
+ compute_status(record, rom, record.actual_length() != 0);
+ return &record;
+}
+
+
+//-------------------------------------------------
+// audit_one_disk - validate a single disk entry
+//-------------------------------------------------
+
+audit_record *media_auditor::audit_one_disk(const rom_entry *rom)
+{
+ // allocate and append a new record
+ audit_record &record = m_record_list.append(*global_alloc(audit_record(*rom, audit_record::MEDIA_DISK)));
+
+ // open the disk
+ emu_file *source_file;
+ chd_file *source;
+ chd_error err = open_disk_image(m_enumerator.options(), &m_enumerator.driver(), rom, &source_file, &source, NULL);
+
+ // if we succeeded, get the hashes
+ if (err == CHDERR_NONE)
+ {
+ static const UINT8 nullhash[20] = { 0 };
+ chd_header header = *chd_get_header(source);
+ hash_collection hashes;
+
+ // if there's a SHA1 hash, add them to the output hash
+ if (memcmp(nullhash, header.sha1, sizeof(header.sha1)) != 0)
+ hashes.add_from_buffer(hash_collection::HASH_SHA1, header.sha1, sizeof(header.sha1));
+
+ // update the actual values
+ record.set_actual(hashes);
+
+ // close the file and release the source
+ chd_close(source);
+ global_free(source_file);
+ }
+
+ // compute the final status
+ compute_status(record, rom, err == CHDERR_NONE);
+ return &record;
+}
+
+
+//-------------------------------------------------
+// compute_status - compute a detailed status
+// based on the information we have
+//-------------------------------------------------
+
+void media_auditor::compute_status(audit_record &record, const rom_entry *rom, bool found)
+{
+ // if not found, provide more details
+ if (!found)
+ {
+ // no good dump
+ if (record.expected_hashes().flag(hash_collection::FLAG_NO_DUMP))
+ record.set_status(audit_record::STATUS_NOT_FOUND, audit_record::SUBSTATUS_NOT_FOUND_NODUMP);
+
+ // optional ROM
+ else if (ROM_ISOPTIONAL(rom))
+ record.set_status(audit_record::STATUS_NOT_FOUND, audit_record::SUBSTATUS_NOT_FOUND_OPTIONAL);
+
+ // just plain old not found
+ else
+ record.set_status(audit_record::STATUS_NOT_FOUND, audit_record::SUBSTATUS_NOT_FOUND);
+ }
+
+ // if found, provide more details
+ else
+ {
+ // length mismatch
+ if (record.expected_length() != record.actual_length())
+ record.set_status(audit_record::STATUS_FOUND_INVALID, audit_record::SUBSTATUS_FOUND_WRONG_LENGTH);
+
+ // found but needs a dump
+ else if (record.expected_hashes().flag(hash_collection::FLAG_NO_DUMP))
+ record.set_status(audit_record::STATUS_GOOD, audit_record::SUBSTATUS_FOUND_NODUMP);
+
+ // incorrect hash
+ else if (record.expected_hashes() != record.actual_hashes())
+ record.set_status(audit_record::STATUS_FOUND_INVALID, audit_record::SUBSTATUS_FOUND_BAD_CHECKSUM);
+
+ // correct hash but needs a redump
+ else if (record.expected_hashes().flag(hash_collection::FLAG_BAD_DUMP))
+ record.set_status(audit_record::STATUS_GOOD, audit_record::SUBSTATUS_GOOD_NEEDS_REDUMP);
+
+ // just plain old good
+ else
+ record.set_status(audit_record::STATUS_GOOD, audit_record::SUBSTATUS_GOOD);
+ }
+}
+
+
+//-------------------------------------------------
+// find_shared_source - return the source that
+// shares a media entry with the same hashes
+//-------------------------------------------------
+const rom_source *media_auditor::find_shared_source(const rom_source *source, const hash_collection &romhashes, UINT64 romlength)
+{
+ const rom_source *highest_source = NULL;
+
+ if (!romhashes.flag(hash_collection::FLAG_NO_DUMP))
+ {
+ if (dynamic_cast<const driver_device *>(source) == NULL)
+ {
+ for (const rom_entry *region = rom_first_region(*source); region; region = rom_next_region(region))
+ for (const rom_entry *rom = rom_first_file(region); rom; rom = rom_next_file(rom))
+ if (ROM_GETLENGTH(rom) == romlength)
+ {
+ hash_collection hashes(ROM_GETHASHDATA(rom));
+ if (hashes == romhashes)
+ highest_source = source;
+ }
+ }
+ else
+ {
+ // iterate up the parent chain
+ for (int drvindex = m_enumerator.find(m_enumerator.driver().parent); drvindex != -1; drvindex = m_enumerator.find(m_enumerator.driver(drvindex).parent))
+ for (const rom_source *source = rom_first_source(m_enumerator.config(drvindex)); source != NULL; source = rom_next_source(*source))
+ for (const rom_entry *region = rom_first_region(*source); region; region = rom_next_region(region))
+ for (const rom_entry *rom = rom_first_file(region); rom; rom = rom_next_file(rom))
+ if (ROM_GETLENGTH(rom) == romlength)
+ {
+ hash_collection hashes(ROM_GETHASHDATA(rom));
+ if (hashes == romhashes)
+ highest_source = source;
+ }
+ }
+ }
+
+ return highest_source;
+}
+
+
+//-------------------------------------------------
+// audit_record - constructor
+//-------------------------------------------------
+
+audit_record::audit_record(const rom_entry &media, media_type type)
+ : m_next(NULL),
+ m_type(type),
+ m_status(STATUS_ERROR),
+ m_substatus(SUBSTATUS_ERROR),
+ m_name(ROM_GETNAME(&media)),
+ m_explength(rom_file_size(&media)),
+ m_length(0)
+{
+ m_exphashes.from_internal_string(ROM_GETHASHDATA(&media));
+}
+
+audit_record::audit_record(const char *name, media_type type)
+ : m_next(NULL),
+ m_type(type),
+ m_status(STATUS_ERROR),
+ m_substatus(SUBSTATUS_ERROR),
+ m_name(name),
+ m_explength(0),
+ m_length(0)
+{
+}