duckstation

duckstation, but archived from the revision just before upstream changed it to a proprietary software project, this version is the libre one
git clone https://git.neptards.moe/u3shit/duckstation.git
Log | Files | Refs | README | LICENSE

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 }