summaryrefslogtreecommitdiffstatshomepage
path: root/src/emu/hash.c
blob: 5c3a293d6042a4a1991320559e734ca77666413c (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
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);
}