mirror of
https://github.com/holub/mame
synced 2025-06-04 20:06:28 +03:00
714 lines
16 KiB
C
714 lines
16 KiB
C
/*********************************************************************
|
|
|
|
hash.c
|
|
|
|
Function to handle hash functions (checksums)
|
|
|
|
Copyright Nicola Salmoria and the MAME Team.
|
|
Visit http://mamedev.org for licensing and usage restrictions.
|
|
|
|
Started by Farfetch'd
|
|
|
|
*********************************************************************/
|
|
|
|
/*
|
|
* Changelog:
|
|
*
|
|
* 20030314: Farfetch'd
|
|
* First release
|
|
*
|
|
*/
|
|
|
|
/*
|
|
* DONE:
|
|
*
|
|
* hash.c/h: New files, implement the new hashing engine with flexible
|
|
* support for more functions (for now, CRC, SHA1 and MD5).
|
|
*
|
|
* common.h: transparently support the new RomModule structure through
|
|
* ROM_* macros, so that old the legacy code still work
|
|
*
|
|
* common.c: updated ROM loading engine to support the new hash engine,
|
|
* using it to verify ROM integrity at load-time. Updated printromlist()
|
|
* (-listroms) to dump all the available checksums, and if a ROM is
|
|
* known to be bad or not.
|
|
*
|
|
* info.c: -listinfo now supports any hashing function correctly
|
|
* (both text and XML mode). Notice that XML header should be
|
|
* rewritten to automatically define the new tags when new
|
|
* functions are added, but I couldn't be bothered for now.
|
|
* It also displays informations about baddump/nodump
|
|
*
|
|
* audit.c/h: Updated audit engine (-verifyroms) to use the new
|
|
* hash functions.
|
|
*
|
|
* fileio.c/h: Updated file engine to use the new hash functions.
|
|
* It is now possible to load by any specified checksum (in case
|
|
* later we support other archivers with SHA1 signatures or
|
|
* equivalent). If the file is open with flag VERIFY_ONLY and
|
|
* the file is within an archive (zip), only the checksums
|
|
* available in the archive header are used.
|
|
*
|
|
* windows/fronthlp.c: Updated -identrom to the new hash engine, now
|
|
* support any hash function available.
|
|
* Added -listsha1 and -listmd5. It would be possible to add
|
|
* also a -listbad now, to list bad dumps (ROMS we need a
|
|
* redump for)
|
|
* Updated -listdupcrc to check for all the available checksums.
|
|
* The output is also a bit more useful and readable, even if it
|
|
* is still not optimal.
|
|
* Update -listwrongmerge to check for all the available checksums.
|
|
*
|
|
* windows/fileio.c: Removed check for FILE_TYPE_NOCRC (does not exist
|
|
* anymore).
|
|
*
|
|
*
|
|
*
|
|
* Technical details:
|
|
*
|
|
* Checksum informations are now stored inside a string. They are
|
|
* stored in "printable hex format", which means that they use
|
|
* more memory than before (since a CRC needs 8 characters to
|
|
* be printed, instead of 4 bytes of raw information). In the
|
|
* driver, they are defined with handy macros which rely on
|
|
* automatic string pasting.
|
|
*
|
|
* Additional flags can also be stored in the string: for now we
|
|
* support NO_DUMP and BAD_DUMP, which replace, respectively,
|
|
* a CRC of 0 and a bit-inverted CRC.
|
|
*
|
|
* All the code that handles hash data is in hash.c. The rest of
|
|
* the core treats the data as an 'opaque type', so that the
|
|
* pointers are just passed along through functions but no
|
|
* operation is performed on the data outside hash.c. This
|
|
* is important in case we want to change the string
|
|
* representation later in the future.
|
|
*
|
|
* When loading a ROM, MAME will calculate and compare the
|
|
* checksum using any function for which the driver has declared
|
|
* an expected checksum. This happens because it would be useless
|
|
* to calculate a checksum if we cannot verify its correctness.
|
|
* For developers, it also means that MAME will not compute the
|
|
* SHA1 for you unless you specify a bogus one in the driver
|
|
* (like SHA1(0)).
|
|
*
|
|
* When verifying a ROM, MAME will use only the checksums available
|
|
* in the archive header (if zip, CRC). This is by design because
|
|
* -verifyroms has always been very fast. It is feasible to add
|
|
* a -fullverifyroms at a later moment, which will decompress the
|
|
* files and compute every checksum that has been declared in the
|
|
* driver.
|
|
*
|
|
* I have also prepared a little tool (SHA1Merger) which takes care
|
|
* of the following tasks:
|
|
*
|
|
* - Given an existing driver in old syntax (0.66 compatible), it will
|
|
* convert all the existing ROM_LOAD entries in the new format, and
|
|
* it will automatically compute and add SHA1 checksum for you if
|
|
* it can find the romset.
|
|
*
|
|
* - Given a romset (ZIP file), it will prepare a ROM definition
|
|
* skeleton for a driver, containing already rom names, lengths, and
|
|
* checksums (both CRC and SHA1).
|
|
*
|
|
* The tool is available on www.mame.net as platform-independent source code
|
|
* (in Python), or win32 standalone executable.
|
|
*
|
|
*/
|
|
|
|
#include <stddef.h>
|
|
#include <ctype.h>
|
|
#include <zlib.h>
|
|
#include "hash.h"
|
|
#include "md5.h"
|
|
#include "sha1.h"
|
|
#include "mame.h"
|
|
#include "romload.h"
|
|
|
|
#define ASSERT(x)
|
|
|
|
#ifndef TRUE
|
|
#define TRUE 1
|
|
#endif
|
|
|
|
#ifndef FALSE
|
|
#define FALSE 0
|
|
#endif
|
|
|
|
struct _hash_function_desc
|
|
{
|
|
const char* name; // human-readable name
|
|
char code; // single-char code used within the hash string
|
|
unsigned int size; // checksum size in bytes
|
|
|
|
// Functions used to calculate the hash of a memory block
|
|
void (*calculate_begin)(void);
|
|
void (*calculate_buffer)(const void* mem, unsigned long len);
|
|
void (*calculate_end)(UINT8* bin_chksum);
|
|
|
|
};
|
|
typedef struct _hash_function_desc hash_function_desc;
|
|
|
|
static void h_crc_begin(void);
|
|
static void h_crc_buffer(const void* mem, unsigned long len);
|
|
static void h_crc_end(UINT8* chksum);
|
|
|
|
static void h_sha1_begin(void);
|
|
static void h_sha1_buffer(const void* mem, unsigned long len);
|
|
static void h_sha1_end(UINT8* chksum);
|
|
|
|
static void h_md5_begin(void);
|
|
static void h_md5_buffer(const void* mem, unsigned long len);
|
|
static void h_md5_end(UINT8* chksum);
|
|
|
|
static const hash_function_desc hash_descs[HASH_NUM_FUNCTIONS] =
|
|
{
|
|
{
|
|
"crc", 'c', 4,
|
|
h_crc_begin,
|
|
h_crc_buffer,
|
|
h_crc_end
|
|
},
|
|
|
|
{
|
|
"sha1", 's', 20,
|
|
h_sha1_begin,
|
|
h_sha1_buffer,
|
|
h_sha1_end
|
|
},
|
|
|
|
{
|
|
"md5", 'm', 16,
|
|
h_md5_begin,
|
|
h_md5_buffer,
|
|
h_md5_end
|
|
},
|
|
};
|
|
|
|
static const char *const info_strings[] =
|
|
{
|
|
"$ND$", // No dump
|
|
"$BD$" // Bad dump
|
|
};
|
|
|
|
static const char binToStr[] = "0123456789abcdef";
|
|
|
|
|
|
static const hash_function_desc* hash_get_function_desc(unsigned int function)
|
|
{
|
|
unsigned int idx = 0;
|
|
|
|
// Calling with zero in here is mostly an internal error
|
|
ASSERT(function != 0);
|
|
|
|
// Compute the index of only one function
|
|
while (!(function & 1))
|
|
{
|
|
idx++;
|
|
function >>= 1;
|
|
}
|
|
|
|
// Specify only one bit or die
|
|
ASSERT(function == 1);
|
|
|
|
return &hash_descs[idx];
|
|
}
|
|
|
|
const char* hash_function_name(unsigned int function)
|
|
{
|
|
const hash_function_desc* info = hash_get_function_desc(function);
|
|
|
|
return info->name;
|
|
}
|
|
|
|
int hash_data_has_checksum(const char* data, unsigned int function)
|
|
{
|
|
const hash_function_desc* info = hash_get_function_desc(function);
|
|
char str[3];
|
|
const char* res;
|
|
|
|
str[0] = info->code;
|
|
str[1] = ':';
|
|
str[2] = '\0';
|
|
|
|
// Check if the specified hash function is used within this data
|
|
res = strstr(data, str);
|
|
|
|
if (!res)
|
|
return 0;
|
|
|
|
// Return the offset within the string where the checksum begins
|
|
return (res - data + 2);
|
|
}
|
|
|
|
static int hash_data_add_binary_checksum(char* d, unsigned int function, const UINT8* checksum)
|
|
{
|
|
const hash_function_desc* desc = hash_get_function_desc(function);
|
|
char* start = d;
|
|
unsigned i;
|
|
|
|
*d++ = desc->code;
|
|
*d++ = ':';
|
|
|
|
for (i=0;i<desc->size;i++)
|
|
{
|
|
UINT8 c = *checksum++;
|
|
|
|
*d++ = binToStr[(c >> 4) & 0xF];
|
|
*d++ = binToStr[(c >> 0) & 0xF];
|
|
}
|
|
|
|
*d++ = '#';
|
|
|
|
// Return the number of written bytes
|
|
return (d - start);
|
|
}
|
|
|
|
|
|
static int hash_compare_checksum(const char* chk1, const char* chk2, int length)
|
|
{
|
|
char c1, c2;
|
|
|
|
// The printable format is twice as longer
|
|
length *= 2;
|
|
|
|
// This is basically a case-insensitive string compare
|
|
while (length--)
|
|
{
|
|
c1 = *chk1++;
|
|
c2 = *chk2++;
|
|
|
|
if (tolower((UINT8)c1) != tolower((UINT8)c2))
|
|
return 0;
|
|
if (!c1)
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
// Compare two hashdata
|
|
int hash_data_is_equal(const char* d1, const char* d2, unsigned int functions)
|
|
{
|
|
int i;
|
|
char incomplete = 0;
|
|
char ok = 0;
|
|
|
|
// If no function is specified, it means we need to check for all
|
|
// of them
|
|
if (!functions)
|
|
functions = ~functions;
|
|
|
|
for (i=1; i != (1<<HASH_NUM_FUNCTIONS); i<<=1)
|
|
if (functions & i)
|
|
{
|
|
int offs1, offs2;
|
|
|
|
// Check if both hashdata contain the current function's checksum
|
|
offs1 = hash_data_has_checksum(d1, i);
|
|
offs2 = hash_data_has_checksum(d2, i);
|
|
|
|
if (offs1 && offs2)
|
|
{
|
|
const hash_function_desc* info = hash_get_function_desc(i);
|
|
|
|
if (!hash_compare_checksum(d1+offs1, d2+offs2, info->size))
|
|
return 0;
|
|
|
|
ok = 1;
|
|
}
|
|
// If the function was contained only in one, remember that our comparison
|
|
// is incomplete
|
|
else if (offs1 || offs2)
|
|
{
|
|
incomplete = 1;
|
|
}
|
|
}
|
|
|
|
// If we could not compare any function, return error
|
|
if (!ok)
|
|
return 0;
|
|
|
|
// Return success code
|
|
return (incomplete ? 2 : 1);
|
|
}
|
|
|
|
|
|
int hash_data_extract_printable_checksum(const char* data, unsigned int function, char* checksum)
|
|
{
|
|
unsigned int i;
|
|
const hash_function_desc* info;
|
|
int offs;
|
|
|
|
// Check if the hashdata contains the requested function
|
|
offs = hash_data_has_checksum(data, function);
|
|
|
|
if (!offs)
|
|
return 0;
|
|
|
|
// Move to the beginning of the checksum
|
|
data += offs;
|
|
|
|
info = hash_get_function_desc(function);
|
|
|
|
// Return the number of required bytes
|
|
if (!checksum)
|
|
return info->size*2+1;
|
|
|
|
// If the terminator is not found at the right position,
|
|
// return a full-zero checksum and warn about it. This is mainly
|
|
// for developers putting checksums of '0' or '1' to ask MAME
|
|
// to compute the correct values for them.
|
|
if (data[info->size*2] != '#')
|
|
{
|
|
memset(checksum, '0', info->size*2);
|
|
checksum[info->size*2] = '\0';
|
|
return 2;
|
|
}
|
|
|
|
// If it contains invalid hexadecimal characters,
|
|
// treat the checksum as zero and return warning
|
|
for (i=0;i<info->size*2;i++)
|
|
if (!(data[i]>='0' && data[i]<='9') &&
|
|
!(data[i]>='a' && data[i]<='f') &&
|
|
!(data[i]>='A' && data[i]<='F'))
|
|
{
|
|
memset(checksum, '0', info->size*2);
|
|
checksum[info->size*2] = '\0';
|
|
return 2;
|
|
}
|
|
|
|
// Copy the checksum (and make it lowercase)
|
|
for (i=0;i<info->size*2;i++)
|
|
checksum[i] = tolower((UINT8)data[i]);
|
|
|
|
checksum[info->size*2] = '\0';
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int hex_string_to_binary(unsigned char* binary, const char* data, int size)
|
|
{
|
|
unsigned int i;
|
|
char c;
|
|
|
|
for (i = 0; i < size * 2; i++)
|
|
{
|
|
c = tolower((UINT8)*data++);
|
|
|
|
if (c >= '0' && c <= '9')
|
|
c -= '0';
|
|
else if (c >= 'a' && c <= 'f')
|
|
c -= 'a' - 10;
|
|
else
|
|
return 1;
|
|
|
|
if (i % 2 == 0)
|
|
binary[i / 2] = c * 16;
|
|
else
|
|
binary[i / 2] += c;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int hash_data_extract_binary_checksum(const char* data, unsigned int function, unsigned char* checksum)
|
|
{
|
|
const hash_function_desc* info;
|
|
int offs;
|
|
|
|
// Check if the hashdata contains the requested function
|
|
offs = hash_data_has_checksum(data, function);
|
|
|
|
if (!offs)
|
|
return 0;
|
|
|
|
// Move to the beginning of the checksum
|
|
data += offs;
|
|
|
|
info = hash_get_function_desc(function);
|
|
|
|
// Return the number of required bytes
|
|
if (!checksum)
|
|
return info->size;
|
|
|
|
// Clear the checksum array
|
|
memset(checksum, 0, info->size);
|
|
|
|
// If the terminator is not found at the right position,
|
|
// return a full-zero checksum and warn about it. This is mainly
|
|
// for developers putting checksums of '0' or '1' to ask MAME
|
|
// to compute the correct values for them.
|
|
if (data[info->size*2] != '#')
|
|
{
|
|
memset(checksum, '\0', info->size);
|
|
return 2;
|
|
}
|
|
|
|
// Convert hex string into binary
|
|
if (hex_string_to_binary(checksum, data, info->size))
|
|
{
|
|
// Invalid character: the checksum is treated as zero,
|
|
// and a warning is returned
|
|
memset(checksum, '\0', info->size);
|
|
return 2;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
int hash_data_has_info(const char* data, unsigned int info)
|
|
{
|
|
char* res = (char*)strstr(data, info_strings[info]);
|
|
|
|
if (!res)
|
|
return 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
void hash_data_copy(char* dst, const char* src)
|
|
{
|
|
// Copying string is enough
|
|
strcpy(dst, src);
|
|
}
|
|
|
|
void hash_data_clear(char* dst)
|
|
{
|
|
// Clear the buffer
|
|
memset(dst, 0, HASH_BUF_SIZE);
|
|
}
|
|
|
|
unsigned int hash_data_used_functions(const char* data)
|
|
{
|
|
int i;
|
|
unsigned int res = 0;
|
|
|
|
if (!data)
|
|
return 0;
|
|
|
|
for (i=0;i<HASH_NUM_FUNCTIONS;i++)
|
|
if (hash_data_has_checksum(data, 1<<i))
|
|
res |= 1<<i;
|
|
|
|
return res;
|
|
}
|
|
|
|
int hash_data_insert_printable_checksum(char* d, unsigned int function, const char* checksum)
|
|
{
|
|
const hash_function_desc* desc;
|
|
UINT8 binary_checksum[20];
|
|
|
|
desc = hash_get_function_desc(function);
|
|
|
|
ASSERT(desc->size <= sizeof(binary_checksum));
|
|
|
|
if (hex_string_to_binary(binary_checksum, checksum, desc->size))
|
|
return 2;
|
|
|
|
return hash_data_insert_binary_checksum(d, function, binary_checksum);
|
|
}
|
|
|
|
int hash_data_insert_binary_checksum(char* d, unsigned int function, const UINT8* checksum)
|
|
{
|
|
int offset;
|
|
|
|
offset = hash_data_has_checksum(d, function);
|
|
|
|
if (!offset)
|
|
{
|
|
d += strlen(d);
|
|
d += hash_data_add_binary_checksum(d, function, checksum);
|
|
*d = '\0';
|
|
|
|
return 1;
|
|
}
|
|
else
|
|
{
|
|
// Move to the start of the whole checksum signature, not only to the checksum
|
|
// itself
|
|
d += offset - 2;
|
|
|
|
// Overwrite previous checksum with new one
|
|
hash_data_add_binary_checksum(d, function, checksum);
|
|
|
|
return 2;
|
|
}
|
|
}
|
|
|
|
void hash_compute(char* dst, const unsigned char* data, unsigned long length, unsigned int functions)
|
|
{
|
|
int i;
|
|
|
|
hash_data_clear(dst);
|
|
|
|
// Zero means use all the functions
|
|
if (functions == 0)
|
|
functions = ~functions;
|
|
|
|
for (i=0;i<HASH_NUM_FUNCTIONS;i++)
|
|
{
|
|
unsigned func = 1 << i;
|
|
|
|
if (functions & func)
|
|
{
|
|
const hash_function_desc* desc = hash_get_function_desc(func);
|
|
UINT8 chksum[256];
|
|
|
|
desc->calculate_begin();
|
|
desc->calculate_buffer(data, length);
|
|
desc->calculate_end(chksum);
|
|
|
|
dst += hash_data_add_binary_checksum(dst, func, chksum);
|
|
}
|
|
}
|
|
|
|
*dst = '\0';
|
|
}
|
|
|
|
void hash_data_print(const char* data, unsigned int functions, char* buffer)
|
|
{
|
|
int i, j;
|
|
char first = 1;
|
|
|
|
if (functions == 0)
|
|
functions = ~functions;
|
|
|
|
buffer[0] = '\0';
|
|
|
|
for (i=0;i<HASH_NUM_FUNCTIONS;i++)
|
|
{
|
|
unsigned func = 1 << i;
|
|
|
|
if ((functions & func) && hash_data_has_checksum(data, func))
|
|
{
|
|
char temp[256];
|
|
|
|
if (!first)
|
|
strcat(buffer, " ");
|
|
first = 0;
|
|
|
|
strcpy(temp, hash_function_name(func));
|
|
for (j = 0; temp[j]; j++)
|
|
temp[j] = toupper((UINT8)temp[j]);
|
|
strcat(buffer, temp);
|
|
strcat(buffer, "(");
|
|
|
|
hash_data_extract_printable_checksum(data, func, temp);
|
|
strcat(buffer, temp);
|
|
strcat(buffer, ")");
|
|
}
|
|
}
|
|
}
|
|
|
|
int hash_verify_string(const char *hash)
|
|
{
|
|
int len, i;
|
|
|
|
if (!hash)
|
|
return FALSE;
|
|
|
|
while(*hash)
|
|
{
|
|
if (*hash == '$')
|
|
{
|
|
if (memcmp(hash, NO_DUMP, 4) && memcmp(hash, BAD_DUMP, 4))
|
|
return FALSE;
|
|
hash += 4;
|
|
}
|
|
else
|
|
{
|
|
/* first make sure that the next char is a colon */
|
|
if (hash[1] != ':')
|
|
return FALSE;
|
|
|
|
/* search for a hash function for this code */
|
|
for (i = 0; i < sizeof(hash_descs) / sizeof(hash_descs[0]); i++)
|
|
{
|
|
if (*hash == hash_descs[i].code)
|
|
break;
|
|
}
|
|
if (i >= sizeof(hash_descs) / sizeof(hash_descs[0]))
|
|
return FALSE;
|
|
|
|
/* we have a proper code */
|
|
len = hash_descs[i].size * 2;
|
|
hash += 2;
|
|
|
|
for (i = 0; (hash[i] != '#') && (i < len); i++)
|
|
{
|
|
if (!isxdigit((UINT8)hash[i]))
|
|
return FALSE;
|
|
}
|
|
if (hash[i] != '#')
|
|
return FALSE;
|
|
|
|
hash += i+1;
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
/*********************************************************************
|
|
Hash functions - Wrappers
|
|
*********************************************************************/
|
|
|
|
static UINT32 crc;
|
|
|
|
static void h_crc_begin(void)
|
|
{
|
|
crc = 0;
|
|
}
|
|
|
|
static void h_crc_buffer(const void* mem, unsigned long len)
|
|
{
|
|
crc = crc32(crc, (UINT8*)mem, len);
|
|
}
|
|
|
|
static void h_crc_end(UINT8* bin_chksum)
|
|
{
|
|
bin_chksum[0] = (UINT8)(crc >> 24);
|
|
bin_chksum[1] = (UINT8)(crc >> 16);
|
|
bin_chksum[2] = (UINT8)(crc >> 8);
|
|
bin_chksum[3] = (UINT8)(crc >> 0);
|
|
}
|
|
|
|
|
|
static struct sha1_ctx sha1ctx;
|
|
|
|
static void h_sha1_begin(void)
|
|
{
|
|
sha1_init(&sha1ctx);
|
|
}
|
|
|
|
static void h_sha1_buffer(const void* mem, unsigned long len)
|
|
{
|
|
sha1_update(&sha1ctx, len, (UINT8*)mem);
|
|
}
|
|
|
|
static void h_sha1_end(UINT8* bin_chksum)
|
|
{
|
|
sha1_final(&sha1ctx);
|
|
sha1_digest(&sha1ctx, 20, bin_chksum);
|
|
}
|
|
|
|
|
|
static struct MD5Context md5_ctx;
|
|
|
|
static void h_md5_begin(void)
|
|
{
|
|
MD5Init(&md5_ctx);
|
|
}
|
|
|
|
static void h_md5_buffer(const void* mem, unsigned long len)
|
|
{
|
|
MD5Update(&md5_ctx, (md5byte*)mem, len);
|
|
}
|
|
|
|
static void h_md5_end(UINT8* bin_chksum)
|
|
{
|
|
MD5Final(bin_chksum, &md5_ctx);
|
|
}
|