text: /***************************************************************************
  
      audit.c
  
      ROM set auditing functions.
  
      Copyright Nicola Salmoria and the MAME Team.
      Visit http://mamedev.org for licensing and usage restrictions.
  
  ***************************************************************************/
  
  #include <stdarg.h>
  #include "driver.h"
  #include "hash.h"
  #include "audit.h"
  #include "harddisk.h"
  #include "sound/samples.h"
  
  
  
  /***************************************************************************
      FUNCTION PROTOTYPES
  ***************************************************************************/
  
  static void audit_one_rom(core_options *options, const rom_entry *rom, const char *regiontag, const game_driver *gamedrv, UINT32 validation, audit_record *record);
  static void audit_one_disk(core_options *options, const rom_entry *rom, const game_driver *gamedrv, UINT32 validation, audit_record *record);
  static int rom_used_by_parent(const game_driver *gamedrv, const rom_entry *romentry, const game_driver **parent);
  
  
  
  /***************************************************************************
      INLINE FUNCTIONS
  ***************************************************************************/
  
  /*-------------------------------------------------
      set_status - shortcut for setting status and
      substatus values
  -------------------------------------------------*/
  
  INLINE void set_status(audit_record *record, UINT8 status, UINT8 substatus)
  {
  	record->status = status;
  	record->substatus = substatus;
  }
  
  
  
  /***************************************************************************
      CORE FUNCTIONS
  ***************************************************************************/
  
  /*-------------------------------------------------
      audit_images - validate the ROM and disk
      images for a game
  -------------------------------------------------*/
  
  int audit_images(core_options *options, const game_driver *gamedrv, UINT32 validation, audit_record **audit)
  {
  	machine_config *config = machine_config_alloc(gamedrv->machine_config);
  	const rom_entry *region, *rom;
  	const rom_source *source;
  	audit_record *record;
  	int anyfound = FALSE;
  	int anyrequired = FALSE;
  	int allshared = TRUE;
  	int records;
  
  	/* determine the number of records we will generate */
  	records = 0;
  	for (source = rom_first_source(gamedrv, config); source != NULL; source = rom_next_source(gamedrv, config, source))
  	{
  		int source_is_gamedrv = rom_source_is_gamedrv(gamedrv, source);
  		for (region = rom_first_region(gamedrv, source); region != NULL; region = rom_next_region(region))
  			for (rom = rom_first_file(region); rom != NULL; rom = rom_next_file(rom))
  				if (ROMREGION_ISROMDATA(region) || ROMREGION_ISDISKDATA(region))
  				{
  					if (source_is_gamedrv && !ROM_ISOPTIONAL(rom) && !ROM_NOGOODDUMP(rom))
  					{
  						anyrequired = TRUE;
  
  						if (allshared && !rom_used_by_parent(gamedrv, rom, NULL))
  							allshared = FALSE;
  					}
  					records++;
  				}
  	}
  
  	if (records > 0)
  	{
  		/* allocate memory for the records */
  		*audit = alloc_array_clear_or_die(audit_record, records);
  		record = *audit;
  
  		/* iterate over ROM sources and regions */
  		for (source = rom_first_source(gamedrv, config); source != NULL; source = rom_next_source(gamedrv, config, source))
  		{
  			int source_is_gamedrv = rom_source_is_gamedrv(gamedrv, source);
  			for (region = rom_first_region(gamedrv, source); region != NULL; region = rom_next_region(region))
  			{
  				const char *regiontag = ROMREGION_ISLOADBYNAME(region) ? ROM_GETNAME(region) : NULL;
  				for (rom = rom_first_file(region); rom; rom = rom_next_file(rom))
  				{
  					/* audit a file */
  					if (ROMREGION_ISROMDATA(region))
  					{
  						audit_one_rom(options, rom, regiontag, gamedrv, validation, record);
  					}
  
  					/* audit a disk */
  					else if (ROMREGION_ISDISKDATA(region))
  					{
  						audit_one_disk(options, rom, gamedrv, validation, record);
  					}
  
  					else
  					{
  						continue;
  					}
  
  					if (source_is_gamedrv && record->status != AUDIT_STATUS_NOT_FOUND && (allshared || !rom_used_by_parent(gamedrv, rom, NULL)))
  						anyfound = TRUE;
  
  					record++;
  				}
  			}
  		}
  	}
  
  	/* if we found nothing, we don't have the set at all */
  	if (!anyfound && anyrequired)
  	{
  		free(*audit);
  		*audit = NULL;
  		records = 0;
  	}
  
  	machine_config_free(config);
  	return records;
  }
  
  
  /*-------------------------------------------------
      audit_samples - validate the samples for a
      game
  -------------------------------------------------*/
  
  int audit_samples(core_options *options, const game_driver *gamedrv, audit_record **audit)
  {
  	int records = 0;
  #if HAS_SAMPLES
  	machine_config *config = machine_config_alloc(gamedrv->machine_config);
  	const device_config *device;
  	audit_record *record;
  	int sampnum;
  
  	/* count the number of sample records attached to this driver */
  	for (device = sound_first(config); device != NULL; device = sound_next(device))
  		if (sound_get_type(device) == SOUND_SAMPLES)
  		{
  			const samples_interface *intf = (const samples_interface *)device->static_config;
  
  			if (intf->samplenames != NULL)
  			{
  				/* iterate over samples in this entry */
  				for (sampnum = 0; intf->samplenames[sampnum] != NULL; sampnum++)
  					if (intf->samplenames[sampnum][0] != '*')
  						records++;
  			}
  		}
  
  	/* if no records, just quit now */
  	if (records == 0)
  		goto skip;
  
  	/* allocate memory for the records */
  	*audit = alloc_array_clear_or_die(audit_record, records);
  	record = *audit;
  
  	/* now iterate over sample entries */
  	for (device = sound_first(config); device != NULL; device = sound_next(device))
  		if (sound_get_type(device) == SOUND_SAMPLES)
  		{
  			const samples_interface *intf = (const samples_interface *)device->static_config;
  			const char *sharedname = NULL;
  
  			if (intf->samplenames != NULL)
  			{
  				/* iterate over samples in this entry */
  				for (sampnum = 0; intf->samplenames[sampnum] != NULL; sampnum++)
  					if (intf->samplenames[sampnum][0] == '*')
  						sharedname = &intf->samplenames[sampnum][1];
  					else
  					{
  						file_error filerr;
  						mame_file *file;
  						astring *fname;
  
  						/* attempt to access the file from the game driver name */
  						fname = astring_assemble_3(astring_alloc(), gamedrv->name, PATH_SEPARATOR, intf->samplenames[sampnum]);
  						filerr = mame_fopen_options(options, SEARCHPATH_SAMPLE, astring_c(fname), OPEN_FLAG_READ | OPEN_FLAG_NO_PRELOAD, &file);
  
  						/* attempt to access the file from the shared driver name */
  						if (filerr != FILERR_NONE && sharedname != NULL)
  						{
  							astring_assemble_3(fname, sharedname, PATH_SEPARATOR, intf->samplenames[sampnum]);
  							filerr = mame_fopen_options(options, SEARCHPATH_SAMPLE, astring_c(fname), OPEN_FLAG_READ | OPEN_FLAG_NO_PRELOAD, &file);
  						}
  						astring_free(fname);
  
  						/* fill in the record */
  						record->type = AUDIT_FILE_SAMPLE;
  						record->name = intf->samplenames[sampnum];
  						if (filerr == FILERR_NONE)
  						{
  							set_status(record++, AUDIT_STATUS_GOOD, SUBSTATUS_GOOD);
  							mame_fclose(file);
  						}
  						else
  							set_status(record++, AUDIT_STATUS_NOT_FOUND, SUBSTATUS_NOT_FOUND);
  					}
  				}
  		}
  
  skip:
  	machine_config_free(config);
  #endif /* HAS_SAMPLES */
  	return records;
  }
  
  
  /*-------------------------------------------------
      audit_summary - output a summary given a
      list of audit records
  -------------------------------------------------*/
  
  int audit_summary(const game_driver *gamedrv, int count, const audit_record *records, int output)
  {
  	int overall_status = CORRECT;
  	int recnum;
  
  	/* no count AND no records means not found */
  	if (count == 0 && records == NULL)
  		return NOTFOUND;
  
  	/* loop over records */
  	for (recnum = 0; recnum < count; recnum++)
  	{
  		const audit_record *record = &records[recnum];
  		int best_new_status = INCORRECT;
  
  		/* skip anything that's fine */
  		if (record->substatus == SUBSTATUS_GOOD)
  			continue;
  
  		/* output the game name, file name, and length (if applicable) */
  		if (output)
  		{
  			mame_printf_info("%-8s: %s", gamedrv->name, record->name);
  			if (record->explength > 0)
  				mame_printf_info(" (%d bytes)", record->explength);
  			mame_printf_info(" - ");
  		}
  
  		/* use the substatus for finer details */
  		switch (record->substatus)
  		{
  			case SUBSTATUS_GOOD_NEEDS_REDUMP:
  				if (output) mame_printf_info("NEEDS REDUMP\n");
  				best_new_status = BEST_AVAILABLE;
  				break;
  
  			case SUBSTATUS_FOUND_NODUMP:
  				if (output) mame_printf_info("NO GOOD DUMP KNOWN\n");
  				best_new_status = BEST_AVAILABLE;
  				break;
  
  			case SUBSTATUS_FOUND_BAD_CHECKSUM:
  				if (output)
  				{
  					char hashbuf[512];
  
  					mame_printf_info("INCORRECT CHECKSUM:\n");
  					hash_data_print(record->exphash, 0, hashbuf);
  					mame_printf_info("EXPECTED: %s\n", hashbuf);
  					hash_data_print(record->hash, 0, hashbuf);
  					mame_printf_info("   FOUND: %s\n", hashbuf);
  				}
  				break;
  
  			case SUBSTATUS_FOUND_WRONG_LENGTH:
  				if (output) mame_printf_info("INCORRECT LENGTH: %d bytes\n", record->length);
  				break;
  
  			case SUBSTATUS_NOT_FOUND:
  				if (output) mame_printf_info("NOT FOUND\n");
  				break;
  
  			case SUBSTATUS_NOT_FOUND_NODUMP:
  				if (output) mame_printf_info("NOT FOUND - NO GOOD DUMP KNOWN\n");
  				best_new_status = BEST_AVAILABLE;
  				break;
  
  			case SUBSTATUS_NOT_FOUND_OPTIONAL:
  				if (output) mame_printf_info("NOT FOUND BUT OPTIONAL\n");
  				best_new_status = BEST_AVAILABLE;
  				break;
  
  			case SUBSTATUS_NOT_FOUND_PARENT:
  				if (output) mame_printf_info("NOT FOUND (shared with parent)\n");
  				break;
  
  			case SUBSTATUS_NOT_FOUND_BIOS:
  				if (output) mame_printf_info("NOT FOUND (BIOS)\n");
  				break;
  		}
  
  		/* downgrade the overall status if necessary */
  		overall_status = MAX(overall_status, best_new_status);
  	}
  
  	return overall_status;
  }
  
  
  
  /***************************************************************************
      UTILITIES
  ***************************************************************************/
  
  /*-------------------------------------------------
      audit_one_rom - validate a single ROM entry
  -------------------------------------------------*/
  
  static void audit_one_rom(core_options *options, const rom_entry *rom, const char *regiontag, const game_driver *gamedrv, UINT32 validation, audit_record *record)
  {
  	const game_driver *drv;
  	UINT32 crc = 0;
  	UINT8 crcs[4];
  	int has_crc;
  
  	/* fill in the record basics */
  	record->type = AUDIT_FILE_ROM;
  	record->name = ROM_GETNAME(rom);
  	record->exphash = ROM_GETHASHDATA(rom);
  	record->length = 0;
  	record->explength = rom_file_size(rom);
  
  	/* see if we have a CRC and extract it if so */
  	has_crc = hash_data_extract_binary_checksum(record->exphash, HASH_CRC, crcs);
  	if (has_crc)
  		crc = (crcs[0] << 24) | (crcs[1] << 16) | (crcs[2] << 8) | crcs[3];
  
  	/* find the file and checksum it, getting the file length along the way */
  	for (drv = gamedrv; drv != NULL; drv = driver_get_clone(drv))
  	{
  		file_error filerr;
  		mame_file *file;
  		astring *fname;
  
  		/* open the file if we can */
  		fname = astring_assemble_3(astring_alloc(), drv->name, PATH_SEPARATOR, ROM_GETNAME(rom));
  	    if (has_crc)
  			filerr = mame_fopen_crc_options(options, SEARCHPATH_ROM, astring_c(fname), crc, OPEN_FLAG_READ | OPEN_FLAG_NO_PRELOAD, &file);
  		else
  			filerr = mame_fopen_options(options, SEARCHPATH_ROM, astring_c(fname), OPEN_FLAG_READ | OPEN_FLAG_NO_PRELOAD, &file);
  		astring_free(fname);
  
  		/* if we got it, extract the hash and length */
  		if (filerr == FILERR_NONE)
  		{
  			hash_data_copy(record->hash, mame_fhash(file, validation));
  			record->length = (UINT32)mame_fsize(file);
  			mame_fclose(file);
  			break;
  		}
  	}
  
  	/* if not found, check the region as a backup */
  	if (record->length == 0 && regiontag != NULL)
  	{
  		file_error filerr;
  		mame_file *file;
  		astring *fname;
  
  		/* open the file if we can */
  		fname = astring_assemble_3(astring_alloc(), regiontag, PATH_SEPARATOR, ROM_GETNAME(rom));
  	    if (has_crc)
  			filerr = mame_fopen_crc_options(options, SEARCHPATH_ROM, astring_c(fname), crc, OPEN_FLAG_READ | OPEN_FLAG_NO_PRELOAD, &file);
  		else
  			filerr = mame_fopen_options(options, SEARCHPATH_ROM, astring_c(fname), OPEN_FLAG_READ | OPEN_FLAG_NO_PRELOAD, &file);
  		astring_free(fname);
  
  		/* if we got it, extract the hash and length */
  		if (filerr == FILERR_NONE)
  		{
  			hash_data_copy(record->hash, mame_fhash(file, validation));
  			record->length = (UINT32)mame_fsize(file);
  			mame_fclose(file);
  		}
  	}
  
  	/* if we failed to find the file, set the appropriate status */
  	if (record->length == 0)
  	{
  		const game_driver *parent;
  
  		/* no good dump */
  		if (hash_data_has_info(record->exphash, HASH_INFO_NO_DUMP))
  			set_status(record, AUDIT_STATUS_NOT_FOUND, SUBSTATUS_NOT_FOUND_NODUMP);
  
  		/* optional ROM */
  		else if (ROM_ISOPTIONAL(rom))
  			set_status(record, AUDIT_STATUS_NOT_FOUND, SUBSTATUS_NOT_FOUND_OPTIONAL);
  
  		/* not found and used by parent */
  		else if (rom_used_by_parent(gamedrv, rom, &parent))
  			set_status(record, AUDIT_STATUS_NOT_FOUND, (parent->flags & GAME_IS_BIOS_ROOT) ? SUBSTATUS_NOT_FOUND_BIOS : SUBSTATUS_NOT_FOUND_PARENT);
  
  		/* just plain old not found */
  		else
  			set_status(record, AUDIT_STATUS_NOT_FOUND, SUBSTATUS_NOT_FOUND);
  	}
  
  	/* if we did find the file, do additional verification */
  	else
  	{
  		/* length mismatch */
  		if (record->explength != record->length)
  			set_status(record, AUDIT_STATUS_FOUND_INVALID, SUBSTATUS_FOUND_WRONG_LENGTH);
  
  		/* found but needs a dump */
  		else if (hash_data_has_info(record->exphash, HASH_INFO_NO_DUMP))
  			set_status(record, AUDIT_STATUS_GOOD, SUBSTATUS_FOUND_NODUMP);
  
  		/* incorrect hash */
  		else if (!hash_data_is_equal(record->exphash, record->hash, 0))
  			set_status(record, AUDIT_STATUS_FOUND_INVALID, SUBSTATUS_FOUND_BAD_CHECKSUM);
  
  		/* correct hash but needs a redump */
  		else if (hash_data_has_info(record->exphash, HASH_INFO_BAD_DUMP))
  			set_status(record, AUDIT_STATUS_GOOD, SUBSTATUS_GOOD_NEEDS_REDUMP);
  
  		/* just plain old good */
  		else
  			set_status(record, AUDIT_STATUS_GOOD, SUBSTATUS_GOOD);
  	}
  }
  
  
  /*-------------------------------------------------
      audit_one_disk - validate a single disk entry
  -------------------------------------------------*/
  
  static void audit_one_disk(core_options *options, const rom_entry *rom, const game_driver *gamedrv, UINT32 validation, audit_record *record)
  {
  	mame_file *source_file;
  	chd_file *source;
  	chd_error err;
  
  	/* fill in the record basics */
  	record->type = AUDIT_FILE_DISK;
  	record->name = ROM_GETNAME(rom);
  	record->exphash = ROM_GETHASHDATA(rom);
  
  	/* open the disk */
  	err = open_disk_image_options(options, gamedrv, rom, &source_file, &source);
  
  	/* if we failed, report the error */
  	if (err != CHDERR_NONE)
  	{
  		/* out of memory */
  		if (err == CHDERR_OUT_OF_MEMORY)
  			set_status(record, AUDIT_STATUS_ERROR, SUBSTATUS_ERROR);
  
  		/* not found but it's not good anyway */
  		else if (hash_data_has_info(record->exphash, HASH_INFO_NO_DUMP))
  			set_status(record, AUDIT_STATUS_NOT_FOUND, SUBSTATUS_NOT_FOUND_NODUMP);
  
  		/* not found but optional */
  		else if (DISK_ISOPTIONAL(rom))
  			set_status(record, AUDIT_STATUS_NOT_FOUND, SUBSTATUS_NOT_FOUND_OPTIONAL);
  
  		/* not found at all */
  		else
  			set_status(record, AUDIT_STATUS_NOT_FOUND, SUBSTATUS_NOT_FOUND);
  	}
  
  	/* if we succeeded, validate it */
  	else
  	{
  		static const UINT8 nullhash[HASH_BUF_SIZE] = { 0 };
  		chd_header header = *chd_get_header(source);
  
  		/* if there's an MD5 or SHA1 hash, add them to the output hash */
  		if (memcmp(nullhash, header.md5, sizeof(header.md5)) != 0)
  			hash_data_insert_binary_checksum(record->hash, HASH_MD5, header.md5);
  		if (memcmp(nullhash, header.sha1, sizeof(header.sha1)) != 0)
  			hash_data_insert_binary_checksum(record->hash, HASH_SHA1, header.sha1);
  
  		/* found but needs a dump */
  		if (hash_data_has_info(record->exphash, HASH_INFO_NO_DUMP))
  			set_status(record, AUDIT_STATUS_GOOD, SUBSTATUS_FOUND_NODUMP);
  
  		/* incorrect hash */
  		else if (!hash_data_is_equal(record->exphash, record->hash, 0))
  			set_status(record, AUDIT_STATUS_FOUND_INVALID, SUBSTATUS_FOUND_BAD_CHECKSUM);
  
  		/* correct hash but needs a redump */
  		else if (hash_data_has_info(record->exphash, HASH_INFO_BAD_DUMP))
  			set_status(record, AUDIT_STATUS_GOOD, SUBSTATUS_GOOD_NEEDS_REDUMP);
  
  		/* just plain good */
  		else
  			set_status(record, AUDIT_STATUS_GOOD, SUBSTATUS_GOOD);
  
  		chd_close(source);
  		mame_fclose(source_file);
  	}
  }
  
  
  /*-------------------------------------------------
      rom_used_by_parent - determine if a given
      ROM is also used by the parent
  -------------------------------------------------*/
  
  static int rom_used_by_parent(const game_driver *gamedrv, const rom_entry *romentry, const game_driver **parent)
  {
  	const char *hash = ROM_GETHASHDATA(romentry);
  	const game_driver *drv;
  
  	/* iterate up the parent chain */
  	for (drv = driver_get_clone(gamedrv); drv != NULL; drv = driver_get_clone(drv))
  	{
  		const rom_entry *region;
  		const rom_entry *rom;
  
  		/* see if the parent has the same ROM or not */
  		for (region = rom_first_region(drv, NULL); region; region = rom_next_region(region))
  			for (rom = rom_first_file(region); rom; rom = rom_next_file(rom))
  				if (hash_data_is_equal(ROM_GETHASHDATA(rom), hash, 0))
  				{
  					if (parent != NULL)
  						*parent = drv;
  					return TRUE;
  				}
  	}
  
  	return FALSE;
  }
  