/********************************************************************* hash.c Function to handle hash functions (checksums) 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 -crconly to disable advanced integrity checks. * This should be needed for people with very slow computers * whose loading time is affected too much by the new hashing * calculations (hello, stephh). * This also means that for -identrom MAME will not have to * decompress the ROM from the ZIP to calculate checksum * informations, since the CRC will be extracted from the header. * 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. * * mame.h: Added new field crc_only to struct GameOptions. * * windows/config.c: Added new option -crconly * * 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 #include #include #include #include #include "hash.h" #include #include "mame_sha1.h" #include "osd_cpu.h" #include "mame.h" #include "common.h" #define ASSERT(x) typedef struct { 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); } 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 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 }, }; const char* info_strings[] = { "$ND$", /* No dump*/ "$BD$" /* Bad dump*/ }; static const char* binToStr = "0123456789abcdef"; static 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) { hash_function_desc* info = hash_get_function_desc(function); return info->name; } int hash_data_has_checksum(const char* data, unsigned int function) { 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, UINT8* checksum) { hash_function_desc* desc = hash_get_function_desc(function); char* start = d; unsigned i; *d++ = desc->code; *d++ = ':'; for (i=0;isize;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<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; 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;isize*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;isize*2;i++) checksum[i] = tolower(data[i]); checksum[info->size*2] = '\0'; return 1; } int hash_data_extract_binary_checksum(const char* data, unsigned int function, unsigned char* checksum) { unsigned int i; 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*/ for (i=0;isize*2;i++) { char c = tolower(*data++); if (c >= '0' && c <= '9') c -= '0'; else if (c >= 'a' && c <= 'f') c -= 'a' - 10; else if (c >= 'A' && c <= 'F') c -= 'A' - 10; else { /* Invalid character: the checksum is treated as zero,*/ /* and a warning is returned*/ memset(checksum, '\0', info->size); return 2; } if (i % 2 == 0) checksum[i / 2] = c * 16; else checksum[i / 2] += c; } 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;icalculate_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= sizeof(hash_descs) / sizeof(hash_descs[0])) return 0; /* 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 0; } if (hash[i] != '#') return 0; hash += i+1; } } return 1; } /********************************************************************* 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); } 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 MD5_CTX md5; static void h_md5_begin(void) { #ifndef HAVE_LIBNX // Add hw crypto later, works without MD5_Init(&md5); #endif } static void h_md5_buffer(const void* mem, unsigned long len) { #ifndef HAVE_LIBNX // Add hw crypto later, works without MD5_Update(&md5, (md5byte*)mem, len); #endif } static void h_md5_end(UINT8* bin_chksum) { #ifndef HAVE_LIBNX // Add hw crypto later, works without MD5_Final(bin_chksum, &md5); #endif }