summaryrefslogtreecommitdiffstatshomepage
path: root/src/emu/hash.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/emu/hash.c')
-rw-r--r--src/emu/hash.c713
1 files changed, 713 insertions, 0 deletions
diff --git a/src/emu/hash.c b/src/emu/hash.c
new file mode 100644
index 00000000000..5c3a293d604
--- /dev/null
+++ b/src/emu/hash.c
@@ -0,0 +1,713 @@
+/*********************************************************************
+
+ hash.c
+
+ Function to handle hash functions (checksums)
+
+ Copyright (c) 1996-2007, 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* 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(c1) != tolower(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(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(*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 = 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(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(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);
+}