hash.c (107269B)
1 #include "rc_hash.h" 2 3 #include "../rc_compat.h" 4 5 #include "md5.h" 6 7 #include <stdio.h> 8 #include <ctype.h> 9 10 #if defined(_WIN32) 11 #define WIN32_LEAN_AND_MEAN 12 #include <windows.h> 13 #include <share.h> 14 #endif 15 16 /* arbitrary limit to prevent allocating and hashing large files */ 17 #define MAX_BUFFER_SIZE 64 * 1024 * 1024 18 19 const char* rc_path_get_filename(const char* path); 20 static int rc_hash_whole_file(char hash[33], const char* path); 21 22 /* ===================================================== */ 23 24 static rc_hash_message_callback error_message_callback = NULL; 25 rc_hash_message_callback verbose_message_callback = NULL; 26 27 void rc_hash_init_error_message_callback(rc_hash_message_callback callback) 28 { 29 error_message_callback = callback; 30 } 31 32 int rc_hash_error(const char* message) 33 { 34 if (error_message_callback) 35 error_message_callback(message); 36 37 return 0; 38 } 39 40 void rc_hash_init_verbose_message_callback(rc_hash_message_callback callback) 41 { 42 verbose_message_callback = callback; 43 } 44 45 static void rc_hash_verbose(const char* message) 46 { 47 if (verbose_message_callback) 48 verbose_message_callback(message); 49 } 50 51 /* ===================================================== */ 52 53 static struct rc_hash_filereader filereader_funcs; 54 static struct rc_hash_filereader* filereader = NULL; 55 56 #if defined(WINVER) && WINVER >= 0x0500 57 static void* filereader_open(const char* path) 58 { 59 /* Windows requires using wchar APIs for Unicode paths */ 60 /* Note that MultiByteToWideChar will only be defined for >= Windows 2000 */ 61 wchar_t* wpath; 62 int wpath_length; 63 FILE* fp; 64 65 /* Calculate wpath length from path */ 66 wpath_length = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, path, -1, NULL, 0); 67 if (wpath_length == 0) /* 0 indicates error (this is likely from invalid UTF-8) */ 68 return NULL; 69 70 wpath = (wchar_t*)malloc(wpath_length * sizeof(wchar_t)); 71 if (!wpath) 72 return NULL; 73 74 if (MultiByteToWideChar(CP_UTF8, 0, path, -1, wpath, wpath_length) == 0) 75 { 76 free(wpath); 77 return NULL; 78 } 79 80 #if defined(__STDC_WANT_SECURE_LIB__) 81 /* have to use _SH_DENYNO because some cores lock the file while its loaded */ 82 fp = _wfsopen(wpath, L"rb", _SH_DENYNO); 83 #else 84 fp = _wfopen(wpath, L"rb"); 85 #endif 86 87 free(wpath); 88 return fp; 89 } 90 #else /* !WINVER >= 0x0500 */ 91 static void* filereader_open(const char* path) 92 { 93 #if defined(__STDC_WANT_SECURE_LIB__) 94 #if defined(WINVER) 95 /* have to use _SH_DENYNO because some cores lock the file while its loaded */ 96 return _fsopen(path, "rb", _SH_DENYNO); 97 #else /* !WINVER */ 98 FILE *fp; 99 fopen_s(&fp, path, "rb"); 100 return fp; 101 #endif 102 #else /* !__STDC_WANT_SECURE_LIB__ */ 103 return fopen(path, "rb"); 104 #endif 105 } 106 #endif /* WINVER >= 0x0500 */ 107 108 static void filereader_seek(void* file_handle, int64_t offset, int origin) 109 { 110 #if defined(_WIN32) 111 _fseeki64((FILE*)file_handle, offset, origin); 112 #elif defined(_LARGEFILE64_SOURCE) 113 fseeko64((FILE*)file_handle, offset, origin); 114 #else 115 fseek((FILE*)file_handle, offset, origin); 116 #endif 117 } 118 119 static int64_t filereader_tell(void* file_handle) 120 { 121 #if defined(_WIN32) 122 return _ftelli64((FILE*)file_handle); 123 #elif defined(_LARGEFILE64_SOURCE) 124 return ftello64((FILE*)file_handle); 125 #else 126 return ftell((FILE*)file_handle); 127 #endif 128 } 129 130 static size_t filereader_read(void* file_handle, void* buffer, size_t requested_bytes) 131 { 132 return fread(buffer, 1, requested_bytes, (FILE*)file_handle); 133 } 134 135 static void filereader_close(void* file_handle) 136 { 137 fclose((FILE*)file_handle); 138 } 139 140 /* for unit tests - normally would call rc_hash_init_custom_filereader(NULL) */ 141 void rc_hash_reset_filereader(void) 142 { 143 filereader = NULL; 144 } 145 146 void rc_hash_init_custom_filereader(struct rc_hash_filereader* reader) 147 { 148 /* initialize with defaults first */ 149 filereader_funcs.open = filereader_open; 150 filereader_funcs.seek = filereader_seek; 151 filereader_funcs.tell = filereader_tell; 152 filereader_funcs.read = filereader_read; 153 filereader_funcs.close = filereader_close; 154 155 /* hook up any provided custom handlers */ 156 if (reader) { 157 if (reader->open) 158 filereader_funcs.open = reader->open; 159 160 if (reader->seek) 161 filereader_funcs.seek = reader->seek; 162 163 if (reader->tell) 164 filereader_funcs.tell = reader->tell; 165 166 if (reader->read) 167 filereader_funcs.read = reader->read; 168 169 if (reader->close) 170 filereader_funcs.close = reader->close; 171 } 172 173 filereader = &filereader_funcs; 174 } 175 176 void* rc_file_open(const char* path) 177 { 178 void* handle; 179 180 if (!filereader) 181 { 182 rc_hash_init_custom_filereader(NULL); 183 if (!filereader) 184 return NULL; 185 } 186 187 handle = filereader->open(path); 188 if (handle && verbose_message_callback) 189 { 190 char message[1024]; 191 snprintf(message, sizeof(message), "Opened %s", rc_path_get_filename(path)); 192 verbose_message_callback(message); 193 } 194 195 return handle; 196 } 197 198 void rc_file_seek(void* file_handle, int64_t offset, int origin) 199 { 200 if (filereader) 201 filereader->seek(file_handle, offset, origin); 202 } 203 204 int64_t rc_file_tell(void* file_handle) 205 { 206 return (filereader) ? filereader->tell(file_handle) : 0; 207 } 208 209 size_t rc_file_read(void* file_handle, void* buffer, int requested_bytes) 210 { 211 return (filereader) ? filereader->read(file_handle, buffer, requested_bytes) : 0; 212 } 213 214 void rc_file_close(void* file_handle) 215 { 216 if (filereader) 217 filereader->close(file_handle); 218 } 219 220 /* ===================================================== */ 221 222 static struct rc_hash_cdreader cdreader_funcs; 223 struct rc_hash_cdreader* cdreader = NULL; 224 225 void rc_hash_init_custom_cdreader(struct rc_hash_cdreader* reader) 226 { 227 if (reader) 228 { 229 memcpy(&cdreader_funcs, reader, sizeof(cdreader_funcs)); 230 cdreader = &cdreader_funcs; 231 } 232 else 233 { 234 cdreader = NULL; 235 } 236 } 237 238 static void* rc_cd_open_track(const char* path, uint32_t track) 239 { 240 if (cdreader && cdreader->open_track) 241 return cdreader->open_track(path, track); 242 243 rc_hash_error("no hook registered for cdreader_open_track"); 244 return NULL; 245 } 246 247 static size_t rc_cd_read_sector(void* track_handle, uint32_t sector, void* buffer, size_t requested_bytes) 248 { 249 if (cdreader && cdreader->read_sector) 250 return cdreader->read_sector(track_handle, sector, buffer, requested_bytes); 251 252 rc_hash_error("no hook registered for cdreader_read_sector"); 253 return 0; 254 } 255 256 static uint32_t rc_cd_first_track_sector(void* track_handle) 257 { 258 if (cdreader && cdreader->first_track_sector) 259 return cdreader->first_track_sector(track_handle); 260 261 rc_hash_error("no hook registered for cdreader_first_track_sector"); 262 return 0; 263 } 264 265 static void rc_cd_close_track(void* track_handle) 266 { 267 if (cdreader && cdreader->close_track) 268 { 269 cdreader->close_track(track_handle); 270 return; 271 } 272 273 rc_hash_error("no hook registered for cdreader_close_track"); 274 } 275 276 static uint32_t rc_cd_find_file_sector(void* track_handle, const char* path, uint32_t* size) 277 { 278 uint8_t buffer[2048], *tmp; 279 int sector; 280 uint32_t num_sectors = 0; 281 size_t filename_length; 282 const char* slash; 283 284 if (!track_handle) 285 return 0; 286 287 /* we start at the root. don't need to explicitly find it */ 288 if (*path == '\\') 289 ++path; 290 291 filename_length = strlen(path); 292 slash = strrchr(path, '\\'); 293 if (slash) 294 { 295 /* find the directory record for the first part of the path */ 296 memcpy(buffer, path, slash - path); 297 buffer[slash - path] = '\0'; 298 299 sector = rc_cd_find_file_sector(track_handle, (const char *)buffer, NULL); 300 if (!sector) 301 return 0; 302 303 ++slash; 304 filename_length -= (slash - path); 305 path = slash; 306 } 307 else 308 { 309 uint32_t logical_block_size; 310 311 /* find the cd information */ 312 if (!rc_cd_read_sector(track_handle, rc_cd_first_track_sector(track_handle) + 16, buffer, 256)) 313 return 0; 314 315 /* the directory_record starts at 156, the sector containing the table of contents is 2 bytes into that. 316 * https://www.cdroller.com/htm/readdata.html 317 */ 318 sector = buffer[156 + 2] | (buffer[156 + 3] << 8) | (buffer[156 + 4] << 16); 319 320 /* if the table of contents spans more than one sector, it's length of section will exceed it's logical block size */ 321 logical_block_size = (buffer[128] | (buffer[128 + 1] << 8)); /* logical block size */ 322 if (logical_block_size == 0) { 323 num_sectors = 1; 324 } else { 325 num_sectors = (buffer[156 + 10] | (buffer[156 + 11] << 8) | (buffer[156 + 12] << 16) | (buffer[156 + 13] << 24)); /* length of section */ 326 num_sectors /= logical_block_size; 327 } 328 } 329 330 /* fetch and process the directory record */ 331 if (!rc_cd_read_sector(track_handle, sector, buffer, sizeof(buffer))) 332 return 0; 333 334 tmp = buffer; 335 do 336 { 337 if (tmp >= buffer + sizeof(buffer) || !*tmp) 338 { 339 /* end of this path table block. if the path table spans multiple sectors, keep scanning */ 340 if (num_sectors > 1) 341 { 342 --num_sectors; 343 if (rc_cd_read_sector(track_handle, ++sector, buffer, sizeof(buffer))) 344 { 345 tmp = buffer; 346 continue; 347 } 348 } 349 break; 350 } 351 352 /* filename is 33 bytes into the record and the format is "FILENAME;version" or "DIRECTORY" */ 353 if ((tmp[32] == filename_length || tmp[33 + filename_length] == ';') && 354 strncasecmp((const char*)(tmp + 33), path, filename_length) == 0) 355 { 356 sector = tmp[2] | (tmp[3] << 8) | (tmp[4] << 16); 357 358 if (verbose_message_callback) 359 { 360 char message[128]; 361 snprintf(message, sizeof(message), "Found %s at sector %d", path, sector); 362 verbose_message_callback(message); 363 } 364 365 if (size) 366 *size = tmp[10] | (tmp[11] << 8) | (tmp[12] << 16) | (tmp[13] << 24); 367 368 return sector; 369 } 370 371 /* the first byte of the record is the length of the record */ 372 tmp += *tmp; 373 } while (1); 374 375 return 0; 376 } 377 378 /* ===================================================== */ 379 380 const char* rc_path_get_filename(const char* path) 381 { 382 const char* ptr = path + strlen(path); 383 do 384 { 385 if (ptr[-1] == '/' || ptr[-1] == '\\') 386 break; 387 388 --ptr; 389 } while (ptr > path); 390 391 return ptr; 392 } 393 394 static const char* rc_path_get_extension(const char* path) 395 { 396 const char* ptr = path + strlen(path); 397 do 398 { 399 if (ptr[-1] == '.') 400 return ptr; 401 402 --ptr; 403 } while (ptr > path); 404 405 return path + strlen(path); 406 } 407 408 int rc_path_compare_extension(const char* path, const char* ext) 409 { 410 size_t path_len = strlen(path); 411 size_t ext_len = strlen(ext); 412 const char* ptr = path + path_len - ext_len; 413 if (ptr[-1] != '.') 414 return 0; 415 416 if (memcmp(ptr, ext, ext_len) == 0) 417 return 1; 418 419 do 420 { 421 if (tolower(*ptr) != *ext) 422 return 0; 423 424 ++ext; 425 ++ptr; 426 } while (*ptr); 427 428 return 1; 429 } 430 431 /* ===================================================== */ 432 433 static void rc_hash_byteswap16(uint8_t* buffer, const uint8_t* stop) 434 { 435 uint32_t* ptr = (uint32_t*)buffer; 436 const uint32_t* stop32 = (const uint32_t*)stop; 437 while (ptr < stop32) 438 { 439 uint32_t temp = *ptr; 440 temp = (temp & 0xFF00FF00) >> 8 | 441 (temp & 0x00FF00FF) << 8; 442 *ptr++ = temp; 443 } 444 } 445 446 static void rc_hash_byteswap32(uint8_t* buffer, const uint8_t* stop) 447 { 448 uint32_t* ptr = (uint32_t*)buffer; 449 const uint32_t* stop32 = (const uint32_t*)stop; 450 while (ptr < stop32) 451 { 452 uint32_t temp = *ptr; 453 temp = (temp & 0xFF000000) >> 24 | 454 (temp & 0x00FF0000) >> 8 | 455 (temp & 0x0000FF00) << 8 | 456 (temp & 0x000000FF) << 24; 457 *ptr++ = temp; 458 } 459 } 460 461 static int rc_hash_finalize(md5_state_t* md5, char hash[33]) 462 { 463 md5_byte_t digest[16]; 464 465 md5_finish(md5, digest); 466 467 /* NOTE: sizeof(hash) is 4 because it's still treated like a pointer, despite specifying a size */ 468 snprintf(hash, 33, "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", 469 digest[0], digest[1], digest[2], digest[3], digest[4], digest[5], digest[6], digest[7], 470 digest[8], digest[9], digest[10], digest[11], digest[12], digest[13], digest[14], digest[15] 471 ); 472 473 if (verbose_message_callback) 474 { 475 char message[128]; 476 snprintf(message, sizeof(message), "Generated hash %s", hash); 477 verbose_message_callback(message); 478 } 479 480 return 1; 481 } 482 483 static int rc_hash_buffer(char hash[33], const uint8_t* buffer, size_t buffer_size) 484 { 485 md5_state_t md5; 486 md5_init(&md5); 487 488 if (buffer_size > MAX_BUFFER_SIZE) 489 buffer_size = MAX_BUFFER_SIZE; 490 491 md5_append(&md5, buffer, (int)buffer_size); 492 493 if (verbose_message_callback) 494 { 495 char message[128]; 496 snprintf(message, sizeof(message), "Hashing %u byte buffer", (unsigned)buffer_size); 497 verbose_message_callback(message); 498 } 499 500 return rc_hash_finalize(&md5, hash); 501 } 502 503 static int rc_hash_cd_file(md5_state_t* md5, void* track_handle, uint32_t sector, const char* name, uint32_t size, const char* description) 504 { 505 uint8_t buffer[2048]; 506 size_t num_read; 507 508 if ((num_read = rc_cd_read_sector(track_handle, sector, buffer, sizeof(buffer))) < sizeof(buffer)) 509 { 510 char message[128]; 511 snprintf(message, sizeof(message), "Could not read %s", description); 512 return rc_hash_error(message); 513 } 514 515 if (size > MAX_BUFFER_SIZE) 516 size = MAX_BUFFER_SIZE; 517 518 if (verbose_message_callback) 519 { 520 char message[128]; 521 if (name) 522 snprintf(message, sizeof(message), "Hashing %s title (%u bytes) and contents (%u bytes) ", name, (unsigned)strlen(name), size); 523 else 524 snprintf(message, sizeof(message), "Hashing %s contents (%u bytes @ sector %u)", description, size, sector); 525 526 verbose_message_callback(message); 527 } 528 529 if (size < (unsigned)num_read) /* we read a whole sector - only hash the part containing file data */ 530 num_read = (size_t)size; 531 532 do 533 { 534 md5_append(md5, buffer, (int)num_read); 535 536 if (size <= (unsigned)num_read) 537 break; 538 size -= (unsigned)num_read; 539 540 ++sector; 541 if (size >= sizeof(buffer)) 542 num_read = rc_cd_read_sector(track_handle, sector, buffer, sizeof(buffer)); 543 else 544 num_read = rc_cd_read_sector(track_handle, sector, buffer, size); 545 } while (num_read > 0); 546 547 return 1; 548 } 549 550 static int rc_hash_3do(char hash[33], const char* path) 551 { 552 uint8_t buffer[2048]; 553 const uint8_t operafs_identifier[7] = { 0x01, 0x5A, 0x5A, 0x5A, 0x5A, 0x5A, 0x01 }; 554 void* track_handle; 555 md5_state_t md5; 556 int sector; 557 int block_size, block_location; 558 int offset, stop; 559 size_t size = 0; 560 561 track_handle = rc_cd_open_track(path, 1); 562 if (!track_handle) 563 return rc_hash_error("Could not open track"); 564 565 /* the Opera filesystem stores the volume information in the first 132 bytes of sector 0 566 * https://github.com/barbeque/3dodump/blob/master/OperaFS-Format.md 567 */ 568 rc_cd_read_sector(track_handle, 0, buffer, 132); 569 570 if (memcmp(buffer, operafs_identifier, sizeof(operafs_identifier)) == 0) 571 { 572 if (verbose_message_callback) 573 { 574 char message[128]; 575 snprintf(message, sizeof(message), "Found 3DO CD, title=%.32s", &buffer[0x28]); 576 verbose_message_callback(message); 577 } 578 579 /* include the volume header in the hash */ 580 md5_init(&md5); 581 md5_append(&md5, buffer, 132); 582 583 /* the block size is at offset 0x4C (assume 0x4C is always 0) */ 584 block_size = buffer[0x4D] * 65536 + buffer[0x4E] * 256 + buffer[0x4F]; 585 586 /* the root directory block location is at offset 0x64 (and duplicated several 587 * times, but we just look at the primary record) (assume 0x64 is always 0)*/ 588 block_location = buffer[0x65] * 65536 + buffer[0x66] * 256 + buffer[0x67]; 589 590 /* multiply the block index by the block size to get the real address */ 591 block_location *= block_size; 592 593 /* convert that to a sector and read it */ 594 sector = block_location / 2048; 595 596 do 597 { 598 rc_cd_read_sector(track_handle, sector, buffer, sizeof(buffer)); 599 600 /* offset to start of entries is at offset 0x10 (assume 0x10 and 0x11 are always 0) */ 601 offset = buffer[0x12] * 256 + buffer[0x13]; 602 603 /* offset to end of entries is at offset 0x0C (assume 0x0C is always 0) */ 604 stop = buffer[0x0D] * 65536 + buffer[0x0E] * 256 + buffer[0x0F]; 605 606 while (offset < stop) 607 { 608 if (buffer[offset + 0x03] == 0x02) /* file */ 609 { 610 if (strcasecmp((const char*)&buffer[offset + 0x20], "LaunchMe") == 0) 611 { 612 /* the block size is at offset 0x0C (assume 0x0C is always 0) */ 613 block_size = buffer[offset + 0x0D] * 65536 + buffer[offset + 0x0E] * 256 + buffer[offset + 0x0F]; 614 615 /* the block location is at offset 0x44 (assume 0x44 is always 0) */ 616 block_location = buffer[offset + 0x45] * 65536 + buffer[offset + 0x46] * 256 + buffer[offset + 0x47]; 617 block_location *= block_size; 618 619 /* the file size is at offset 0x10 (assume 0x10 is always 0) */ 620 size = (size_t)buffer[offset + 0x11] * 65536 + buffer[offset + 0x12] * 256 + buffer[offset + 0x13]; 621 622 if (verbose_message_callback) 623 { 624 char message[128]; 625 snprintf(message, sizeof(message), "Hashing header (%u bytes) and %.32s (%u bytes) ", 132, &buffer[offset + 0x20], (unsigned)size); 626 verbose_message_callback(message); 627 } 628 629 break; 630 } 631 } 632 633 /* the number of extra copies of the file is at offset 0x40 (assume 0x40-0x42 are always 0) */ 634 offset += 0x48 + buffer[offset + 0x43] * 4; 635 } 636 637 if (size != 0) 638 break; 639 640 /* did not find the file, see if the directory listing is continued in another sector */ 641 offset = buffer[0x02] * 256 + buffer[0x03]; 642 643 /* no more sectors to search*/ 644 if (offset == 0xFFFF) 645 break; 646 647 /* get next sector */ 648 offset *= block_size; 649 sector = (block_location + offset) / 2048; 650 } while (1); 651 652 if (size == 0) 653 { 654 rc_cd_close_track(track_handle); 655 return rc_hash_error("Could not find LaunchMe"); 656 } 657 658 sector = block_location / 2048; 659 660 while (size > 2048) 661 { 662 rc_cd_read_sector(track_handle, sector, buffer, sizeof(buffer)); 663 md5_append(&md5, buffer, sizeof(buffer)); 664 665 ++sector; 666 size -= 2048; 667 } 668 669 rc_cd_read_sector(track_handle, sector, buffer, size); 670 md5_append(&md5, buffer, (int)size); 671 } 672 else 673 { 674 rc_cd_close_track(track_handle); 675 return rc_hash_error("Not a 3DO CD"); 676 } 677 678 rc_cd_close_track(track_handle); 679 680 return rc_hash_finalize(&md5, hash); 681 } 682 683 static int rc_hash_7800(char hash[33], const uint8_t* buffer, size_t buffer_size) 684 { 685 /* if the file contains a header, ignore it */ 686 if (memcmp(&buffer[1], "ATARI7800", 9) == 0) 687 { 688 rc_hash_verbose("Ignoring 7800 header"); 689 690 buffer += 128; 691 buffer_size -= 128; 692 } 693 694 return rc_hash_buffer(hash, buffer, buffer_size); 695 } 696 697 struct rc_hash_zip_idx 698 { 699 size_t length; 700 uint8_t* data; 701 }; 702 703 struct rc_hash_ms_dos_dosz_state 704 { 705 const char* path; 706 const struct rc_hash_ms_dos_dosz_state* child; 707 }; 708 709 static int rc_hash_zip_idx_sort(const void* a, const void* b) 710 { 711 struct rc_hash_zip_idx *A = (struct rc_hash_zip_idx*)a, *B = (struct rc_hash_zip_idx*)b; 712 size_t len = (A->length < B->length ? A->length : B->length); 713 return memcmp(A->data, B->data, len); 714 } 715 716 static int rc_hash_ms_dos_parent(md5_state_t* md5, const struct rc_hash_ms_dos_dosz_state *child, const char* parentname, uint32_t parentname_len); 717 static int rc_hash_ms_dos_dosc(md5_state_t* md5, const struct rc_hash_ms_dos_dosz_state *dosz); 718 719 static int rc_hash_zip_file(md5_state_t* md5, void* file_handle, const struct rc_hash_ms_dos_dosz_state* dosz) 720 { 721 uint8_t buf[2048], *alloc_buf, *cdir_start, *cdir_max, *cdir, *hashdata, eocdirhdr_size, cdirhdr_size, nparents; 722 uint32_t cdir_entry_len; 723 size_t sizeof_idx, indices_offset, alloc_size; 724 int64_t i_file, archive_size, ecdh_ofs, total_files, cdir_size, cdir_ofs; 725 struct rc_hash_zip_idx* hashindices, *hashindex; 726 727 rc_file_seek(file_handle, 0, SEEK_END); 728 archive_size = rc_file_tell(file_handle); 729 730 /* Basic sanity checks - reject files which are too small */ 731 eocdirhdr_size = 22; /* the 'end of central directory header' is 22 bytes */ 732 if (archive_size < eocdirhdr_size) 733 return rc_hash_error("ZIP is too small"); 734 735 /* Macros used for reading ZIP and writing to a buffer for hashing (undefined again at the end of the function) */ 736 #define RC_ZIP_READ_LE16(p) ((uint16_t)(((const uint8_t*)(p))[0]) | ((uint16_t)(((const uint8_t*)(p))[1]) << 8U)) 737 #define RC_ZIP_READ_LE32(p) ((uint32_t)(((const uint8_t*)(p))[0]) | ((uint32_t)(((const uint8_t*)(p))[1]) << 8U) | ((uint32_t)(((const uint8_t*)(p))[2]) << 16U) | ((uint32_t)(((const uint8_t*)(p))[3]) << 24U)) 738 #define RC_ZIP_READ_LE64(p) ((uint64_t)(((const uint8_t*)(p))[0]) | ((uint64_t)(((const uint8_t*)(p))[1]) << 8U) | ((uint64_t)(((const uint8_t*)(p))[2]) << 16U) | ((uint64_t)(((const uint8_t*)(p))[3]) << 24U) | ((uint64_t)(((const uint8_t*)(p))[4]) << 32U) | ((uint64_t)(((const uint8_t*)(p))[5]) << 40U) | ((uint64_t)(((const uint8_t*)(p))[6]) << 48U) | ((uint64_t)(((const uint8_t*)(p))[7]) << 56U)) 739 #define RC_ZIP_WRITE_LE32(p,v) { ((uint8_t*)(p))[0] = (uint8_t)((uint32_t)(v) & 0xFF); ((uint8_t*)(p))[1] = (uint8_t)(((uint32_t)(v) >> 8) & 0xFF); ((uint8_t*)(p))[2] = (uint8_t)(((uint32_t)(v) >> 16) & 0xFF); ((uint8_t*)(p))[3] = (uint8_t)((uint32_t)(v) >> 24); } 740 #define RC_ZIP_WRITE_LE64(p,v) { ((uint8_t*)(p))[0] = (uint8_t)((uint64_t)(v) & 0xFF); ((uint8_t*)(p))[1] = (uint8_t)(((uint64_t)(v) >> 8) & 0xFF); ((uint8_t*)(p))[2] = (uint8_t)(((uint64_t)(v) >> 16) & 0xFF); ((uint8_t*)(p))[3] = (uint8_t)(((uint64_t)(v) >> 24) & 0xFF); ((uint8_t*)(p))[4] = (uint8_t)(((uint64_t)(v) >> 32) & 0xFF); ((uint8_t*)(p))[5] = (uint8_t)(((uint64_t)(v) >> 40) & 0xFF); ((uint8_t*)(p))[6] = (uint8_t)(((uint64_t)(v) >> 48) & 0xFF); ((uint8_t*)(p))[7] = (uint8_t)((uint64_t)(v) >> 56); } 741 742 /* Find the end of central directory record by scanning the file from the end towards the beginning */ 743 for (ecdh_ofs = archive_size - sizeof(buf); ; ecdh_ofs -= (sizeof(buf) - 3)) 744 { 745 int i, n = sizeof(buf); 746 if (ecdh_ofs < 0) 747 ecdh_ofs = 0; 748 if (n > archive_size) 749 n = (int)archive_size; 750 rc_file_seek(file_handle, ecdh_ofs, SEEK_SET); 751 if (rc_file_read(file_handle, buf, n) != (size_t)n) 752 return rc_hash_error("ZIP read error"); 753 for (i = n - 4; i >= 0; --i) 754 if (RC_ZIP_READ_LE32(buf + i) == 0x06054b50) /* end of central directory header signature */ 755 break; 756 if (i >= 0) 757 { 758 ecdh_ofs += i; 759 break; 760 } 761 if (!ecdh_ofs || (archive_size - ecdh_ofs) >= (0xFFFF + eocdirhdr_size)) 762 return rc_hash_error("Failed to find ZIP central directory"); 763 } 764 765 /* Read and verify the end of central directory record. */ 766 rc_file_seek(file_handle, ecdh_ofs, SEEK_SET); 767 if (rc_file_read(file_handle, buf, eocdirhdr_size) != eocdirhdr_size) 768 return rc_hash_error("Failed to read ZIP central directory"); 769 770 /* Read central dir information from end of central directory header */ 771 total_files = RC_ZIP_READ_LE16(buf + 0x0A); 772 cdir_size = RC_ZIP_READ_LE32(buf + 0x0C); 773 cdir_ofs = RC_ZIP_READ_LE32(buf + 0x10); 774 775 /* Check if this is a Zip64 file. In the block of code below: 776 * - 20 is the size of the ZIP64 end of central directory locator 777 * - 56 is the size of the ZIP64 end of central directory header 778 */ 779 if ((cdir_ofs == 0xFFFFFFFF || cdir_size == 0xFFFFFFFF || total_files == 0xFFFF) && ecdh_ofs >= (20 + 56)) 780 { 781 /* Read the ZIP64 end of central directory locator if it actually exists */ 782 rc_file_seek(file_handle, ecdh_ofs - 20, SEEK_SET); 783 if (rc_file_read(file_handle, buf, 20) == 20 && RC_ZIP_READ_LE32(buf) == 0x07064b50) /* locator signature */ 784 { 785 /* Found the locator, now read the actual ZIP64 end of central directory header */ 786 int64_t ecdh64_ofs = (int64_t)RC_ZIP_READ_LE64(buf + 0x08); 787 if (ecdh64_ofs <= (archive_size - 56)) 788 { 789 rc_file_seek(file_handle, ecdh64_ofs, SEEK_SET); 790 if (rc_file_read(file_handle, buf, 56) == 56 && RC_ZIP_READ_LE32(buf) == 0x06064b50) /* header signature */ 791 { 792 total_files = RC_ZIP_READ_LE64(buf + 0x20); 793 cdir_size = RC_ZIP_READ_LE64(buf + 0x28); 794 cdir_ofs = RC_ZIP_READ_LE64(buf + 0x30); 795 } 796 } 797 } 798 } 799 800 /* Basic verificaton of central directory (limit to a 256MB content directory) */ 801 cdirhdr_size = 46; /* the 'central directory header' is 46 bytes */ 802 if ((cdir_size >= 0x10000000) || (cdir_size < total_files * cdirhdr_size) || ((cdir_ofs + cdir_size) > archive_size)) 803 return rc_hash_error("Central directory of ZIP file is invalid"); 804 805 /* Allocate once for both directory and our temporary sort index (memory aligned to sizeof(rc_hash_zip_idx)) */ 806 sizeof_idx = sizeof(struct rc_hash_zip_idx); 807 indices_offset = (size_t)((cdir_size + sizeof_idx - 1) / sizeof_idx * sizeof_idx); 808 alloc_size = (size_t)(indices_offset + total_files * sizeof_idx); 809 alloc_buf = (uint8_t*)malloc(alloc_size); 810 811 /* Read entire central directory to a buffer */ 812 if (!alloc_buf) 813 return rc_hash_error("Could not allocate temporary buffer"); 814 rc_file_seek(file_handle, cdir_ofs, SEEK_SET); 815 if ((int64_t)rc_file_read(file_handle, alloc_buf, (int)cdir_size) != cdir_size) 816 { 817 free(alloc_buf); 818 return rc_hash_error("Failed to read central directory of ZIP file"); 819 } 820 821 cdir_start = alloc_buf; 822 cdir_max = cdir_start + cdir_size - cdirhdr_size; 823 cdir = cdir_start; 824 825 /* Write our temporary hash data to the same buffer we read the central directory from. 826 * We can do that because the amount of data we keep for each file is guaranteed to be less than the file record. 827 */ 828 hashdata = alloc_buf; 829 hashindices = (struct rc_hash_zip_idx*)(alloc_buf + indices_offset); 830 hashindex = hashindices; 831 832 /* Now process the central directory file records */ 833 for (i_file = nparents = 0, cdir = cdir_start; i_file < total_files && cdir >= cdir_start && cdir <= cdir_max; i_file++, cdir += cdir_entry_len) 834 { 835 const uint8_t *name, *name_end; 836 uint32_t signature = RC_ZIP_READ_LE32(cdir + 0x00); 837 uint32_t method = RC_ZIP_READ_LE16(cdir + 0x0A); 838 uint32_t crc32 = RC_ZIP_READ_LE32(cdir + 0x10); 839 uint64_t comp_size = RC_ZIP_READ_LE32(cdir + 0x14); 840 uint64_t decomp_size = RC_ZIP_READ_LE32(cdir + 0x18); 841 uint32_t filename_len = RC_ZIP_READ_LE16(cdir + 0x1C); 842 int32_t extra_len = RC_ZIP_READ_LE16(cdir + 0x1E); 843 int32_t comment_len = RC_ZIP_READ_LE16(cdir + 0x20); 844 int32_t external_attr = RC_ZIP_READ_LE16(cdir + 0x26); 845 uint64_t local_hdr_ofs = RC_ZIP_READ_LE32(cdir + 0x2A); 846 cdir_entry_len = cdirhdr_size + filename_len + extra_len + comment_len; 847 848 if (signature != 0x02014b50) /* expected central directory entry signature */ 849 break; 850 851 /* Ignore records describing a directory (we only hash file records) */ 852 name = (cdir + cdirhdr_size); 853 if (name[filename_len - 1] == '/' || name[filename_len - 1] == '\\' || (external_attr & 0x10)) 854 continue; 855 856 /* Handle Zip64 fields */ 857 if (decomp_size == 0xFFFFFFFF || comp_size == 0xFFFFFFFF || local_hdr_ofs == 0xFFFFFFFF) 858 { 859 int invalid = 0; 860 const uint8_t *x = cdir + cdirhdr_size + filename_len, *xEnd, *field, *fieldEnd; 861 for (xEnd = x + extra_len; (x + (sizeof(uint16_t) * 2)) < xEnd; x = fieldEnd) 862 { 863 field = x + (sizeof(uint16_t) * 2); 864 fieldEnd = field + RC_ZIP_READ_LE16(x + 2); 865 if (RC_ZIP_READ_LE16(x) != 0x0001 || fieldEnd > xEnd) 866 continue; /* Not the Zip64 extended information extra field */ 867 868 if (decomp_size == 0xFFFFFFFF) 869 { 870 if ((unsigned)(fieldEnd - field) < sizeof(uint64_t)) { invalid = 1; break; } 871 decomp_size = RC_ZIP_READ_LE64(field); 872 field += sizeof(uint64_t); 873 } 874 if (comp_size == 0xFFFFFFFF) 875 { 876 if ((unsigned)(fieldEnd - field) < sizeof(uint64_t)) { invalid = 1; break; } 877 comp_size = RC_ZIP_READ_LE64(field); 878 field += sizeof(uint64_t); 879 } 880 if (local_hdr_ofs == 0xFFFFFFFF) 881 { 882 if ((unsigned)(fieldEnd - field) < sizeof(uint64_t)) { invalid = 1; break; } 883 local_hdr_ofs = RC_ZIP_READ_LE64(field); 884 field += sizeof(uint64_t); 885 } 886 break; 887 } 888 if (invalid) 889 { 890 free(alloc_buf); 891 return rc_hash_error("Encountered invalid Zip64 file"); 892 } 893 } 894 895 /* Basic sanity check on file record */ 896 /* 30 is the length of the local directory header preceeding the compressed data */ 897 if ((!method && decomp_size != comp_size) || (decomp_size && !comp_size) || ((local_hdr_ofs + 30 + comp_size) > (uint64_t)archive_size)) 898 { 899 free(alloc_buf); 900 return rc_hash_error("Encountered invalid entry in ZIP central directory"); 901 } 902 903 /* A DOSZ file can contain a special empty <base>.dosz.parent file in its root which means a parent dosz file is used */ 904 if (dosz && decomp_size == 0 && filename_len > 7 && !strncasecmp((const char*)name + filename_len - 7, ".parent", 7) && !memchr(name, '/', filename_len) && !memchr(name, '\\', filename_len)) 905 { 906 /* A DOSZ file can only have one parent file */ 907 if (nparents++) 908 { 909 free(alloc_buf); 910 return rc_hash_error("Invalid DOSZ file with multiple parents"); 911 } 912 913 /* If there is an error with the parent DOSZ, abort now */ 914 if (!rc_hash_ms_dos_parent(md5, dosz, (const char*)name, (filename_len - 7))) 915 { 916 free(alloc_buf); 917 return 0; 918 } 919 920 /* We don't hash this meta file so a user is free to rename it and the parent file */ 921 continue; 922 } 923 924 /* Write the pointer and length of the data we record about this file */ 925 hashindex->data = hashdata; 926 hashindex->length = filename_len + 1 + 4 + 8; 927 hashindex++; 928 929 /* Convert and store the file name in the hash data buffer */ 930 for (name_end = name + filename_len; name != name_end; name++) 931 { 932 *(hashdata++) = 933 (*name == '\\' ? '/' : /* convert back-slashes to regular slashes */ 934 (*name >= 'A' && *name <= 'Z') ? (*name | 0x20) : /* convert upper case letters to lower case */ 935 *name); /* else use the byte as-is */ 936 } 937 938 /* Add zero terminator, CRC32 and decompressed size to the hash data buffer */ 939 *(hashdata++) = '\0'; 940 RC_ZIP_WRITE_LE32(hashdata, crc32); 941 hashdata += 4; 942 RC_ZIP_WRITE_LE64(hashdata, decomp_size); 943 hashdata += 8; 944 945 if (verbose_message_callback) 946 { 947 char message[1024]; 948 snprintf(message, sizeof(message), "File in ZIP: %.*s (%u bytes, CRC32 = %08X)", filename_len, (const char*)(cdir + cdirhdr_size), (unsigned)decomp_size, crc32); 949 verbose_message_callback(message); 950 } 951 } 952 953 if (verbose_message_callback) 954 { 955 char message[1024]; 956 snprintf(message, sizeof(message), "Hashing %u files in ZIP archive", (unsigned)(hashindex - hashindices)); 957 verbose_message_callback(message); 958 } 959 960 /* Sort the file list indices */ 961 qsort(hashindices, (hashindex - hashindices), sizeof(struct rc_hash_zip_idx), rc_hash_zip_idx_sort); 962 963 /* Hash the data in the order of the now sorted indices */ 964 for (; hashindices != hashindex; hashindices++) 965 md5_append(md5, hashindices->data, (int)hashindices->length); 966 967 free(alloc_buf); 968 969 /* If this is a .dosz file, check if an associated .dosc file exists */ 970 if (dosz && !rc_hash_ms_dos_dosc(md5, dosz)) 971 return 0; 972 973 return 1; 974 975 #undef RC_ZIP_READ_LE16 976 #undef RC_ZIP_READ_LE32 977 #undef RC_ZIP_READ_LE64 978 #undef RC_ZIP_WRITE_LE32 979 #undef RC_ZIP_WRITE_LE64 980 } 981 982 static int rc_hash_ms_dos_parent(md5_state_t* md5, const struct rc_hash_ms_dos_dosz_state *child, const char* parentname, uint32_t parentname_len) 983 { 984 const char *lastfslash = strrchr(child->path, '/'); 985 const char *lastbslash = strrchr(child->path, '\\'); 986 const char *lastslash = (lastbslash > lastfslash ? lastbslash : lastfslash); 987 size_t dir_len = (lastslash ? (lastslash + 1 - child->path) : 0); 988 char* parent_path = (char*)malloc(dir_len + parentname_len + 1); 989 struct rc_hash_ms_dos_dosz_state parent; 990 const struct rc_hash_ms_dos_dosz_state *check; 991 void* parent_handle; 992 int parent_res; 993 994 /* Build the path of the parent by combining the directory of the current file with the name */ 995 if (!parent_path) 996 return rc_hash_error("Could not allocate temporary buffer"); 997 998 memcpy(parent_path, child->path, dir_len); 999 memcpy(parent_path + dir_len, parentname, parentname_len); 1000 parent_path[dir_len + parentname_len] = '\0'; 1001 1002 /* Make sure there is no recursion where a parent DOSZ is an already seen child DOSZ */ 1003 for (check = child->child; check; check = check->child) 1004 { 1005 if (!strcmp(check->path, parent_path)) 1006 { 1007 free(parent_path); 1008 return rc_hash_error("Invalid DOSZ file with recursive parents"); 1009 } 1010 } 1011 1012 /* Try to open the parent DOSZ file */ 1013 parent_handle = rc_file_open(parent_path); 1014 if (!parent_handle) 1015 { 1016 char message[1024]; 1017 snprintf(message, sizeof(message), "DOSZ parent file '%s' does not exist", parent_path); 1018 free(parent_path); 1019 return rc_hash_error(message); 1020 } 1021 1022 /* Fully hash the parent DOSZ ahead of the child */ 1023 parent.path = parent_path; 1024 parent.child = child; 1025 parent_res = rc_hash_zip_file(md5, parent_handle, &parent); 1026 rc_file_close(parent_handle); 1027 free(parent_path); 1028 return parent_res; 1029 } 1030 1031 static int rc_hash_ms_dos_dosc(md5_state_t* md5, const struct rc_hash_ms_dos_dosz_state *dosz) 1032 { 1033 size_t path_len = strlen(dosz->path); 1034 if (dosz->path[path_len-1] == 'z' || dosz->path[path_len-1] == 'Z') 1035 { 1036 void* file_handle; 1037 char *dosc_path = strdup(dosz->path); 1038 if (!dosc_path) 1039 return rc_hash_error("Could not allocate temporary buffer"); 1040 1041 /* Swap the z to c and use the same capitalization, hash the file if it exists */ 1042 dosc_path[path_len-1] = (dosz->path[path_len-1] == 'z' ? 'c' : 'C'); 1043 file_handle = rc_file_open(dosc_path); 1044 free(dosc_path); 1045 1046 if (file_handle) 1047 { 1048 /* Hash the DOSC as a plain zip file (pass NULL as dosz state) */ 1049 int res = rc_hash_zip_file(md5, file_handle, NULL); 1050 rc_file_close(file_handle); 1051 if (!res) 1052 return 0; 1053 } 1054 } 1055 return 1; 1056 } 1057 1058 static int rc_hash_ms_dos(char hash[33], const char* path) 1059 { 1060 struct rc_hash_ms_dos_dosz_state dosz; 1061 md5_state_t md5; 1062 int res; 1063 1064 void* file_handle = rc_file_open(path); 1065 if (!file_handle) 1066 return rc_hash_error("Could not open file"); 1067 1068 /* hash the main content zip file first */ 1069 md5_init(&md5); 1070 dosz.path = path; 1071 dosz.child = NULL; 1072 res = rc_hash_zip_file(&md5, file_handle, &dosz); 1073 rc_file_close(file_handle); 1074 1075 if (!res) 1076 return 0; 1077 1078 return rc_hash_finalize(&md5, hash); 1079 } 1080 1081 static int rc_hash_arcade(char hash[33], const char* path) 1082 { 1083 /* arcade hash is just the hash of the filename (no extension) - the cores are pretty stringent about having the right ROM data */ 1084 const char* filename = rc_path_get_filename(path); 1085 const char* ext = rc_path_get_extension(filename); 1086 char buffer[128]; /* realistically, this should never need more than ~32 characters */ 1087 size_t filename_length = ext - filename - 1; 1088 1089 /* fbneo supports loading subsystems by using specific folder names. 1090 * if one is found, include it in the hash. 1091 * https://github.com/libretro/FBNeo/blob/master/src/burner/libretro/README.md#emulating-consoles-and-computers 1092 */ 1093 if (filename > path + 1) 1094 { 1095 int include_folder = 0; 1096 const char* folder = filename - 1; 1097 size_t parent_folder_length = 0; 1098 1099 do 1100 { 1101 if (folder[-1] == '/' || folder[-1] == '\\') 1102 break; 1103 1104 --folder; 1105 } while (folder > path); 1106 1107 parent_folder_length = filename - folder - 1; 1108 if (parent_folder_length < 16) 1109 { 1110 char* ptr = buffer; 1111 while (folder < filename - 1) 1112 *ptr++ = tolower(*folder++); 1113 *ptr = '\0'; 1114 1115 folder = buffer; 1116 } 1117 1118 switch (parent_folder_length) 1119 { 1120 case 3: 1121 if (memcmp(folder, "nes", 3) == 0 || /* NES */ 1122 memcmp(folder, "fds", 3) == 0 || /* FDS */ 1123 memcmp(folder, "sms", 3) == 0 || /* Master System */ 1124 memcmp(folder, "msx", 3) == 0 || /* MSX */ 1125 memcmp(folder, "ngp", 3) == 0 || /* NeoGeo Pocket */ 1126 memcmp(folder, "pce", 3) == 0 || /* PCEngine */ 1127 memcmp(folder, "chf", 3) == 0 || /* ChannelF */ 1128 memcmp(folder, "sgx", 3) == 0) /* SuperGrafX */ 1129 include_folder = 1; 1130 break; 1131 case 4: 1132 if (memcmp(folder, "tg16", 4) == 0 || /* TurboGrafx-16 */ 1133 memcmp(folder, "msx1", 4) == 0) /* MSX */ 1134 include_folder = 1; 1135 break; 1136 case 5: 1137 if (memcmp(folder, "neocd", 5) == 0) /* NeoGeo CD */ 1138 include_folder = 1; 1139 break; 1140 case 6: 1141 if (memcmp(folder, "coleco", 6) == 0 || /* Colecovision */ 1142 memcmp(folder, "sg1000", 6) == 0) /* SG-1000 */ 1143 include_folder = 1; 1144 break; 1145 case 7: 1146 if (memcmp(folder, "genesis", 7) == 0) /* Megadrive (Genesis) */ 1147 include_folder = 1; 1148 break; 1149 case 8: 1150 if (memcmp(folder, "gamegear", 8) == 0 || /* Game Gear */ 1151 memcmp(folder, "megadriv", 8) == 0 || /* Megadrive */ 1152 memcmp(folder, "pcengine", 8) == 0 || /* PCEngine */ 1153 memcmp(folder, "channelf", 8) == 0 || /* ChannelF */ 1154 memcmp(folder, "spectrum", 8) == 0) /* ZX Spectrum */ 1155 include_folder = 1; 1156 break; 1157 case 9: 1158 if (memcmp(folder, "megadrive", 9) == 0) /* Megadrive */ 1159 include_folder = 1; 1160 break; 1161 case 10: 1162 if (memcmp(folder, "supergrafx", 10) == 0 || /* SuperGrafX */ 1163 memcmp(folder, "zxspectrum", 10) == 0) /* ZX Spectrum */ 1164 include_folder = 1; 1165 break; 1166 case 12: 1167 if (memcmp(folder, "mastersystem", 12) == 0 || /* Master System */ 1168 memcmp(folder, "colecovision", 12) == 0) /* Colecovision */ 1169 include_folder = 1; 1170 break; 1171 default: 1172 break; 1173 } 1174 1175 if (include_folder) 1176 { 1177 if (parent_folder_length + filename_length + 1 < sizeof(buffer)) 1178 { 1179 buffer[parent_folder_length] = '_'; 1180 memcpy(&buffer[parent_folder_length + 1], filename, filename_length); 1181 return rc_hash_buffer(hash, (uint8_t*)&buffer[0], parent_folder_length + filename_length + 1); 1182 } 1183 } 1184 } 1185 1186 return rc_hash_buffer(hash, (uint8_t*)filename, filename_length); 1187 } 1188 1189 static int rc_hash_text(char hash[33], const uint8_t* buffer, size_t buffer_size) 1190 { 1191 md5_state_t md5; 1192 const uint8_t* scan = buffer; 1193 const uint8_t* stop = buffer + buffer_size; 1194 1195 md5_init(&md5); 1196 1197 do { 1198 /* find end of line */ 1199 while (scan < stop && *scan != '\r' && *scan != '\n') 1200 ++scan; 1201 1202 md5_append(&md5, buffer, (int)(scan - buffer)); 1203 1204 /* include a normalized line ending */ 1205 /* NOTE: this causes a line ending to be hashed at the end of the file, even if one was not present */ 1206 md5_append(&md5, (const uint8_t*)"\n", 1); 1207 1208 /* skip newline */ 1209 if (scan < stop && *scan == '\r') 1210 ++scan; 1211 if (scan < stop && *scan == '\n') 1212 ++scan; 1213 1214 buffer = scan; 1215 } while (scan < stop); 1216 1217 return rc_hash_finalize(&md5, hash); 1218 } 1219 1220 /* helper variable only used for testing */ 1221 const char* _rc_hash_jaguar_cd_homebrew_hash = NULL; 1222 1223 static int rc_hash_jaguar_cd(char hash[33], const char* path) 1224 { 1225 uint8_t buffer[2352]; 1226 char message[128]; 1227 void* track_handle; 1228 md5_state_t md5; 1229 int byteswapped = 0; 1230 uint32_t size = 0; 1231 uint32_t offset = 0; 1232 uint32_t sector = 0; 1233 uint32_t remaining; 1234 uint32_t i; 1235 1236 /* Jaguar CD header is in the first sector of the first data track OF THE SECOND SESSION. 1237 * The first track must be an audio track, but may be a warning message or actual game audio */ 1238 track_handle = rc_cd_open_track(path, RC_HASH_CDTRACK_FIRST_OF_SECOND_SESSION); 1239 if (!track_handle) 1240 return rc_hash_error("Could not open track"); 1241 1242 /* The header is an unspecified distance into the first sector, but usually two bytes in. 1243 * It consists of 64 bytes of "TAIR" or "ATRI" repeating, depending on whether or not the data 1244 * is byteswapped. Then another 32 byte that reads "ATARI APPROVED DATA HEADER ATRI " 1245 * (possibly byteswapped). Then a big-endian 32-bit value for the address where the boot code 1246 * should be loaded, and a second big-endian 32-bit value for the size of the boot code. */ 1247 sector = rc_cd_first_track_sector(track_handle); 1248 rc_cd_read_sector(track_handle, sector, buffer, sizeof(buffer)); 1249 1250 for (i = 64; i < sizeof(buffer) - 32 - 4 * 3; i++) 1251 { 1252 if (memcmp(&buffer[i], "TARA IPARPVODED TA AEHDAREA RT I", 32) == 0) 1253 { 1254 byteswapped = 1; 1255 offset = i + 32 + 4; 1256 size = (buffer[offset] << 16) | (buffer[offset + 1] << 24) | (buffer[offset + 2]) | (buffer[offset + 3] << 8); 1257 break; 1258 } 1259 else if (memcmp(&buffer[i], "ATARI APPROVED DATA HEADER ATRI ", 32) == 0) 1260 { 1261 byteswapped = 0; 1262 offset = i + 32 + 4; 1263 size = (buffer[offset] << 24) | (buffer[offset + 1] << 16) | (buffer[offset + 2] << 8) | (buffer[offset + 3]); 1264 break; 1265 } 1266 } 1267 1268 if (size == 0) /* did not see ATARI APPROVED DATA HEADER */ 1269 { 1270 rc_cd_close_track(track_handle); 1271 return rc_hash_error("Not a Jaguar CD"); 1272 } 1273 1274 i = 0; /* only loop once */ 1275 do 1276 { 1277 md5_init(&md5); 1278 1279 offset += 4; 1280 1281 if (verbose_message_callback) 1282 { 1283 snprintf(message, sizeof(message), "Hashing boot executable (%u bytes starting at %u bytes into sector %u)", size, offset, sector); 1284 rc_hash_verbose(message); 1285 } 1286 1287 if (size > MAX_BUFFER_SIZE) 1288 size = MAX_BUFFER_SIZE; 1289 1290 do 1291 { 1292 if (byteswapped) 1293 rc_hash_byteswap16(buffer, &buffer[sizeof(buffer)]); 1294 1295 remaining = sizeof(buffer) - offset; 1296 if (remaining >= size) 1297 { 1298 md5_append(&md5, &buffer[offset], size); 1299 size = 0; 1300 break; 1301 } 1302 1303 md5_append(&md5, &buffer[offset], remaining); 1304 size -= remaining; 1305 offset = 0; 1306 } while (rc_cd_read_sector(track_handle, ++sector, buffer, sizeof(buffer)) == sizeof(buffer)); 1307 1308 rc_cd_close_track(track_handle); 1309 1310 if (size > 0) 1311 return rc_hash_error("Not enough data"); 1312 1313 rc_hash_finalize(&md5, hash); 1314 1315 /* homebrew games all seem to have the same boot executable and store the actual game code in track 2. 1316 * if we generated something other than the homebrew hash, return it. assume all homebrews are byteswapped. */ 1317 if (strcmp(hash, "254487b59ab21bc005338e85cbf9fd2f") != 0 || !byteswapped) { 1318 if (_rc_hash_jaguar_cd_homebrew_hash == NULL || strcmp(hash, _rc_hash_jaguar_cd_homebrew_hash) != 0) 1319 return 1; 1320 } 1321 1322 /* if we've already been through the loop a second time, just return the hash */ 1323 if (i == 1) 1324 return 1; 1325 ++i; 1326 1327 if (verbose_message_callback) 1328 { 1329 snprintf(message, sizeof(message), "Potential homebrew at sector %u, checking for KART data in track 2", sector); 1330 rc_hash_verbose(message); 1331 } 1332 1333 track_handle = rc_cd_open_track(path, 2); 1334 if (!track_handle) 1335 return rc_hash_error("Could not open track"); 1336 1337 /* track 2 of the homebrew code has the 64 bytes or ATRI followed by 32 bytes of "ATARI APPROVED DATA HEADER ATRI!", 1338 * then 64 bytes of KART repeating. */ 1339 sector = rc_cd_first_track_sector(track_handle); 1340 rc_cd_read_sector(track_handle, sector, buffer, sizeof(buffer)); 1341 if (memcmp(&buffer[0x5E], "RT!IRTKA", 8) != 0) 1342 return rc_hash_error("Homebrew executable not found in track 2"); 1343 1344 /* found KART data*/ 1345 if (verbose_message_callback) 1346 { 1347 snprintf(message, sizeof(message), "Found KART data in track 2"); 1348 rc_hash_verbose(message); 1349 } 1350 1351 offset = 0xA6; 1352 size = (buffer[offset] << 16) | (buffer[offset + 1] << 24) | (buffer[offset + 2]) | (buffer[offset + 3] << 8); 1353 } while (1); 1354 } 1355 1356 static int rc_hash_lynx(char hash[33], const uint8_t* buffer, size_t buffer_size) 1357 { 1358 /* if the file contains a header, ignore it */ 1359 if (buffer[0] == 'L' && buffer[1] == 'Y' && buffer[2] == 'N' && buffer[3] == 'X' && buffer[4] == 0) 1360 { 1361 rc_hash_verbose("Ignoring LYNX header"); 1362 1363 buffer += 64; 1364 buffer_size -= 64; 1365 } 1366 1367 return rc_hash_buffer(hash, buffer, buffer_size); 1368 } 1369 1370 static int rc_hash_neogeo_cd(char hash[33], const char* path) 1371 { 1372 char buffer[1024], *ptr; 1373 void* track_handle; 1374 uint32_t sector; 1375 uint32_t size; 1376 md5_state_t md5; 1377 1378 track_handle = rc_cd_open_track(path, 1); 1379 if (!track_handle) 1380 return rc_hash_error("Could not open track"); 1381 1382 /* https://wiki.neogeodev.org/index.php?title=IPL_file, https://wiki.neogeodev.org/index.php?title=PRG_file 1383 * IPL file specifies data to be loaded before the game starts. PRG files are the executable code 1384 */ 1385 sector = rc_cd_find_file_sector(track_handle, "IPL.TXT", &size); 1386 if (!sector) 1387 { 1388 rc_cd_close_track(track_handle); 1389 return rc_hash_error("Not a NeoGeo CD game disc"); 1390 } 1391 1392 if (rc_cd_read_sector(track_handle, sector, buffer, sizeof(buffer)) == 0) 1393 { 1394 rc_cd_close_track(track_handle); 1395 return 0; 1396 } 1397 1398 md5_init(&md5); 1399 1400 buffer[sizeof(buffer) - 1] = '\0'; 1401 ptr = &buffer[0]; 1402 do 1403 { 1404 char* start = ptr; 1405 while (*ptr && *ptr != '.') 1406 ++ptr; 1407 1408 if (strncasecmp(ptr, ".PRG", 4) == 0) 1409 { 1410 ptr += 4; 1411 *ptr++ = '\0'; 1412 1413 sector = rc_cd_find_file_sector(track_handle, start, &size); 1414 if (!sector || !rc_hash_cd_file(&md5, track_handle, sector, NULL, size, start)) 1415 { 1416 char error[128]; 1417 rc_cd_close_track(track_handle); 1418 snprintf(error, sizeof(error), "Could not read %.16s", start); 1419 return rc_hash_error(error); 1420 } 1421 } 1422 1423 while (*ptr && *ptr != '\n') 1424 ++ptr; 1425 if (*ptr != '\n') 1426 break; 1427 ++ptr; 1428 } while (*ptr != '\0' && *ptr != '\x1a'); 1429 1430 rc_cd_close_track(track_handle); 1431 return rc_hash_finalize(&md5, hash); 1432 } 1433 1434 static int rc_hash_nes(char hash[33], const uint8_t* buffer, size_t buffer_size) 1435 { 1436 /* if the file contains a header, ignore it */ 1437 if (buffer[0] == 'N' && buffer[1] == 'E' && buffer[2] == 'S' && buffer[3] == 0x1A) 1438 { 1439 rc_hash_verbose("Ignoring NES header"); 1440 1441 buffer += 16; 1442 buffer_size -= 16; 1443 } 1444 else if (buffer[0] == 'F' && buffer[1] == 'D' && buffer[2] == 'S' && buffer[3] == 0x1A) 1445 { 1446 rc_hash_verbose("Ignoring FDS header"); 1447 1448 buffer += 16; 1449 buffer_size -= 16; 1450 } 1451 1452 return rc_hash_buffer(hash, buffer, buffer_size); 1453 } 1454 1455 static int rc_hash_n64(char hash[33], const char* path) 1456 { 1457 uint8_t* buffer; 1458 uint8_t* stop; 1459 const size_t buffer_size = 65536; 1460 md5_state_t md5; 1461 size_t remaining; 1462 void* file_handle; 1463 int is_v64 = 0; 1464 int is_n64 = 0; 1465 1466 file_handle = rc_file_open(path); 1467 if (!file_handle) 1468 return rc_hash_error("Could not open file"); 1469 1470 buffer = (uint8_t*)malloc(buffer_size); 1471 if (!buffer) 1472 { 1473 rc_file_close(file_handle); 1474 return rc_hash_error("Could not allocate temporary buffer"); 1475 } 1476 stop = buffer + buffer_size; 1477 1478 /* read first byte so we can detect endianness */ 1479 rc_file_seek(file_handle, 0, SEEK_SET); 1480 rc_file_read(file_handle, buffer, 1); 1481 1482 if (buffer[0] == 0x80) /* z64 format (big endian [native]) */ 1483 { 1484 } 1485 else if (buffer[0] == 0x37) /* v64 format (byteswapped) */ 1486 { 1487 rc_hash_verbose("converting v64 to z64"); 1488 is_v64 = 1; 1489 } 1490 else if (buffer[0] == 0x40) /* n64 format (little endian) */ 1491 { 1492 rc_hash_verbose("converting n64 to z64"); 1493 is_n64 = 1; 1494 } 1495 else if (buffer[0] == 0xE8 || buffer[0] == 0x22) /* ndd format (don't byteswap) */ 1496 { 1497 } 1498 else 1499 { 1500 free(buffer); 1501 rc_file_close(file_handle); 1502 1503 rc_hash_verbose("Not a Nintendo 64 ROM"); 1504 return 0; 1505 } 1506 1507 /* calculate total file size */ 1508 rc_file_seek(file_handle, 0, SEEK_END); 1509 remaining = (size_t)rc_file_tell(file_handle); 1510 if (remaining > MAX_BUFFER_SIZE) 1511 remaining = MAX_BUFFER_SIZE; 1512 1513 if (verbose_message_callback) 1514 { 1515 char message[64]; 1516 snprintf(message, sizeof(message), "Hashing %u bytes", (unsigned)remaining); 1517 verbose_message_callback(message); 1518 } 1519 1520 /* begin hashing */ 1521 md5_init(&md5); 1522 1523 rc_file_seek(file_handle, 0, SEEK_SET); 1524 while (remaining >= buffer_size) 1525 { 1526 rc_file_read(file_handle, buffer, (int)buffer_size); 1527 1528 if (is_v64) 1529 rc_hash_byteswap16(buffer, stop); 1530 else if (is_n64) 1531 rc_hash_byteswap32(buffer, stop); 1532 1533 md5_append(&md5, buffer, (int)buffer_size); 1534 remaining -= buffer_size; 1535 } 1536 1537 if (remaining > 0) 1538 { 1539 rc_file_read(file_handle, buffer, (int)remaining); 1540 1541 stop = buffer + remaining; 1542 if (is_v64) 1543 rc_hash_byteswap16(buffer, stop); 1544 else if (is_n64) 1545 rc_hash_byteswap32(buffer, stop); 1546 1547 md5_append(&md5, buffer, (int)remaining); 1548 } 1549 1550 /* cleanup */ 1551 rc_file_close(file_handle); 1552 free(buffer); 1553 1554 return rc_hash_finalize(&md5, hash); 1555 } 1556 1557 static int rc_hash_nintendo_ds(char hash[33], const char* path) 1558 { 1559 uint8_t header[512]; 1560 uint8_t* hash_buffer; 1561 uint32_t hash_size, arm9_size, arm9_addr, arm7_size, arm7_addr, icon_addr; 1562 size_t num_read; 1563 int64_t offset = 0; 1564 md5_state_t md5; 1565 void* file_handle; 1566 1567 file_handle = rc_file_open(path); 1568 if (!file_handle) 1569 return rc_hash_error("Could not open file"); 1570 1571 rc_file_seek(file_handle, 0, SEEK_SET); 1572 if (rc_file_read(file_handle, header, sizeof(header)) != 512) 1573 return rc_hash_error("Failed to read header"); 1574 1575 if (header[0] == 0x2E && header[1] == 0x00 && header[2] == 0x00 && header[3] == 0xEA && 1576 header[0xB0] == 0x44 && header[0xB1] == 0x46 && header[0xB2] == 0x96 && header[0xB3] == 0) 1577 { 1578 /* SuperCard header detected, ignore it */ 1579 rc_hash_verbose("Ignoring SuperCard header"); 1580 1581 offset = 512; 1582 rc_file_seek(file_handle, offset, SEEK_SET); 1583 rc_file_read(file_handle, header, sizeof(header)); 1584 } 1585 1586 arm9_addr = header[0x20] | (header[0x21] << 8) | (header[0x22] << 16) | (header[0x23] << 24); 1587 arm9_size = header[0x2C] | (header[0x2D] << 8) | (header[0x2E] << 16) | (header[0x2F] << 24); 1588 arm7_addr = header[0x30] | (header[0x31] << 8) | (header[0x32] << 16) | (header[0x33] << 24); 1589 arm7_size = header[0x3C] | (header[0x3D] << 8) | (header[0x3E] << 16) | (header[0x3F] << 24); 1590 icon_addr = header[0x68] | (header[0x69] << 8) | (header[0x6A] << 16) | (header[0x6B] << 24); 1591 1592 if (arm9_size + arm7_size > 16 * 1024 * 1024) 1593 { 1594 /* sanity check - code blocks are typically less than 1MB each - assume not a DS ROM */ 1595 snprintf((char*)header, sizeof(header), "arm9 code size (%u) + arm7 code size (%u) exceeds 16MB", arm9_size, arm7_size); 1596 return rc_hash_error((const char*)header); 1597 } 1598 1599 hash_size = 0xA00; 1600 if (arm9_size > hash_size) 1601 hash_size = arm9_size; 1602 if (arm7_size > hash_size) 1603 hash_size = arm7_size; 1604 1605 hash_buffer = (uint8_t*)malloc(hash_size); 1606 if (!hash_buffer) 1607 { 1608 rc_file_close(file_handle); 1609 1610 snprintf((char*)header, sizeof(header), "Failed to allocate %u bytes", hash_size); 1611 return rc_hash_error((const char*)header); 1612 } 1613 1614 md5_init(&md5); 1615 1616 rc_hash_verbose("Hashing 352 byte header"); 1617 md5_append(&md5, header, 0x160); 1618 1619 if (verbose_message_callback) 1620 { 1621 snprintf((char*)header, sizeof(header), "Hashing %u byte arm9 code (at %08X)", arm9_size, arm9_addr); 1622 verbose_message_callback((const char*)header); 1623 } 1624 1625 rc_file_seek(file_handle, arm9_addr + offset, SEEK_SET); 1626 rc_file_read(file_handle, hash_buffer, arm9_size); 1627 md5_append(&md5, hash_buffer, arm9_size); 1628 1629 if (verbose_message_callback) 1630 { 1631 snprintf((char*)header, sizeof(header), "Hashing %u byte arm7 code (at %08X)", arm7_size, arm7_addr); 1632 verbose_message_callback((const char*)header); 1633 } 1634 1635 rc_file_seek(file_handle, arm7_addr + offset, SEEK_SET); 1636 rc_file_read(file_handle, hash_buffer, arm7_size); 1637 md5_append(&md5, hash_buffer, arm7_size); 1638 1639 if (verbose_message_callback) 1640 { 1641 snprintf((char*)header, sizeof(header), "Hashing 2560 byte icon and labels data (at %08X)", icon_addr); 1642 verbose_message_callback((const char*)header); 1643 } 1644 1645 rc_file_seek(file_handle, icon_addr + offset, SEEK_SET); 1646 num_read = rc_file_read(file_handle, hash_buffer, 0xA00); 1647 if (num_read < 0xA00) 1648 { 1649 /* some homebrew games don't provide a full icon block, and no data after the icon block. 1650 * if we didn't get a full icon block, fill the remaining portion with 0s 1651 */ 1652 if (verbose_message_callback) 1653 { 1654 snprintf((char*)header, sizeof(header), "Warning: only got %u bytes for icon and labels data, 0-padding to 2560 bytes", (unsigned)num_read); 1655 verbose_message_callback((const char*)header); 1656 } 1657 1658 memset(&hash_buffer[num_read], 0, 0xA00 - num_read); 1659 } 1660 md5_append(&md5, hash_buffer, 0xA00); 1661 1662 free(hash_buffer); 1663 rc_file_close(file_handle); 1664 1665 return rc_hash_finalize(&md5, hash); 1666 } 1667 1668 static int rc_hash_gamecube(char hash[33], const char* path) 1669 { 1670 md5_state_t md5; 1671 void* file_handle; 1672 const uint32_t BASE_HEADER_SIZE = 0x2440; 1673 const uint32_t MAX_HEADER_SIZE = 1024 * 1024; 1674 uint32_t apploader_header_size, apploader_body_size, apploader_trailer_size, header_size; 1675 uint8_t quad_buffer[4]; 1676 uint8_t addr_buffer[0xD8]; 1677 uint8_t* buffer; 1678 uint32_t dol_offset; 1679 uint32_t dol_offsets[18]; 1680 uint32_t dol_sizes[18]; 1681 uint32_t dol_buf_size = 0; 1682 uint32_t ix; 1683 1684 file_handle = rc_file_open(path); 1685 if (!file_handle) 1686 return rc_hash_error("Could not open file"); 1687 1688 /* Verify Gamecube */ 1689 rc_file_seek(file_handle, 0x1c, SEEK_SET); 1690 rc_file_read(file_handle, quad_buffer, 4); 1691 if (quad_buffer[0] != 0xC2|| quad_buffer[1] != 0x33 || quad_buffer[2] != 0x9F || quad_buffer[3] != 0x3D) 1692 { 1693 rc_file_close(file_handle); 1694 return rc_hash_error("Not a Gamecube disc"); 1695 } 1696 1697 /* GetApploaderSize */ 1698 rc_file_seek(file_handle, BASE_HEADER_SIZE + 0x14, SEEK_SET); 1699 apploader_header_size = 0x20; 1700 rc_file_read(file_handle, quad_buffer, 4); 1701 apploader_body_size = 1702 (quad_buffer[0] << 24) | (quad_buffer[1] << 16) | (quad_buffer[2] << 8) | quad_buffer[3]; 1703 rc_file_read(file_handle, quad_buffer, 4); 1704 apploader_trailer_size = 1705 (quad_buffer[0] << 24) | (quad_buffer[1] << 16) | (quad_buffer[2] << 8) | quad_buffer[3]; 1706 header_size = BASE_HEADER_SIZE + apploader_header_size + apploader_body_size + apploader_trailer_size; 1707 if (header_size > MAX_HEADER_SIZE) header_size = MAX_HEADER_SIZE; 1708 1709 /* Hash headers */ 1710 buffer = (uint8_t*)malloc(header_size); 1711 if (!buffer) 1712 { 1713 rc_file_close(file_handle); 1714 return rc_hash_error("Could not allocate temporary buffer"); 1715 } 1716 rc_file_seek(file_handle, 0, SEEK_SET); 1717 rc_file_read(file_handle, buffer, header_size); 1718 md5_init(&md5); 1719 if (verbose_message_callback) 1720 { 1721 char message[128]; 1722 snprintf(message, sizeof(message), "Hashing %u byte header", header_size); 1723 verbose_message_callback(message); 1724 } 1725 md5_append(&md5, buffer, header_size); 1726 1727 /* GetBootDOLOffset 1728 * Base header size is guaranteed larger than 0x423 therefore buffer contains dol_offset right now 1729 */ 1730 dol_offset = (buffer[0x420] << 24) | (buffer[0x421] << 16) | (buffer[0x422] << 8) | buffer[0x423]; 1731 free(buffer); 1732 1733 /* Find offsetsand sizes for the 7 main.dol code segments and 11 main.dol data segments */ 1734 rc_file_seek(file_handle, dol_offset, SEEK_SET); 1735 rc_file_read(file_handle, addr_buffer, 0xD8); 1736 for (ix = 0; ix < 18; ix++) 1737 { 1738 dol_offsets[ix] = 1739 (addr_buffer[0x0 + ix * 4] << 24) | 1740 (addr_buffer[0x1 + ix * 4] << 16) | 1741 (addr_buffer[0x2 + ix * 4] << 8) | 1742 addr_buffer[0x3 + ix * 4]; 1743 dol_sizes[ix] = 1744 (addr_buffer[0x90 + ix * 4] << 24) | 1745 (addr_buffer[0x91 + ix * 4] << 16) | 1746 (addr_buffer[0x92 + ix * 4] << 8) | 1747 addr_buffer[0x93 + ix * 4]; 1748 dol_buf_size = (dol_sizes[ix] > dol_buf_size) ? dol_sizes[ix] : dol_buf_size; 1749 } 1750 1751 /* Iterate through the 18 main.dol segments and hash each */ 1752 buffer = (uint8_t*)malloc(dol_buf_size); 1753 if (!buffer) 1754 { 1755 rc_file_close(file_handle); 1756 return rc_hash_error("Could not allocate temporary buffer"); 1757 } 1758 for (ix = 0; ix < 18; ix++) 1759 { 1760 if (dol_sizes[ix] == 0) 1761 continue; 1762 rc_file_seek(file_handle, dol_offsets[ix], SEEK_SET); 1763 rc_file_read(file_handle, buffer, dol_sizes[ix]); 1764 if (verbose_message_callback) 1765 { 1766 char message[128]; 1767 if (ix < 7) 1768 snprintf(message, sizeof(message), "Hashing %u byte main.dol code segment %u", dol_sizes[ix], ix); 1769 else 1770 snprintf(message, sizeof(message), "Hashing %u byte main.dol data segment %u", dol_sizes[ix], ix - 7); 1771 verbose_message_callback(message); 1772 } 1773 md5_append(&md5, buffer, dol_sizes[ix]); 1774 } 1775 1776 /* Finalize */ 1777 rc_file_close(file_handle); 1778 free(buffer); 1779 1780 return rc_hash_finalize(&md5, hash); 1781 } 1782 1783 static int rc_hash_pce(char hash[33], const uint8_t* buffer, size_t buffer_size) 1784 { 1785 /* if the file contains a header, ignore it (expect ROM data to be multiple of 128KB) */ 1786 uint32_t calc_size = ((uint32_t)buffer_size / 0x20000) * 0x20000; 1787 if (buffer_size - calc_size == 512) 1788 { 1789 rc_hash_verbose("Ignoring PCE header"); 1790 1791 buffer += 512; 1792 buffer_size -= 512; 1793 } 1794 1795 return rc_hash_buffer(hash, buffer, buffer_size); 1796 } 1797 1798 static int rc_hash_pce_track(char hash[33], void* track_handle) 1799 { 1800 uint8_t buffer[2048]; 1801 md5_state_t md5; 1802 uint32_t sector, num_sectors; 1803 uint32_t size; 1804 1805 /* the PC-Engine uses the second sector to specify boot information and program name. 1806 * the string "PC Engine CD-ROM SYSTEM" should exist at 32 bytes into the sector 1807 * http://shu.sheldows.com/shu/download/pcedocs/pce_cdrom.html 1808 */ 1809 if (rc_cd_read_sector(track_handle, rc_cd_first_track_sector(track_handle) + 1, buffer, 128) < 128) 1810 { 1811 return rc_hash_error("Not a PC Engine CD"); 1812 } 1813 1814 /* normal PC Engine CD will have a header block in sector 1 */ 1815 if (memcmp("PC Engine CD-ROM SYSTEM", &buffer[32], 23) == 0) 1816 { 1817 /* the title of the disc is the last 22 bytes of the header */ 1818 md5_init(&md5); 1819 md5_append(&md5, &buffer[106], 22); 1820 1821 if (verbose_message_callback) 1822 { 1823 char message[128]; 1824 buffer[128] = '\0'; 1825 snprintf(message, sizeof(message), "Found PC Engine CD, title=%.22s", &buffer[106]); 1826 verbose_message_callback(message); 1827 } 1828 1829 /* the first three bytes specify the sector of the program data, and the fourth byte 1830 * is the number of sectors. 1831 */ 1832 sector = (buffer[0] << 16) + (buffer[1] << 8) + buffer[2]; 1833 num_sectors = buffer[3]; 1834 1835 if (verbose_message_callback) 1836 { 1837 char message[128]; 1838 snprintf(message, sizeof(message), "Hashing %d sectors starting at sector %d", num_sectors, sector); 1839 verbose_message_callback(message); 1840 } 1841 1842 sector += rc_cd_first_track_sector(track_handle); 1843 while (num_sectors > 0) 1844 { 1845 rc_cd_read_sector(track_handle, sector, buffer, sizeof(buffer)); 1846 md5_append(&md5, buffer, sizeof(buffer)); 1847 1848 ++sector; 1849 --num_sectors; 1850 } 1851 } 1852 /* GameExpress CDs use a standard Joliet filesystem - locate and hash the BOOT.BIN */ 1853 else if ((sector = rc_cd_find_file_sector(track_handle, "BOOT.BIN", &size)) != 0 && size < MAX_BUFFER_SIZE) 1854 { 1855 md5_init(&md5); 1856 while (size > sizeof(buffer)) 1857 { 1858 rc_cd_read_sector(track_handle, sector, buffer, sizeof(buffer)); 1859 md5_append(&md5, buffer, sizeof(buffer)); 1860 1861 ++sector; 1862 size -= sizeof(buffer); 1863 } 1864 1865 if (size > 0) 1866 { 1867 rc_cd_read_sector(track_handle, sector, buffer, size); 1868 md5_append(&md5, buffer, size); 1869 } 1870 } 1871 else 1872 { 1873 return rc_hash_error("Not a PC Engine CD"); 1874 } 1875 1876 return rc_hash_finalize(&md5, hash); 1877 } 1878 1879 static int rc_hash_pce_cd(char hash[33], const char* path) 1880 { 1881 int result; 1882 void* track_handle = rc_cd_open_track(path, RC_HASH_CDTRACK_FIRST_DATA); 1883 if (!track_handle) 1884 return rc_hash_error("Could not open track"); 1885 1886 result = rc_hash_pce_track(hash, track_handle); 1887 1888 rc_cd_close_track(track_handle); 1889 1890 return result; 1891 } 1892 1893 static int rc_hash_pcfx_cd(char hash[33], const char* path) 1894 { 1895 uint8_t buffer[2048]; 1896 void* track_handle; 1897 md5_state_t md5; 1898 int sector, num_sectors; 1899 1900 /* PC-FX executable can be in any track. Assume it's in the largest data track and check there first */ 1901 track_handle = rc_cd_open_track(path, RC_HASH_CDTRACK_LARGEST); 1902 if (!track_handle) 1903 return rc_hash_error("Could not open track"); 1904 1905 /* PC-FX CD will have a header marker in sector 0 */ 1906 sector = rc_cd_first_track_sector(track_handle); 1907 rc_cd_read_sector(track_handle, sector, buffer, 32); 1908 if (memcmp("PC-FX:Hu_CD-ROM", &buffer[0], 15) != 0) 1909 { 1910 rc_cd_close_track(track_handle); 1911 1912 /* not found in the largest data track, check track 2 */ 1913 track_handle = rc_cd_open_track(path, 2); 1914 if (!track_handle) 1915 return rc_hash_error("Could not open track"); 1916 1917 sector = rc_cd_first_track_sector(track_handle); 1918 rc_cd_read_sector(track_handle, sector, buffer, 32); 1919 } 1920 1921 if (memcmp("PC-FX:Hu_CD-ROM", &buffer[0], 15) == 0) 1922 { 1923 /* PC-FX boot header fills the first two sectors of the disc 1924 * https://bitbucket.org/trap15/pcfxtools/src/master/pcfx-cdlink.c 1925 * the important stuff is the first 128 bytes of the second sector (title being the first 32) */ 1926 rc_cd_read_sector(track_handle, sector + 1, buffer, 128); 1927 1928 md5_init(&md5); 1929 md5_append(&md5, buffer, 128); 1930 1931 if (verbose_message_callback) 1932 { 1933 char message[128]; 1934 buffer[128] = '\0'; 1935 snprintf(message, sizeof(message), "Found PC-FX CD, title=%.32s", &buffer[0]); 1936 verbose_message_callback(message); 1937 } 1938 1939 /* the program sector is in bytes 33-36 (assume byte 36 is 0) */ 1940 sector = (buffer[34] << 16) + (buffer[33] << 8) + buffer[32]; 1941 1942 /* the number of sectors the program occupies is in bytes 37-40 (assume byte 40 is 0) */ 1943 num_sectors = (buffer[38] << 16) + (buffer[37] << 8) + buffer[36]; 1944 1945 if (verbose_message_callback) 1946 { 1947 char message[128]; 1948 snprintf(message, sizeof(message), "Hashing %d sectors starting at sector %d", num_sectors, sector); 1949 verbose_message_callback(message); 1950 } 1951 1952 sector += rc_cd_first_track_sector(track_handle); 1953 while (num_sectors > 0) 1954 { 1955 rc_cd_read_sector(track_handle, sector, buffer, sizeof(buffer)); 1956 md5_append(&md5, buffer, sizeof(buffer)); 1957 1958 ++sector; 1959 --num_sectors; 1960 } 1961 } 1962 else 1963 { 1964 int result = 0; 1965 rc_cd_read_sector(track_handle, sector + 1, buffer, 128); 1966 1967 /* some PC-FX CDs still identify as PCE CDs */ 1968 if (memcmp("PC Engine CD-ROM SYSTEM", &buffer[32], 23) == 0) 1969 result = rc_hash_pce_track(hash, track_handle); 1970 1971 rc_cd_close_track(track_handle); 1972 if (result) 1973 return result; 1974 1975 return rc_hash_error("Not a PC-FX CD"); 1976 } 1977 1978 rc_cd_close_track(track_handle); 1979 1980 return rc_hash_finalize(&md5, hash); 1981 } 1982 1983 static int rc_hash_dreamcast(char hash[33], const char* path) 1984 { 1985 uint8_t buffer[256] = ""; 1986 void* track_handle; 1987 char exe_file[32] = ""; 1988 uint32_t size; 1989 uint32_t sector; 1990 int result = 0; 1991 md5_state_t md5; 1992 int i = 0; 1993 1994 /* track 03 is the data track that contains the TOC and IP.BIN */ 1995 track_handle = rc_cd_open_track(path, 3); 1996 if (track_handle) 1997 { 1998 /* first 256 bytes from first sector should have IP.BIN structure that stores game meta information 1999 * https://mc.pp.se/dc/ip.bin.html */ 2000 rc_cd_read_sector(track_handle, rc_cd_first_track_sector(track_handle), buffer, sizeof(buffer)); 2001 } 2002 2003 if (memcmp(&buffer[0], "SEGA SEGAKATANA ", 16) != 0) 2004 { 2005 if (track_handle) 2006 rc_cd_close_track(track_handle); 2007 2008 /* not a gd-rom dreamcast file. check for mil-cd by looking for the marker in the first data track */ 2009 track_handle = rc_cd_open_track(path, RC_HASH_CDTRACK_FIRST_DATA); 2010 if (!track_handle) 2011 return rc_hash_error("Could not open track"); 2012 2013 rc_cd_read_sector(track_handle, rc_cd_first_track_sector(track_handle), buffer, sizeof(buffer)); 2014 if (memcmp(&buffer[0], "SEGA SEGAKATANA ", 16) != 0) 2015 { 2016 /* did not find marker on track 3 or first data track */ 2017 rc_cd_close_track(track_handle); 2018 return rc_hash_error("Not a Dreamcast CD"); 2019 } 2020 } 2021 2022 /* start the hash with the game meta information */ 2023 md5_init(&md5); 2024 md5_append(&md5, (md5_byte_t*)buffer, 256); 2025 2026 if (verbose_message_callback) 2027 { 2028 char message[256]; 2029 uint8_t* ptr = &buffer[0xFF]; 2030 while (ptr > &buffer[0x80] && ptr[-1] == ' ') 2031 --ptr; 2032 *ptr = '\0'; 2033 2034 snprintf(message, sizeof(message), "Found Dreamcast CD: %.128s (%.16s)", (const char*)&buffer[0x80], (const char*)&buffer[0x40]); 2035 verbose_message_callback(message); 2036 } 2037 2038 /* the boot filename is 96 bytes into the meta information (https://mc.pp.se/dc/ip0000.bin.html) */ 2039 /* remove whitespace from bootfile */ 2040 i = 0; 2041 while (!isspace((unsigned char)buffer[96 + i]) && i < 16) 2042 ++i; 2043 2044 /* sometimes boot file isn't present on meta information. 2045 * nothing can be done, as even the core doesn't run the game in this case. */ 2046 if (i == 0) 2047 { 2048 rc_cd_close_track(track_handle); 2049 return rc_hash_error("Boot executable not specified on IP.BIN"); 2050 } 2051 2052 memcpy(exe_file, &buffer[96], i); 2053 exe_file[i] = '\0'; 2054 2055 sector = rc_cd_find_file_sector(track_handle, exe_file, &size); 2056 if (sector == 0) 2057 { 2058 rc_cd_close_track(track_handle); 2059 return rc_hash_error("Could not locate boot executable"); 2060 } 2061 2062 if (rc_cd_read_sector(track_handle, sector, buffer, 1)) 2063 { 2064 /* the boot executable is in the primary data track */ 2065 } 2066 else 2067 { 2068 rc_cd_close_track(track_handle); 2069 2070 /* the boot executable is normally in the last track */ 2071 track_handle = rc_cd_open_track(path, RC_HASH_CDTRACK_LAST); 2072 } 2073 2074 result = rc_hash_cd_file(&md5, track_handle, sector, NULL, size, "boot executable"); 2075 rc_cd_close_track(track_handle); 2076 2077 rc_hash_finalize(&md5, hash); 2078 return result; 2079 } 2080 2081 static int rc_hash_find_playstation_executable(void* track_handle, const char* boot_key, const char* cdrom_prefix, 2082 char exe_name[], uint32_t exe_name_size, uint32_t* exe_size) 2083 { 2084 uint8_t buffer[2048]; 2085 uint32_t size; 2086 char* ptr; 2087 char* start; 2088 const size_t boot_key_len = strlen(boot_key); 2089 const size_t cdrom_prefix_len = strlen(cdrom_prefix); 2090 int sector; 2091 2092 sector = rc_cd_find_file_sector(track_handle, "SYSTEM.CNF", NULL); 2093 if (!sector) 2094 return 0; 2095 2096 size = (uint32_t)rc_cd_read_sector(track_handle, sector, buffer, sizeof(buffer) - 1); 2097 buffer[size] = '\0'; 2098 2099 sector = 0; 2100 for (ptr = (char*)buffer; *ptr; ++ptr) 2101 { 2102 if (strncmp(ptr, boot_key, boot_key_len) == 0) 2103 { 2104 ptr += boot_key_len; 2105 while (isspace((unsigned char)*ptr)) 2106 ++ptr; 2107 2108 if (*ptr == '=') 2109 { 2110 ++ptr; 2111 while (isspace((unsigned char)*ptr)) 2112 ++ptr; 2113 2114 if (strncmp(ptr, cdrom_prefix, cdrom_prefix_len) == 0) 2115 ptr += cdrom_prefix_len; 2116 while (*ptr == '\\') 2117 ++ptr; 2118 2119 start = ptr; 2120 while (!isspace((unsigned char)*ptr) && *ptr != ';') 2121 ++ptr; 2122 2123 size = (uint32_t)(ptr - start); 2124 if (size >= exe_name_size) 2125 size = exe_name_size - 1; 2126 2127 memcpy(exe_name, start, size); 2128 exe_name[size] = '\0'; 2129 2130 if (verbose_message_callback) 2131 { 2132 snprintf((char*)buffer, sizeof(buffer), "Looking for boot executable: %s", exe_name); 2133 verbose_message_callback((const char*)buffer); 2134 } 2135 2136 sector = rc_cd_find_file_sector(track_handle, exe_name, exe_size); 2137 break; 2138 } 2139 } 2140 2141 /* advance to end of line */ 2142 while (*ptr && *ptr != '\n') 2143 ++ptr; 2144 } 2145 2146 return sector; 2147 } 2148 2149 static int rc_hash_psx(char hash[33], const char* path) 2150 { 2151 uint8_t buffer[32]; 2152 char exe_name[64] = ""; 2153 void* track_handle; 2154 uint32_t sector; 2155 uint32_t size; 2156 int result = 0; 2157 md5_state_t md5; 2158 2159 track_handle = rc_cd_open_track(path, 1); 2160 if (!track_handle) 2161 return rc_hash_error("Could not open track"); 2162 2163 sector = rc_hash_find_playstation_executable(track_handle, "BOOT", "cdrom:", exe_name, sizeof(exe_name), &size); 2164 if (!sector) 2165 { 2166 sector = rc_cd_find_file_sector(track_handle, "PSX.EXE", &size); 2167 if (sector) 2168 memcpy(exe_name, "PSX.EXE", 8); 2169 } 2170 2171 if (!sector) 2172 { 2173 rc_hash_error("Could not locate primary executable"); 2174 } 2175 else if (rc_cd_read_sector(track_handle, sector, buffer, sizeof(buffer)) < sizeof(buffer)) 2176 { 2177 rc_hash_error("Could not read primary executable"); 2178 } 2179 else 2180 { 2181 if (memcmp(buffer, "PS-X EXE", 7) != 0) 2182 { 2183 if (verbose_message_callback) 2184 { 2185 char message[128]; 2186 snprintf(message, sizeof(message), "%s did not contain PS-X EXE marker", exe_name); 2187 verbose_message_callback(message); 2188 } 2189 } 2190 else 2191 { 2192 /* the PS-X EXE header specifies the executable size as a 4-byte value 28 bytes into the header, which doesn't 2193 * include the header itself. We want to include the header in the hash, so append another 2048 to that value. 2194 */ 2195 size = (((uint8_t)buffer[31] << 24) | ((uint8_t)buffer[30] << 16) | ((uint8_t)buffer[29] << 8) | (uint8_t)buffer[28]) + 2048; 2196 } 2197 2198 /* there's a few games that use a singular engine and only differ via their data files. luckily, they have unique 2199 * serial numbers, and use the serial number as the boot file in the standard way. include the boot file name in the hash. 2200 */ 2201 md5_init(&md5); 2202 md5_append(&md5, (md5_byte_t*)exe_name, (int)strlen(exe_name)); 2203 2204 result = rc_hash_cd_file(&md5, track_handle, sector, exe_name, size, "primary executable"); 2205 rc_hash_finalize(&md5, hash); 2206 } 2207 2208 rc_cd_close_track(track_handle); 2209 2210 return result; 2211 } 2212 2213 static int rc_hash_ps2(char hash[33], const char* path) 2214 { 2215 uint8_t buffer[4]; 2216 char exe_name[64] = ""; 2217 void* track_handle; 2218 uint32_t sector; 2219 uint32_t size; 2220 int result = 0; 2221 md5_state_t md5; 2222 2223 track_handle = rc_cd_open_track(path, 1); 2224 if (!track_handle) 2225 return rc_hash_error("Could not open track"); 2226 2227 sector = rc_hash_find_playstation_executable(track_handle, "BOOT2", "cdrom0:", exe_name, sizeof(exe_name), &size); 2228 if (!sector) 2229 { 2230 rc_hash_error("Could not locate primary executable"); 2231 } 2232 else if (rc_cd_read_sector(track_handle, sector, buffer, sizeof(buffer)) < sizeof(buffer)) 2233 { 2234 rc_hash_error("Could not read primary executable"); 2235 } 2236 else 2237 { 2238 if (memcmp(buffer, "\x7f\x45\x4c\x46", 4) != 0) 2239 { 2240 if (verbose_message_callback) 2241 { 2242 char message[128]; 2243 snprintf(message, sizeof(message), "%s did not contain ELF marker", exe_name); 2244 verbose_message_callback(message); 2245 } 2246 } 2247 2248 /* there's a few games that use a singular engine and only differ via their data files. luckily, they have unique 2249 * serial numbers, and use the serial number as the boot file in the standard way. include the boot file name in the hash. 2250 */ 2251 md5_init(&md5); 2252 md5_append(&md5, (md5_byte_t*)exe_name, (int)strlen(exe_name)); 2253 2254 result = rc_hash_cd_file(&md5, track_handle, sector, exe_name, size, "primary executable"); 2255 rc_hash_finalize(&md5, hash); 2256 } 2257 2258 rc_cd_close_track(track_handle); 2259 2260 return result; 2261 } 2262 2263 static int rc_hash_psp(char hash[33], const char* path) 2264 { 2265 void* track_handle; 2266 uint32_t sector; 2267 uint32_t size; 2268 md5_state_t md5; 2269 2270 /* https://www.psdevwiki.com/psp/PBP 2271 * A PBP file is an archive containing the PARAM.SFO, primary executable, and a bunch of metadata. 2272 * While we could extract the PARAM.SFO and primary executable to mimic the normal PSP hashing logic, 2273 * it's easier to just hash the entire file. This also helps alleviate issues where the primary 2274 * executable is just a game engine and the only differentiating data would be the metadata. */ 2275 if (rc_path_compare_extension(path, "pbp")) 2276 return rc_hash_whole_file(hash, path); 2277 2278 track_handle = rc_cd_open_track(path, 1); 2279 if (!track_handle) 2280 return rc_hash_error("Could not open track"); 2281 2282 /* http://www.romhacking.net/forum/index.php?topic=30899.0 2283 * PSP_GAME/PARAM.SFO contains key/value pairs identifying the game for the system (i.e. serial number, 2284 * name, version). PSP_GAME/SYSDIR/EBOOT.BIN is the encrypted primary executable. 2285 */ 2286 sector = rc_cd_find_file_sector(track_handle, "PSP_GAME\\PARAM.SFO", &size); 2287 if (!sector) 2288 { 2289 rc_cd_close_track(track_handle); 2290 return rc_hash_error("Not a PSP game disc"); 2291 } 2292 2293 md5_init(&md5); 2294 if (!rc_hash_cd_file(&md5, track_handle, sector, NULL, size, "PSP_GAME\\PARAM.SFO")) 2295 { 2296 rc_cd_close_track(track_handle); 2297 return 0; 2298 } 2299 2300 sector = rc_cd_find_file_sector(track_handle, "PSP_GAME\\SYSDIR\\EBOOT.BIN", &size); 2301 if (!sector) 2302 { 2303 rc_cd_close_track(track_handle); 2304 return rc_hash_error("Could not find primary executable"); 2305 } 2306 2307 if (!rc_hash_cd_file(&md5, track_handle, sector, NULL, size, "PSP_GAME\\SYSDIR\\EBOOT.BIN")) 2308 { 2309 rc_cd_close_track(track_handle); 2310 return 0; 2311 } 2312 2313 rc_cd_close_track(track_handle); 2314 return rc_hash_finalize(&md5, hash); 2315 } 2316 2317 static int rc_hash_sega_cd(char hash[33], const char* path) 2318 { 2319 uint8_t buffer[512]; 2320 void* track_handle; 2321 2322 track_handle = rc_cd_open_track(path, 1); 2323 if (!track_handle) 2324 return rc_hash_error("Could not open track"); 2325 2326 /* the first 512 bytes of sector 0 are a volume header and ROM header that uniquely identify the game. 2327 * After that is an arbitrary amount of code that ensures the game is being run in the correct region. 2328 * Then more arbitrary code follows that actually starts the boot process. Somewhere in there, the 2329 * primary executable is loaded. In many cases, a single game will have multiple executables, so even 2330 * if we could determine the primary one, it's just the tip of the iceberg. As such, we've decided that 2331 * hashing the volume and ROM headers is sufficient for identifying the game, and we'll have to trust 2332 * that our players aren't modifying anything else on the disc. 2333 */ 2334 rc_cd_read_sector(track_handle, 0, buffer, sizeof(buffer)); 2335 rc_cd_close_track(track_handle); 2336 2337 if (memcmp(buffer, "SEGADISCSYSTEM ", 16) != 0 && /* Sega CD */ 2338 memcmp(buffer, "SEGA SEGASATURN ", 16) != 0) /* Sega Saturn */ 2339 { 2340 return rc_hash_error("Not a Sega CD"); 2341 } 2342 2343 return rc_hash_buffer(hash, buffer, sizeof(buffer)); 2344 } 2345 2346 static int rc_hash_scv(char hash[33], const uint8_t* buffer, size_t buffer_size) 2347 { 2348 /* if the file contains a header, ignore it */ 2349 /* https://gitlab.com/MaaaX-EmuSCV/libretro-emuscv/-/blob/master/readme.txt#L211 */ 2350 if (memcmp(buffer, "EmuSCV", 6) == 0) 2351 { 2352 rc_hash_verbose("Ignoring SCV header"); 2353 2354 buffer += 32; 2355 buffer_size -= 32; 2356 } 2357 2358 return rc_hash_buffer(hash, buffer, buffer_size); 2359 } 2360 2361 static int rc_hash_snes(char hash[33], const uint8_t* buffer, size_t buffer_size) 2362 { 2363 /* if the file contains a header, ignore it */ 2364 uint32_t calc_size = ((uint32_t)buffer_size / 0x2000) * 0x2000; 2365 if (buffer_size - calc_size == 512) 2366 { 2367 rc_hash_verbose("Ignoring SNES header"); 2368 2369 buffer += 512; 2370 buffer_size -= 512; 2371 } 2372 2373 return rc_hash_buffer(hash, buffer, buffer_size); 2374 } 2375 2376 struct rc_buffered_file 2377 { 2378 const uint8_t* read_ptr; 2379 const uint8_t* data; 2380 size_t data_size; 2381 }; 2382 2383 static struct rc_buffered_file rc_buffered_file; 2384 2385 static void* rc_file_open_buffered_file(const char* path) 2386 { 2387 struct rc_buffered_file* handle = (struct rc_buffered_file*)malloc(sizeof(struct rc_buffered_file)); 2388 (void)path; 2389 2390 if (handle) 2391 memcpy(handle, &rc_buffered_file, sizeof(rc_buffered_file)); 2392 2393 return handle; 2394 } 2395 2396 void rc_file_seek_buffered_file(void* file_handle, int64_t offset, int origin) 2397 { 2398 struct rc_buffered_file* buffered_file = (struct rc_buffered_file*)file_handle; 2399 switch (origin) 2400 { 2401 case SEEK_SET: buffered_file->read_ptr = buffered_file->data + offset; break; 2402 case SEEK_CUR: buffered_file->read_ptr += offset; break; 2403 case SEEK_END: buffered_file->read_ptr = buffered_file->data + buffered_file->data_size + offset; break; 2404 } 2405 2406 if (buffered_file->read_ptr < buffered_file->data) 2407 buffered_file->read_ptr = buffered_file->data; 2408 else if (buffered_file->read_ptr > buffered_file->data + buffered_file->data_size) 2409 buffered_file->read_ptr = buffered_file->data + buffered_file->data_size; 2410 } 2411 2412 int64_t rc_file_tell_buffered_file(void* file_handle) 2413 { 2414 struct rc_buffered_file* buffered_file = (struct rc_buffered_file*)file_handle; 2415 return (buffered_file->read_ptr - buffered_file->data); 2416 } 2417 2418 size_t rc_file_read_buffered_file(void* file_handle, void* buffer, size_t requested_bytes) 2419 { 2420 struct rc_buffered_file* buffered_file = (struct rc_buffered_file*)file_handle; 2421 const int64_t remaining = buffered_file->data_size - (buffered_file->read_ptr - buffered_file->data); 2422 if ((int)requested_bytes > remaining) 2423 requested_bytes = (int)remaining; 2424 2425 memcpy(buffer, buffered_file->read_ptr, requested_bytes); 2426 buffered_file->read_ptr += requested_bytes; 2427 return requested_bytes; 2428 } 2429 2430 void rc_file_close_buffered_file(void* file_handle) 2431 { 2432 free(file_handle); 2433 } 2434 2435 static int rc_hash_file_from_buffer(char hash[33], uint32_t console_id, const uint8_t* buffer, size_t buffer_size) 2436 { 2437 struct rc_hash_filereader buffered_filereader_funcs; 2438 struct rc_hash_filereader* old_filereader = filereader; 2439 int result; 2440 2441 memset(&buffered_filereader_funcs, 0, sizeof(buffered_filereader_funcs)); 2442 buffered_filereader_funcs.open = rc_file_open_buffered_file; 2443 buffered_filereader_funcs.close = rc_file_close_buffered_file; 2444 buffered_filereader_funcs.read = rc_file_read_buffered_file; 2445 buffered_filereader_funcs.seek = rc_file_seek_buffered_file; 2446 buffered_filereader_funcs.tell = rc_file_tell_buffered_file; 2447 filereader = &buffered_filereader_funcs; 2448 2449 rc_buffered_file.data = rc_buffered_file.read_ptr = buffer; 2450 rc_buffered_file.data_size = buffer_size; 2451 2452 result = rc_hash_generate_from_file(hash, console_id, "[buffered file]"); 2453 2454 filereader = old_filereader; 2455 return result; 2456 } 2457 2458 int rc_hash_generate_from_buffer(char hash[33], uint32_t console_id, const uint8_t* buffer, size_t buffer_size) 2459 { 2460 switch (console_id) 2461 { 2462 default: 2463 { 2464 char message[128]; 2465 snprintf(message, sizeof(message), "Unsupported console for buffer hash: %d", console_id); 2466 return rc_hash_error(message); 2467 } 2468 2469 case RC_CONSOLE_AMSTRAD_PC: 2470 case RC_CONSOLE_APPLE_II: 2471 case RC_CONSOLE_ARCADIA_2001: 2472 case RC_CONSOLE_ATARI_2600: 2473 case RC_CONSOLE_ATARI_JAGUAR: 2474 case RC_CONSOLE_COLECOVISION: 2475 case RC_CONSOLE_COMMODORE_64: 2476 case RC_CONSOLE_ELEKTOR_TV_GAMES_COMPUTER: 2477 case RC_CONSOLE_FAIRCHILD_CHANNEL_F: 2478 case RC_CONSOLE_GAMEBOY: 2479 case RC_CONSOLE_GAMEBOY_ADVANCE: 2480 case RC_CONSOLE_GAMEBOY_COLOR: 2481 case RC_CONSOLE_GAME_GEAR: 2482 case RC_CONSOLE_INTELLIVISION: 2483 case RC_CONSOLE_INTERTON_VC_4000: 2484 case RC_CONSOLE_MAGNAVOX_ODYSSEY2: 2485 case RC_CONSOLE_MASTER_SYSTEM: 2486 case RC_CONSOLE_MEGA_DRIVE: 2487 case RC_CONSOLE_MEGADUCK: 2488 case RC_CONSOLE_MSX: 2489 case RC_CONSOLE_NEOGEO_POCKET: 2490 case RC_CONSOLE_ORIC: 2491 case RC_CONSOLE_PC8800: 2492 case RC_CONSOLE_POKEMON_MINI: 2493 case RC_CONSOLE_SEGA_32X: 2494 case RC_CONSOLE_SG1000: 2495 case RC_CONSOLE_SUPERVISION: 2496 case RC_CONSOLE_TI83: 2497 case RC_CONSOLE_TIC80: 2498 case RC_CONSOLE_UZEBOX: 2499 case RC_CONSOLE_VECTREX: 2500 case RC_CONSOLE_VIRTUAL_BOY: 2501 case RC_CONSOLE_WASM4: 2502 case RC_CONSOLE_WONDERSWAN: 2503 return rc_hash_buffer(hash, buffer, buffer_size); 2504 2505 case RC_CONSOLE_ARDUBOY: 2506 /* https://en.wikipedia.org/wiki/Intel_HEX */ 2507 return rc_hash_text(hash, buffer, buffer_size); 2508 2509 case RC_CONSOLE_ATARI_7800: 2510 return rc_hash_7800(hash, buffer, buffer_size); 2511 2512 case RC_CONSOLE_ATARI_LYNX: 2513 return rc_hash_lynx(hash, buffer, buffer_size); 2514 2515 case RC_CONSOLE_NINTENDO: 2516 return rc_hash_nes(hash, buffer, buffer_size); 2517 2518 case RC_CONSOLE_PC_ENGINE: /* NOTE: does not support PCEngine CD */ 2519 return rc_hash_pce(hash, buffer, buffer_size); 2520 2521 case RC_CONSOLE_SUPER_CASSETTEVISION: 2522 return rc_hash_scv(hash, buffer, buffer_size); 2523 2524 case RC_CONSOLE_SUPER_NINTENDO: 2525 return rc_hash_snes(hash, buffer, buffer_size); 2526 2527 case RC_CONSOLE_NINTENDO_64: 2528 case RC_CONSOLE_NINTENDO_3DS: 2529 case RC_CONSOLE_NINTENDO_DS: 2530 case RC_CONSOLE_NINTENDO_DSI: 2531 return rc_hash_file_from_buffer(hash, console_id, buffer, buffer_size); 2532 } 2533 } 2534 2535 static int rc_hash_whole_file(char hash[33], const char* path) 2536 { 2537 md5_state_t md5; 2538 uint8_t* buffer; 2539 int64_t size; 2540 const size_t buffer_size = 65536; 2541 void* file_handle; 2542 size_t remaining; 2543 int result = 0; 2544 2545 file_handle = rc_file_open(path); 2546 if (!file_handle) 2547 return rc_hash_error("Could not open file"); 2548 2549 rc_file_seek(file_handle, 0, SEEK_END); 2550 size = rc_file_tell(file_handle); 2551 2552 if (verbose_message_callback) 2553 { 2554 char message[1024]; 2555 if (size > MAX_BUFFER_SIZE) 2556 snprintf(message, sizeof(message), "Hashing first %u bytes (of %u bytes) of %s", MAX_BUFFER_SIZE, (unsigned)size, rc_path_get_filename(path)); 2557 else 2558 snprintf(message, sizeof(message), "Hashing %s (%u bytes)", rc_path_get_filename(path), (unsigned)size); 2559 verbose_message_callback(message); 2560 } 2561 2562 if (size > MAX_BUFFER_SIZE) 2563 remaining = MAX_BUFFER_SIZE; 2564 else 2565 remaining = (size_t)size; 2566 2567 md5_init(&md5); 2568 2569 buffer = (uint8_t*)malloc(buffer_size); 2570 if (buffer) 2571 { 2572 rc_file_seek(file_handle, 0, SEEK_SET); 2573 while (remaining >= buffer_size) 2574 { 2575 rc_file_read(file_handle, buffer, (int)buffer_size); 2576 md5_append(&md5, buffer, (int)buffer_size); 2577 remaining -= buffer_size; 2578 } 2579 2580 if (remaining > 0) 2581 { 2582 rc_file_read(file_handle, buffer, (int)remaining); 2583 md5_append(&md5, buffer, (int)remaining); 2584 } 2585 2586 free(buffer); 2587 result = rc_hash_finalize(&md5, hash); 2588 } 2589 2590 rc_file_close(file_handle); 2591 return result; 2592 } 2593 2594 static int rc_hash_buffered_file(char hash[33], uint32_t console_id, const char* path) 2595 { 2596 uint8_t* buffer; 2597 int64_t size; 2598 int result = 0; 2599 void* file_handle; 2600 2601 file_handle = rc_file_open(path); 2602 if (!file_handle) 2603 return rc_hash_error("Could not open file"); 2604 2605 rc_file_seek(file_handle, 0, SEEK_END); 2606 size = rc_file_tell(file_handle); 2607 2608 if (verbose_message_callback) 2609 { 2610 char message[1024]; 2611 if (size > MAX_BUFFER_SIZE) 2612 snprintf(message, sizeof(message), "Buffering first %u bytes (of %d bytes) of %s", MAX_BUFFER_SIZE, (unsigned)size, rc_path_get_filename(path)); 2613 else 2614 snprintf(message, sizeof(message), "Buffering %s (%d bytes)", rc_path_get_filename(path), (unsigned)size); 2615 verbose_message_callback(message); 2616 } 2617 2618 if (size > MAX_BUFFER_SIZE) 2619 size = MAX_BUFFER_SIZE; 2620 2621 buffer = (uint8_t*)malloc((size_t)size); 2622 if (buffer) 2623 { 2624 rc_file_seek(file_handle, 0, SEEK_SET); 2625 rc_file_read(file_handle, buffer, (int)size); 2626 2627 result = rc_hash_generate_from_buffer(hash, console_id, buffer, (size_t)size); 2628 2629 free(buffer); 2630 } 2631 2632 rc_file_close(file_handle); 2633 return result; 2634 } 2635 2636 static int rc_hash_path_is_absolute(const char* path) 2637 { 2638 if (!path[0]) 2639 return 0; 2640 2641 /* "/path/to/file" or "\path\to\file" */ 2642 if (path[0] == '/' || path[0] == '\\') 2643 return 1; 2644 2645 /* "C:\path\to\file" */ 2646 if (path[1] == ':' && path[2] == '\\') 2647 return 1; 2648 2649 /* "scheme:/path/to/file" */ 2650 while (*path) 2651 { 2652 if (path[0] == ':' && path[1] == '/') 2653 return 1; 2654 2655 ++path; 2656 } 2657 2658 return 0; 2659 } 2660 2661 static const char* rc_hash_get_first_item_from_playlist(const char* path) 2662 { 2663 char buffer[1024]; 2664 char* disc_path; 2665 char* ptr, *start, *next; 2666 size_t num_read, path_len, file_len; 2667 void* file_handle; 2668 2669 file_handle = rc_file_open(path); 2670 if (!file_handle) 2671 { 2672 rc_hash_error("Could not open playlist"); 2673 return NULL; 2674 } 2675 2676 num_read = rc_file_read(file_handle, buffer, sizeof(buffer) - 1); 2677 buffer[num_read] = '\0'; 2678 2679 rc_file_close(file_handle); 2680 2681 ptr = start = buffer; 2682 do 2683 { 2684 /* ignore empty and commented lines */ 2685 while (*ptr == '#' || *ptr == '\r' || *ptr == '\n') 2686 { 2687 while (*ptr && *ptr != '\n') 2688 ++ptr; 2689 if (*ptr) 2690 ++ptr; 2691 } 2692 2693 /* find and extract the current line */ 2694 start = ptr; 2695 while (*ptr && *ptr != '\n') 2696 ++ptr; 2697 next = ptr; 2698 2699 /* remove trailing whitespace - especially '\r' */ 2700 while (ptr > start && isspace((unsigned char)ptr[-1])) 2701 --ptr; 2702 2703 /* if we found a non-empty line, break out of the loop to process it */ 2704 file_len = ptr - start; 2705 if (file_len) 2706 break; 2707 2708 /* did we reach the end of the file? */ 2709 if (!*next) 2710 return NULL; 2711 2712 /* if the line only contained whitespace, keep searching */ 2713 ptr = next + 1; 2714 } while (1); 2715 2716 if (verbose_message_callback) 2717 { 2718 char message[1024]; 2719 snprintf(message, sizeof(message), "Extracted %.*s from playlist", (int)file_len, start); 2720 verbose_message_callback(message); 2721 } 2722 2723 start[file_len++] = '\0'; 2724 if (rc_hash_path_is_absolute(start)) 2725 path_len = 0; 2726 else 2727 path_len = rc_path_get_filename(path) - path; 2728 2729 disc_path = (char*)malloc(path_len + file_len + 1); 2730 if (!disc_path) 2731 return NULL; 2732 2733 if (path_len) 2734 memcpy(disc_path, path, path_len); 2735 2736 memcpy(&disc_path[path_len], start, file_len); 2737 return disc_path; 2738 } 2739 2740 static int rc_hash_generate_from_playlist(char hash[33], uint32_t console_id, const char* path) 2741 { 2742 int result; 2743 const char* disc_path; 2744 2745 if (verbose_message_callback) 2746 { 2747 char message[1024]; 2748 snprintf(message, sizeof(message), "Processing playlist: %s", rc_path_get_filename(path)); 2749 verbose_message_callback(message); 2750 } 2751 2752 disc_path = rc_hash_get_first_item_from_playlist(path); 2753 if (!disc_path) 2754 return rc_hash_error("Failed to get first item from playlist"); 2755 2756 result = rc_hash_generate_from_file(hash, console_id, disc_path); 2757 2758 free((void*)disc_path); 2759 return result; 2760 } 2761 2762 int rc_hash_generate_from_file(char hash[33], uint32_t console_id, const char* path) 2763 { 2764 switch (console_id) 2765 { 2766 default: 2767 { 2768 char buffer[128]; 2769 snprintf(buffer, sizeof(buffer), "Unsupported console for file hash: %d", console_id); 2770 return rc_hash_error(buffer); 2771 } 2772 2773 case RC_CONSOLE_ARCADIA_2001: 2774 case RC_CONSOLE_ATARI_2600: 2775 case RC_CONSOLE_ATARI_JAGUAR: 2776 case RC_CONSOLE_COLECOVISION: 2777 case RC_CONSOLE_ELEKTOR_TV_GAMES_COMPUTER: 2778 case RC_CONSOLE_FAIRCHILD_CHANNEL_F: 2779 case RC_CONSOLE_GAMEBOY: 2780 case RC_CONSOLE_GAMEBOY_ADVANCE: 2781 case RC_CONSOLE_GAMEBOY_COLOR: 2782 case RC_CONSOLE_GAME_GEAR: 2783 case RC_CONSOLE_INTELLIVISION: 2784 case RC_CONSOLE_INTERTON_VC_4000: 2785 case RC_CONSOLE_MAGNAVOX_ODYSSEY2: 2786 case RC_CONSOLE_MASTER_SYSTEM: 2787 case RC_CONSOLE_MEGADUCK: 2788 case RC_CONSOLE_NEOGEO_POCKET: 2789 case RC_CONSOLE_ORIC: 2790 case RC_CONSOLE_POKEMON_MINI: 2791 case RC_CONSOLE_SEGA_32X: 2792 case RC_CONSOLE_SG1000: 2793 case RC_CONSOLE_SUPERVISION: 2794 case RC_CONSOLE_TI83: 2795 case RC_CONSOLE_TIC80: 2796 case RC_CONSOLE_UZEBOX: 2797 case RC_CONSOLE_VECTREX: 2798 case RC_CONSOLE_VIRTUAL_BOY: 2799 case RC_CONSOLE_WASM4: 2800 case RC_CONSOLE_WONDERSWAN: 2801 /* generic whole-file hash - don't buffer */ 2802 return rc_hash_whole_file(hash, path); 2803 2804 case RC_CONSOLE_AMSTRAD_PC: 2805 case RC_CONSOLE_APPLE_II: 2806 case RC_CONSOLE_COMMODORE_64: 2807 case RC_CONSOLE_MEGA_DRIVE: 2808 case RC_CONSOLE_MSX: 2809 case RC_CONSOLE_PC8800: 2810 /* generic whole-file hash with m3u support - don't buffer */ 2811 if (rc_path_compare_extension(path, "m3u")) 2812 return rc_hash_generate_from_playlist(hash, console_id, path); 2813 2814 return rc_hash_whole_file(hash, path); 2815 2816 case RC_CONSOLE_ARDUBOY: 2817 case RC_CONSOLE_ATARI_7800: 2818 case RC_CONSOLE_ATARI_LYNX: 2819 case RC_CONSOLE_NINTENDO: 2820 case RC_CONSOLE_PC_ENGINE: 2821 case RC_CONSOLE_SUPER_CASSETTEVISION: 2822 case RC_CONSOLE_SUPER_NINTENDO: 2823 /* additional logic whole-file hash - buffer then call rc_hash_generate_from_buffer */ 2824 return rc_hash_buffered_file(hash, console_id, path); 2825 2826 case RC_CONSOLE_3DO: 2827 if (rc_path_compare_extension(path, "m3u")) 2828 return rc_hash_generate_from_playlist(hash, console_id, path); 2829 2830 return rc_hash_3do(hash, path); 2831 2832 case RC_CONSOLE_ARCADE: 2833 return rc_hash_arcade(hash, path); 2834 2835 case RC_CONSOLE_ATARI_JAGUAR_CD: 2836 return rc_hash_jaguar_cd(hash, path); 2837 2838 case RC_CONSOLE_DREAMCAST: 2839 if (rc_path_compare_extension(path, "m3u")) 2840 return rc_hash_generate_from_playlist(hash, console_id, path); 2841 2842 return rc_hash_dreamcast(hash, path); 2843 2844 case RC_CONSOLE_GAMECUBE: 2845 return rc_hash_gamecube(hash, path); 2846 2847 case RC_CONSOLE_MS_DOS: 2848 return rc_hash_ms_dos(hash, path); 2849 2850 case RC_CONSOLE_NEO_GEO_CD: 2851 return rc_hash_neogeo_cd(hash, path); 2852 2853 case RC_CONSOLE_NINTENDO_64: 2854 return rc_hash_n64(hash, path); 2855 2856 case RC_CONSOLE_NINTENDO_DS: 2857 case RC_CONSOLE_NINTENDO_DSI: 2858 return rc_hash_nintendo_ds(hash, path); 2859 2860 case RC_CONSOLE_PC_ENGINE_CD: 2861 if (rc_path_compare_extension(path, "cue") || rc_path_compare_extension(path, "chd")) 2862 return rc_hash_pce_cd(hash, path); 2863 2864 if (rc_path_compare_extension(path, "m3u")) 2865 return rc_hash_generate_from_playlist(hash, console_id, path); 2866 2867 return rc_hash_buffered_file(hash, console_id, path); 2868 2869 case RC_CONSOLE_PCFX: 2870 if (rc_path_compare_extension(path, "m3u")) 2871 return rc_hash_generate_from_playlist(hash, console_id, path); 2872 2873 return rc_hash_pcfx_cd(hash, path); 2874 2875 case RC_CONSOLE_PLAYSTATION: 2876 if (rc_path_compare_extension(path, "m3u")) 2877 return rc_hash_generate_from_playlist(hash, console_id, path); 2878 2879 return rc_hash_psx(hash, path); 2880 2881 case RC_CONSOLE_PLAYSTATION_2: 2882 if (rc_path_compare_extension(path, "m3u")) 2883 return rc_hash_generate_from_playlist(hash, console_id, path); 2884 2885 return rc_hash_ps2(hash, path); 2886 2887 case RC_CONSOLE_PSP: 2888 return rc_hash_psp(hash, path); 2889 2890 case RC_CONSOLE_SEGA_CD: 2891 case RC_CONSOLE_SATURN: 2892 if (rc_path_compare_extension(path, "m3u")) 2893 return rc_hash_generate_from_playlist(hash, console_id, path); 2894 2895 return rc_hash_sega_cd(hash, path); 2896 } 2897 } 2898 2899 static void rc_hash_iterator_append_console(struct rc_hash_iterator* iterator, uint8_t console_id) 2900 { 2901 int i = 0; 2902 while (iterator->consoles[i] != 0) 2903 { 2904 if (iterator->consoles[i] == console_id) 2905 return; 2906 2907 ++i; 2908 } 2909 2910 iterator->consoles[i] = console_id; 2911 } 2912 2913 static void rc_hash_initialize_dsk_iterator(struct rc_hash_iterator* iterator, const char* path) 2914 { 2915 size_t size = iterator->buffer_size; 2916 if (size == 0) 2917 { 2918 /* attempt to use disk size to determine system */ 2919 void* file = rc_file_open(path); 2920 if (file) 2921 { 2922 rc_file_seek(file, 0, SEEK_END); 2923 size = (size_t)rc_file_tell(file); 2924 rc_file_close(file); 2925 } 2926 } 2927 2928 if (size == 512 * 9 * 80) /* 360KB */ 2929 { 2930 /* FAT-12 3.5" DD (512 byte sectors, 9 sectors per track, 80 tracks per side */ 2931 /* FAT-12 5.25" DD double-sided (512 byte sectors, 9 sectors per track, 80 tracks per side */ 2932 iterator->consoles[0] = RC_CONSOLE_MSX; 2933 } 2934 else if (size == 512 * 9 * 80 * 2) /* 720KB */ 2935 { 2936 /* FAT-12 3.5" DD double-sided (512 byte sectors, 9 sectors per track, 80 tracks per side */ 2937 iterator->consoles[0] = RC_CONSOLE_MSX; 2938 } 2939 else if (size == 512 * 9 * 40) /* 180KB */ 2940 { 2941 /* FAT-12 5.25" DD (512 byte sectors, 9 sectors per track, 40 tracks per side */ 2942 iterator->consoles[0] = RC_CONSOLE_MSX; 2943 2944 /* AMSDOS 3" - 40 tracks */ 2945 iterator->consoles[1] = RC_CONSOLE_AMSTRAD_PC; 2946 } 2947 else if (size == 256 * 16 * 35) /* 140KB */ 2948 { 2949 /* Apple II new format - 256 byte sectors, 16 sectors per track, 35 tracks per side */ 2950 iterator->consoles[0] = RC_CONSOLE_APPLE_II; 2951 } 2952 else if (size == 256 * 13 * 35) /* 113.75KB */ 2953 { 2954 /* Apple II old format - 256 byte sectors, 13 sectors per track, 35 tracks per side */ 2955 iterator->consoles[0] = RC_CONSOLE_APPLE_II; 2956 } 2957 2958 /* once a best guess has been identified, make sure the others are added as fallbacks */ 2959 2960 /* check MSX first, as Apple II isn't supported by RetroArch, and RAppleWin won't use the iterator */ 2961 rc_hash_iterator_append_console(iterator, RC_CONSOLE_MSX); 2962 rc_hash_iterator_append_console(iterator, RC_CONSOLE_AMSTRAD_PC); 2963 rc_hash_iterator_append_console(iterator, RC_CONSOLE_APPLE_II); 2964 } 2965 2966 void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char* path, const uint8_t* buffer, size_t buffer_size) 2967 { 2968 int need_path = !buffer; 2969 2970 memset(iterator, 0, sizeof(*iterator)); 2971 iterator->buffer = buffer; 2972 iterator->buffer_size = buffer_size; 2973 2974 iterator->consoles[0] = 0; 2975 2976 do 2977 { 2978 const char* ext = rc_path_get_extension(path); 2979 switch (tolower(*ext)) 2980 { 2981 case '2': 2982 if (rc_path_compare_extension(ext, "2d")) 2983 { 2984 iterator->consoles[0] = RC_CONSOLE_SHARPX1; 2985 } 2986 break; 2987 2988 case '3': 2989 if (rc_path_compare_extension(ext, "3ds") || 2990 rc_path_compare_extension(ext, "3dsx")) 2991 { 2992 iterator->consoles[0] = RC_CONSOLE_NINTENDO_3DS; 2993 } 2994 break; 2995 2996 case '7': 2997 if (rc_path_compare_extension(ext, "7z")) 2998 { 2999 /* decompressing zip file not supported */ 3000 iterator->consoles[0] = RC_CONSOLE_ARCADE; 3001 need_path = 1; 3002 } 3003 break; 3004 3005 case '8': 3006 /* http://tibasicdev.wikidot.com/file-extensions */ 3007 if (rc_path_compare_extension(ext, "83g") || 3008 rc_path_compare_extension(ext, "83p")) 3009 { 3010 iterator->consoles[0] = RC_CONSOLE_TI83; 3011 } 3012 break; 3013 3014 case 'a': 3015 if (rc_path_compare_extension(ext, "a78")) 3016 { 3017 iterator->consoles[0] = RC_CONSOLE_ATARI_7800; 3018 } 3019 else if (rc_path_compare_extension(ext, "app") || 3020 rc_path_compare_extension(ext, "axf")) 3021 { 3022 iterator->consoles[0] = RC_CONSOLE_NINTENDO_3DS; 3023 } 3024 break; 3025 3026 case 'b': 3027 if (rc_path_compare_extension(ext, "bin")) 3028 { 3029 if (buffer_size == 0) 3030 { 3031 /* raw bin file may be a CD track. if it's more than 32MB, try a CD hash. */ 3032 void* file = rc_file_open(path); 3033 if (file) 3034 { 3035 int64_t size; 3036 3037 rc_file_seek(file, 0, SEEK_END); 3038 size = rc_file_tell(file); 3039 rc_file_close(file); 3040 3041 if (size > 32 * 1024 * 1024) 3042 { 3043 iterator->consoles[0] = RC_CONSOLE_3DO; /* 4DO supports directly opening the bin file */ 3044 iterator->consoles[1] = RC_CONSOLE_PLAYSTATION; /* PCSX ReARMed supports directly opening the bin file*/ 3045 iterator->consoles[2] = RC_CONSOLE_PLAYSTATION_2; /* PCSX2 supports directly opening the bin file*/ 3046 iterator->consoles[3] = RC_CONSOLE_SEGA_CD; /* Genesis Plus GX supports directly opening the bin file*/ 3047 3048 /* fallback to megadrive which just does a full hash. */ 3049 iterator->consoles[4] = RC_CONSOLE_MEGA_DRIVE; 3050 break; 3051 } 3052 } 3053 } 3054 3055 /* bin is associated with MegaDrive, Sega32X, Atari 2600, Watara Supervision, MegaDuck, 3056 * Fairchild Channel F, Arcadia 2001, Interton VC 4000, and Super Cassette Vision. 3057 * Since they all use the same hashing algorithm, only specify one of them */ 3058 iterator->consoles[0] = RC_CONSOLE_MEGA_DRIVE; 3059 } 3060 else if (rc_path_compare_extension(ext, "bs")) 3061 { 3062 iterator->consoles[0] = RC_CONSOLE_SUPER_NINTENDO; 3063 } 3064 break; 3065 3066 case 'c': 3067 if (rc_path_compare_extension(ext, "cue")) 3068 { 3069 iterator->consoles[0] = RC_CONSOLE_PLAYSTATION; 3070 iterator->consoles[1] = RC_CONSOLE_PLAYSTATION_2; 3071 iterator->consoles[2] = RC_CONSOLE_DREAMCAST; 3072 iterator->consoles[3] = RC_CONSOLE_SEGA_CD; /* ASSERT: handles both Sega CD and Saturn */ 3073 iterator->consoles[4] = RC_CONSOLE_PC_ENGINE_CD; 3074 iterator->consoles[5] = RC_CONSOLE_3DO; 3075 iterator->consoles[6] = RC_CONSOLE_PCFX; 3076 iterator->consoles[7] = RC_CONSOLE_NEO_GEO_CD; 3077 iterator->consoles[8] = RC_CONSOLE_ATARI_JAGUAR_CD; 3078 need_path = 1; 3079 } 3080 else if (rc_path_compare_extension(ext, "chd")) 3081 { 3082 iterator->consoles[0] = RC_CONSOLE_PLAYSTATION; 3083 iterator->consoles[1] = RC_CONSOLE_PLAYSTATION_2; 3084 iterator->consoles[2] = RC_CONSOLE_DREAMCAST; 3085 iterator->consoles[3] = RC_CONSOLE_SEGA_CD; /* ASSERT: handles both Sega CD and Saturn */ 3086 iterator->consoles[4] = RC_CONSOLE_PSP; 3087 iterator->consoles[5] = RC_CONSOLE_PC_ENGINE_CD; 3088 iterator->consoles[6] = RC_CONSOLE_3DO; 3089 iterator->consoles[7] = RC_CONSOLE_NEO_GEO_CD; 3090 iterator->consoles[8] = RC_CONSOLE_PCFX; 3091 need_path = 1; 3092 } 3093 else if (rc_path_compare_extension(ext, "col")) 3094 { 3095 iterator->consoles[0] = RC_CONSOLE_COLECOVISION; 3096 } 3097 else if (rc_path_compare_extension(ext, "cas")) 3098 { 3099 iterator->consoles[0] = RC_CONSOLE_MSX; 3100 } 3101 else if (rc_path_compare_extension(ext, "chf")) 3102 { 3103 iterator->consoles[0] = RC_CONSOLE_FAIRCHILD_CHANNEL_F; 3104 } 3105 else if (rc_path_compare_extension(ext, "cart")) 3106 { 3107 iterator->consoles[0] = RC_CONSOLE_SUPER_CASSETTEVISION; 3108 } 3109 else if (rc_path_compare_extension(ext, "cci") || 3110 rc_path_compare_extension(ext, "cia") || 3111 rc_path_compare_extension(ext, "cxi")) 3112 { 3113 iterator->consoles[0] = RC_CONSOLE_NINTENDO_3DS; 3114 } 3115 break; 3116 3117 case 'd': 3118 if (rc_path_compare_extension(ext, "dsk")) 3119 { 3120 rc_hash_initialize_dsk_iterator(iterator, path); 3121 } 3122 else if (rc_path_compare_extension(ext, "d64")) 3123 { 3124 iterator->consoles[0] = RC_CONSOLE_COMMODORE_64; 3125 } 3126 else if (rc_path_compare_extension(ext, "d88")) 3127 { 3128 iterator->consoles[0] = RC_CONSOLE_PC8800; 3129 iterator->consoles[1] = RC_CONSOLE_SHARPX1; 3130 } 3131 else if (rc_path_compare_extension(ext, "dosz")) 3132 { 3133 iterator->consoles[0] = RC_CONSOLE_MS_DOS; 3134 } 3135 break; 3136 3137 case 'e': 3138 if (rc_path_compare_extension(ext, "elf")) 3139 { 3140 /* This should probably apply to more consoles in the future */ 3141 /* Although in any case this just hashes the entire file */ 3142 iterator->consoles[0] = RC_CONSOLE_NINTENDO_3DS; 3143 } 3144 break; 3145 3146 case 'f': 3147 if (rc_path_compare_extension(ext, "fig")) 3148 { 3149 iterator->consoles[0] = RC_CONSOLE_SUPER_NINTENDO; 3150 } 3151 else if (rc_path_compare_extension(ext, "fds")) 3152 { 3153 iterator->consoles[0] = RC_CONSOLE_NINTENDO; 3154 } 3155 else if (rc_path_compare_extension(ext, "fd")) 3156 { 3157 iterator->consoles[0] = RC_CONSOLE_THOMSONTO8; /* disk */ 3158 } 3159 break; 3160 3161 case 'g': 3162 if (rc_path_compare_extension(ext, "gba")) 3163 { 3164 iterator->consoles[0] = RC_CONSOLE_GAMEBOY_ADVANCE; 3165 } 3166 else if (rc_path_compare_extension(ext, "gbc")) 3167 { 3168 iterator->consoles[0] = RC_CONSOLE_GAMEBOY_COLOR; 3169 } 3170 else if (rc_path_compare_extension(ext, "gb")) 3171 { 3172 iterator->consoles[0] = RC_CONSOLE_GAMEBOY; 3173 } 3174 else if (rc_path_compare_extension(ext, "gg")) 3175 { 3176 iterator->consoles[0] = RC_CONSOLE_GAME_GEAR; 3177 } 3178 else if (rc_path_compare_extension(ext, "gdi")) 3179 { 3180 iterator->consoles[0] = RC_CONSOLE_DREAMCAST; 3181 } 3182 break; 3183 3184 case 'h': 3185 if (rc_path_compare_extension(ext, "hex")) 3186 { 3187 iterator->consoles[0] = RC_CONSOLE_ARDUBOY; 3188 } 3189 break; 3190 3191 case 'i': 3192 if (rc_path_compare_extension(ext, "iso")) 3193 { 3194 iterator->consoles[0] = RC_CONSOLE_PLAYSTATION_2; 3195 iterator->consoles[1] = RC_CONSOLE_PSP; 3196 iterator->consoles[2] = RC_CONSOLE_3DO; 3197 iterator->consoles[3] = RC_CONSOLE_SEGA_CD; /* ASSERT: handles both Sega CD and Saturn */ 3198 need_path = 1; 3199 } 3200 break; 3201 3202 case 'j': 3203 if (rc_path_compare_extension(ext, "jag")) 3204 { 3205 iterator->consoles[0] = RC_CONSOLE_ATARI_JAGUAR; 3206 } 3207 break; 3208 3209 case 'k': 3210 if (rc_path_compare_extension(ext, "k7")) 3211 { 3212 iterator->consoles[0] = RC_CONSOLE_THOMSONTO8; /* tape */ 3213 } 3214 break; 3215 3216 case 'l': 3217 if (rc_path_compare_extension(ext, "lnx")) 3218 { 3219 iterator->consoles[0] = RC_CONSOLE_ATARI_LYNX; 3220 } 3221 break; 3222 3223 case 'm': 3224 if (rc_path_compare_extension(ext, "m3u")) 3225 { 3226 const char* disc_path = rc_hash_get_first_item_from_playlist(path); 3227 if (!disc_path) /* did not find a disc */ 3228 return; 3229 3230 iterator->buffer = NULL; /* ignore buffer; assume it's the m3u contents */ 3231 3232 path = iterator->path = disc_path; 3233 continue; /* retry with disc_path */ 3234 } 3235 else if (rc_path_compare_extension(ext, "md")) 3236 { 3237 iterator->consoles[0] = RC_CONSOLE_MEGA_DRIVE; 3238 } 3239 else if (rc_path_compare_extension(ext, "min")) 3240 { 3241 iterator->consoles[0] = RC_CONSOLE_POKEMON_MINI; 3242 } 3243 else if (rc_path_compare_extension(ext, "mx1")) 3244 { 3245 iterator->consoles[0] = RC_CONSOLE_MSX; 3246 } 3247 else if (rc_path_compare_extension(ext, "mx2")) 3248 { 3249 iterator->consoles[0] = RC_CONSOLE_MSX; 3250 } 3251 else if (rc_path_compare_extension(ext, "m5")) 3252 { 3253 iterator->consoles[0] = RC_CONSOLE_THOMSONTO8; /* cartridge */ 3254 } 3255 else if (rc_path_compare_extension(ext, "m7")) 3256 { 3257 iterator->consoles[0] = RC_CONSOLE_THOMSONTO8; /* cartridge */ 3258 } 3259 break; 3260 3261 case 'n': 3262 if (rc_path_compare_extension(ext, "nes")) 3263 { 3264 iterator->consoles[0] = RC_CONSOLE_NINTENDO; 3265 } 3266 else if (rc_path_compare_extension(ext, "nds")) 3267 { 3268 iterator->consoles[0] = RC_CONSOLE_NINTENDO_DS; /* ASSERT: handles both DS and DSi */ 3269 } 3270 else if (rc_path_compare_extension(ext, "n64") || 3271 rc_path_compare_extension(ext, "ndd")) 3272 { 3273 iterator->consoles[0] = RC_CONSOLE_NINTENDO_64; 3274 } 3275 else if (rc_path_compare_extension(ext, "ngc")) 3276 { 3277 iterator->consoles[0] = RC_CONSOLE_NEOGEO_POCKET; 3278 } 3279 else if (rc_path_compare_extension(ext, "nib")) 3280 { 3281 /* also Apple II, but both are full-file hashes */ 3282 iterator->consoles[0] = RC_CONSOLE_COMMODORE_64; 3283 } 3284 break; 3285 3286 case 'p': 3287 if (rc_path_compare_extension(ext, "pce")) 3288 { 3289 iterator->consoles[0] = RC_CONSOLE_PC_ENGINE; 3290 } 3291 else if (rc_path_compare_extension(ext, "pbp")) 3292 { 3293 iterator->consoles[0] = RC_CONSOLE_PSP; 3294 } 3295 else if (rc_path_compare_extension(ext, "pgm")) 3296 { 3297 iterator->consoles[0] = RC_CONSOLE_ELEKTOR_TV_GAMES_COMPUTER; 3298 } 3299 break; 3300 3301 case 'r': 3302 if (rc_path_compare_extension(ext, "rom")) 3303 { 3304 /* rom is associated with MSX, Thomson TO-8, and Fairchild Channel F. 3305 * Since they all use the same hashing algorithm, only specify one of them */ 3306 iterator->consoles[0] = RC_CONSOLE_MSX; 3307 } 3308 if (rc_path_compare_extension(ext, "ri")) 3309 { 3310 iterator->consoles[0] = RC_CONSOLE_MSX; 3311 } 3312 break; 3313 3314 case 's': 3315 if (rc_path_compare_extension(ext, "smc") || 3316 rc_path_compare_extension(ext, "sfc") || 3317 rc_path_compare_extension(ext, "swc")) 3318 { 3319 iterator->consoles[0] = RC_CONSOLE_SUPER_NINTENDO; 3320 } 3321 else if (rc_path_compare_extension(ext, "sg")) 3322 { 3323 iterator->consoles[0] = RC_CONSOLE_SG1000; 3324 } 3325 else if (rc_path_compare_extension(ext, "sgx")) 3326 { 3327 iterator->consoles[0] = RC_CONSOLE_PC_ENGINE; 3328 } 3329 else if (rc_path_compare_extension(ext, "sv")) 3330 { 3331 iterator->consoles[0] = RC_CONSOLE_SUPERVISION; 3332 } 3333 else if (rc_path_compare_extension(ext, "sap")) 3334 { 3335 iterator->consoles[0] = RC_CONSOLE_THOMSONTO8; /* disk */ 3336 } 3337 break; 3338 3339 case 't': 3340 if (rc_path_compare_extension(ext, "tap")) 3341 { 3342 iterator->consoles[0] = RC_CONSOLE_ORIC; 3343 } 3344 else if (rc_path_compare_extension(ext, "tic")) 3345 { 3346 iterator->consoles[0] = RC_CONSOLE_TIC80; 3347 } 3348 else if (rc_path_compare_extension(ext, "tvc")) 3349 { 3350 iterator->consoles[0] = RC_CONSOLE_ELEKTOR_TV_GAMES_COMPUTER; 3351 } 3352 break; 3353 3354 case 'u': 3355 if (rc_path_compare_extension(ext, "uze")) 3356 { 3357 iterator->consoles[0] = RC_CONSOLE_UZEBOX; 3358 } 3359 break; 3360 3361 case 'v': 3362 if (rc_path_compare_extension(ext, "vb")) 3363 { 3364 iterator->consoles[0] = RC_CONSOLE_VIRTUAL_BOY; 3365 } 3366 else if (rc_path_compare_extension(ext, "v64")) 3367 { 3368 iterator->consoles[0] = RC_CONSOLE_NINTENDO_64; 3369 } 3370 break; 3371 3372 case 'w': 3373 if (rc_path_compare_extension(ext, "wsc")) 3374 { 3375 iterator->consoles[0] = RC_CONSOLE_WONDERSWAN; 3376 } 3377 else if (rc_path_compare_extension(ext, "wasm")) 3378 { 3379 iterator->consoles[0] = RC_CONSOLE_WASM4; 3380 } 3381 else if (rc_path_compare_extension(ext, "woz")) 3382 { 3383 iterator->consoles[0] = RC_CONSOLE_APPLE_II; 3384 } 3385 break; 3386 3387 case 'z': 3388 if (rc_path_compare_extension(ext, "zip")) 3389 { 3390 /* decompressing zip file not supported */ 3391 iterator->consoles[0] = RC_CONSOLE_ARCADE; 3392 need_path = 1; 3393 } 3394 else if (rc_path_compare_extension(ext, "z64")) 3395 { 3396 iterator->consoles[0] = RC_CONSOLE_NINTENDO_64; 3397 } 3398 break; 3399 } 3400 3401 if (verbose_message_callback) 3402 { 3403 char message[256]; 3404 int count = 0; 3405 while (iterator->consoles[count]) 3406 ++count; 3407 3408 snprintf(message, sizeof(message), "Found %d potential consoles for %s file extension", count, ext); 3409 verbose_message_callback(message); 3410 } 3411 3412 /* loop is only for specific cases that redirect to another file - like m3u */ 3413 break; 3414 } while (1); 3415 3416 if (need_path && !iterator->path) 3417 iterator->path = strdup(path); 3418 3419 /* if we didn't match the extension, default to something that does a whole file hash */ 3420 if (!iterator->consoles[0]) 3421 iterator->consoles[0] = RC_CONSOLE_GAMEBOY; 3422 } 3423 3424 void rc_hash_destroy_iterator(struct rc_hash_iterator* iterator) 3425 { 3426 if (iterator->path) 3427 { 3428 free((void*)iterator->path); 3429 iterator->path = NULL; 3430 } 3431 } 3432 3433 int rc_hash_iterate(char hash[33], struct rc_hash_iterator* iterator) 3434 { 3435 int next_console; 3436 int result = 0; 3437 3438 do 3439 { 3440 next_console = iterator->consoles[iterator->index]; 3441 if (next_console == 0) 3442 { 3443 hash[0] = '\0'; 3444 break; 3445 } 3446 3447 ++iterator->index; 3448 3449 if (verbose_message_callback) 3450 { 3451 char message[128]; 3452 snprintf(message, sizeof(message), "Trying console %d", next_console); 3453 verbose_message_callback(message); 3454 } 3455 3456 if (iterator->buffer) 3457 result = rc_hash_generate_from_buffer(hash, next_console, iterator->buffer, iterator->buffer_size); 3458 else 3459 result = rc_hash_generate_from_file(hash, next_console, iterator->path); 3460 3461 } while (!result); 3462 3463 return result; 3464 }