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

rc_libretro.c (27984B)


      1 /* This file provides a series of functions for integrating RetroAchievements with libretro.
      2  * These functions will be called by a libretro frontend to validate certain expected behaviors
      3  * and simplify mapping core data to the RAIntegration DLL.
      4  * 
      5  * Originally designed to be shared between RALibretro and RetroArch, but will simplify
      6  * integrating with any other frontends.
      7  */
      8 
      9 #include "rc_libretro.h"
     10 
     11 #include "rc_consoles.h"
     12 #include "rc_compat.h"
     13 
     14 #include <ctype.h>
     15 #include <string.h>
     16 
     17 /* internal helper functions in hash.c */
     18 extern void* rc_file_open(const char* path);
     19 extern void rc_file_seek(void* file_handle, int64_t offset, int origin);
     20 extern int64_t rc_file_tell(void* file_handle);
     21 extern size_t rc_file_read(void* file_handle, void* buffer, int requested_bytes);
     22 extern void rc_file_close(void* file_handle);
     23 extern int rc_path_compare_extension(const char* path, const char* ext);
     24 extern int rc_hash_error(const char* message);
     25 
     26 
     27 static rc_libretro_message_callback rc_libretro_verbose_message_callback = NULL;
     28 
     29 /* a value that starts with a comma is a CSV.
     30  * if it starts with an exclamation point, it's everything but the provided value.
     31  * if it starts with an exclamntion point followed by a comma, it's everything but the CSV values.
     32  * values are case-insensitive */
     33 typedef struct rc_disallowed_core_settings_t
     34 {
     35   const char* library_name;
     36   const rc_disallowed_setting_t* disallowed_settings;
     37 } rc_disallowed_core_settings_t;
     38 
     39 static const rc_disallowed_setting_t _rc_disallowed_bsnes_settings[] = {
     40   { "bsnes_region", "pal" },
     41   { NULL, NULL }
     42 };
     43 
     44 static const rc_disallowed_setting_t _rc_disallowed_cap32_settings[] = {
     45   { "cap32_autorun", "disabled" },
     46   { NULL, NULL }
     47 };
     48 
     49 static const rc_disallowed_setting_t _rc_disallowed_dolphin_settings[] = {
     50   { "dolphin_cheats_enabled", "enabled" },
     51   { NULL, NULL }
     52 };
     53 
     54 static const rc_disallowed_setting_t _rc_disallowed_dosbox_pure_settings[] = {
     55   { "dosbox_pure_strict_mode", "false" },
     56   { NULL, NULL }
     57 };
     58 
     59 static const rc_disallowed_setting_t _rc_disallowed_duckstation_settings[] = {
     60   { "duckstation_CDROM.LoadImagePatches", "true" },
     61   { NULL, NULL }
     62 };
     63 
     64 static const rc_disallowed_setting_t _rc_disallowed_ecwolf_settings[] = {
     65   { "ecwolf-invulnerability", "enabled" },
     66   { NULL, NULL }
     67 };
     68 
     69 static const rc_disallowed_setting_t _rc_disallowed_fbneo_settings[] = {
     70   { "fbneo-allow-patched-romsets", "enabled" },
     71   { "fbneo-cheat-*", "!,Disabled,0 - Disabled" },
     72   { "fbneo-cpu-speed-adjust", "??%" }, /* disallow speeds under 100% */
     73   { "fbneo-dipswitch-*", "Universe BIOS*" },
     74   { "fbneo-neogeo-mode", "UNIBIOS" },
     75   { NULL, NULL }
     76 };
     77 
     78 static const rc_disallowed_setting_t _rc_disallowed_fceumm_settings[] = {
     79   { "fceumm_region", ",PAL,Dendy" },
     80   { NULL, NULL }
     81 };
     82 
     83 static const rc_disallowed_setting_t _rc_disallowed_gpgx_settings[] = {
     84   { "genesis_plus_gx_lock_on", ",action replay (pro),game genie" },
     85   { "genesis_plus_gx_region_detect", "pal" },
     86   { NULL, NULL }
     87 };
     88 
     89 static const rc_disallowed_setting_t _rc_disallowed_gpgx_wide_settings[] = {
     90   { "genesis_plus_gx_wide_lock_on", ",action replay (pro),game genie" },
     91   { "genesis_plus_gx_wide_region_detect", "pal" },
     92   { NULL, NULL }
     93 };
     94 
     95 static const rc_disallowed_setting_t _rc_disallowed_mesen_settings[] = {
     96   { "mesen_region", ",PAL,Dendy" },
     97   { NULL, NULL }
     98 };
     99 
    100 static const rc_disallowed_setting_t _rc_disallowed_mesen_s_settings[] = {
    101   { "mesen-s_region", "PAL" },
    102   { NULL, NULL }
    103 };
    104 
    105 static const rc_disallowed_setting_t _rc_disallowed_neocd_settings[] = {
    106   { "neocd_bios", "uni-bios*" },
    107   { NULL, NULL }
    108 };
    109 
    110 static const rc_disallowed_setting_t _rc_disallowed_pcsx_rearmed_settings[] = {
    111   { "pcsx_rearmed_region", "pal" },
    112   { NULL, NULL }
    113 };
    114 
    115 static const rc_disallowed_setting_t _rc_disallowed_picodrive_settings[] = {
    116   { "picodrive_region", ",Europe,Japan PAL" },
    117   { NULL, NULL }
    118 };
    119 
    120 static const rc_disallowed_setting_t _rc_disallowed_ppsspp_settings[] = {
    121   { "ppsspp_cheats", "enabled" },
    122   { NULL, NULL }
    123 };
    124 
    125 static const rc_disallowed_setting_t _rc_disallowed_quasi88_settings[] = {
    126   { "q88_cpu_clock", ",1,2" },
    127   { NULL, NULL }
    128 };
    129 
    130 static const rc_disallowed_setting_t _rc_disallowed_smsplus_settings[] = {
    131   { "smsplus_region", "pal" },
    132   { NULL, NULL }
    133 };
    134 
    135 static const rc_disallowed_setting_t _rc_disallowed_snes9x_settings[] = {
    136   { "snes9x_gfx_clip", "disabled" },
    137   { "snes9x_gfx_transp", "disabled" },
    138   { "snes9x_layer_*", "disabled" },
    139   { "snes9x_region", "pal" },
    140   { NULL, NULL }
    141 };
    142 
    143 static const rc_disallowed_setting_t _rc_disallowed_vice_settings[] = {
    144   { "vice_autostart", "disabled" }, /* autostart dictates initial load and reset from menu */
    145   { "vice_reset", "!autostart" }, /* reset dictates behavior when pressing reset button (END) */
    146   { NULL, NULL }
    147 };
    148 
    149 static const rc_disallowed_setting_t _rc_disallowed_virtual_jaguar_settings[] = {
    150   { "virtualjaguar_pal", "enabled" },
    151   { NULL, NULL }
    152 };
    153 
    154 static const rc_disallowed_core_settings_t rc_disallowed_core_settings[] = {
    155   { "bsnes-mercury", _rc_disallowed_bsnes_settings },
    156   { "cap32", _rc_disallowed_cap32_settings },
    157   { "dolphin-emu", _rc_disallowed_dolphin_settings },
    158   { "DOSBox-pure", _rc_disallowed_dosbox_pure_settings },
    159   { "DuckStation", _rc_disallowed_duckstation_settings },
    160   { "ecwolf", _rc_disallowed_ecwolf_settings },
    161   { "FCEUmm", _rc_disallowed_fceumm_settings },
    162   { "FinalBurn Neo", _rc_disallowed_fbneo_settings },
    163   { "Genesis Plus GX", _rc_disallowed_gpgx_settings },
    164   { "Genesis Plus GX Wide", _rc_disallowed_gpgx_wide_settings },
    165   { "Mesen", _rc_disallowed_mesen_settings },
    166   { "Mesen-S", _rc_disallowed_mesen_s_settings },
    167   { "NeoCD", _rc_disallowed_neocd_settings },
    168   { "PPSSPP", _rc_disallowed_ppsspp_settings },
    169   { "PCSX-ReARMed", _rc_disallowed_pcsx_rearmed_settings },
    170   { "PicoDrive", _rc_disallowed_picodrive_settings },
    171   { "QUASI88", _rc_disallowed_quasi88_settings },
    172   { "SMS Plus GX", _rc_disallowed_smsplus_settings },
    173   { "Snes9x", _rc_disallowed_snes9x_settings },
    174   { "VICE x64", _rc_disallowed_vice_settings },
    175   { "Virtual Jaguar", _rc_disallowed_virtual_jaguar_settings },
    176   { NULL, NULL }
    177 };
    178 
    179 static int rc_libretro_string_equal_nocase_wildcard(const char* test, const char* value) {
    180   char c1, c2;
    181   while ((c1 = *test++)) {
    182     if (tolower(c1) != tolower(c2 = *value++) && c2 != '?')
    183       return (c2 == '*');
    184   }
    185 
    186   return (*value == '\0');
    187 }
    188 
    189 static int rc_libretro_match_value(const char* val, const char* match) {
    190   /* if value starts with a comma, it's a CSV list of potential matches */
    191   if (*match == ',') {
    192     do {
    193       const char* ptr = ++match;
    194       size_t size;
    195 
    196       while (*match && *match != ',')
    197         ++match;
    198 
    199       size = match - ptr;
    200       if (val[size] == '\0') {
    201         if (memcmp(ptr, val, size) == 0) {
    202           return 1;
    203         }
    204         else {
    205           char buffer[128];
    206           memcpy(buffer, ptr, size);
    207           buffer[size] = '\0';
    208           if (rc_libretro_string_equal_nocase_wildcard(buffer, val))
    209             return 1;
    210         }
    211       }
    212     } while (*match == ',');
    213 
    214     return 0;
    215   }
    216 
    217   /* a leading exclamation point means the provided value(s) are not forbidden (are allowed) */
    218   if (*match == '!')
    219     return !rc_libretro_match_value(val, &match[1]);
    220 
    221   /* just a single value, attempt to match it */
    222   return rc_libretro_string_equal_nocase_wildcard(val, match);
    223 }
    224 
    225 int rc_libretro_is_setting_allowed(const rc_disallowed_setting_t* disallowed_settings, const char* setting, const char* value) {
    226   const char* key;
    227   size_t key_len;
    228 
    229   for (; disallowed_settings->setting; ++disallowed_settings) {
    230     key = disallowed_settings->setting;
    231     key_len = strlen(key);
    232 
    233     if (key[key_len - 1] == '*') {
    234       if (memcmp(setting, key, key_len - 1) == 0) {
    235         if (rc_libretro_match_value(value, disallowed_settings->value))
    236           return 0;
    237       }
    238     }
    239     else {
    240       if (memcmp(setting, key, key_len + 1) == 0) {
    241         if (rc_libretro_match_value(value, disallowed_settings->value))
    242           return 0;
    243       }
    244     }
    245   }
    246 
    247   return 1;
    248 }
    249 
    250 const rc_disallowed_setting_t* rc_libretro_get_disallowed_settings(const char* library_name) {
    251   const rc_disallowed_core_settings_t* core_filter = rc_disallowed_core_settings;
    252   size_t library_name_length;
    253 
    254   if (!library_name || !library_name[0])
    255     return NULL;
    256 
    257   library_name_length = strlen(library_name) + 1;
    258   while (core_filter->library_name) {
    259     if (memcmp(core_filter->library_name, library_name, library_name_length) == 0)
    260       return core_filter->disallowed_settings;
    261 
    262     ++core_filter;
    263   }
    264 
    265   return NULL;
    266 }
    267 
    268 typedef struct rc_disallowed_core_systems_t
    269 {
    270     const char* library_name;
    271     const uint32_t disallowed_consoles[4];
    272 } rc_disallowed_core_systems_t;
    273 
    274 static const rc_disallowed_core_systems_t rc_disallowed_core_systems[] = {
    275   /* https://github.com/libretro/Mesen-S/issues/8 */
    276   { "Mesen-S", { RC_CONSOLE_GAMEBOY, RC_CONSOLE_GAMEBOY_COLOR, 0 }},
    277   { NULL, { 0 } }
    278 };
    279 
    280 int rc_libretro_is_system_allowed(const char* library_name, uint32_t console_id) {
    281   const rc_disallowed_core_systems_t* core_filter = rc_disallowed_core_systems;
    282   size_t library_name_length;
    283   size_t i;
    284 
    285   if (!library_name || !library_name[0])
    286     return 1;
    287 
    288   library_name_length = strlen(library_name) + 1;
    289   while (core_filter->library_name) {
    290     if (memcmp(core_filter->library_name, library_name, library_name_length) == 0) {
    291       for (i = 0; i < sizeof(core_filter->disallowed_consoles) / sizeof(core_filter->disallowed_consoles[0]); ++i) {
    292         if (core_filter->disallowed_consoles[i] == console_id)
    293           return 0;
    294       }
    295       break;
    296     }
    297 
    298     ++core_filter;
    299   }
    300 
    301   return 1;
    302 }
    303 
    304 uint8_t* rc_libretro_memory_find_avail(const rc_libretro_memory_regions_t* regions, uint32_t address, uint32_t* avail) {
    305   uint32_t i;
    306 
    307   for (i = 0; i < regions->count; ++i) {
    308     const size_t size = regions->size[i];
    309     if (address < size) {
    310       if (regions->data[i] == NULL)
    311         break;
    312 
    313       if (avail)
    314         *avail = (uint32_t)(size - address);
    315 
    316       return &regions->data[i][address];
    317     }
    318 
    319     address -= (uint32_t)size;
    320   }
    321 
    322   if (avail)
    323     *avail = 0;
    324 
    325   return NULL;
    326 }
    327 
    328 uint8_t* rc_libretro_memory_find(const rc_libretro_memory_regions_t* regions, uint32_t address) {
    329   return rc_libretro_memory_find_avail(regions, address, NULL);
    330 }
    331 
    332 uint32_t rc_libretro_memory_read(const rc_libretro_memory_regions_t* regions, uint32_t address,
    333       uint8_t* buffer, uint32_t num_bytes) {
    334   uint32_t bytes_read = 0;
    335   uint32_t avail;
    336   uint32_t i;
    337 
    338   for (i = 0; i < regions->count; ++i) {
    339     const size_t size = regions->size[i];
    340     if (address >= size) {
    341       /* address is not in this block, adjust and look at next block */
    342       address -= (unsigned)size;
    343       continue;
    344     }
    345 
    346     if (regions->data[i] == NULL) /* no memory associated to this block. abort */
    347       break;
    348 
    349     avail = (unsigned)(size - address);
    350     if (avail >= num_bytes) {
    351       /* requested memory is fully within this block, copy and return it */
    352       memcpy(buffer, &regions->data[i][address], num_bytes);
    353       bytes_read += num_bytes;
    354       return bytes_read;
    355     }
    356 
    357     /* copy whatever is available in this block, and adjust for the next block */
    358     memcpy(buffer, &regions->data[i][address], avail);
    359     buffer += avail;
    360     bytes_read += avail;
    361     num_bytes -= avail;
    362     address = 0;
    363   }
    364 
    365   return bytes_read;
    366 }
    367 
    368 void rc_libretro_init_verbose_message_callback(rc_libretro_message_callback callback) {
    369   rc_libretro_verbose_message_callback = callback;
    370 }
    371 
    372 static void rc_libretro_verbose(const char* message) {
    373   if (rc_libretro_verbose_message_callback)
    374     rc_libretro_verbose_message_callback(message);
    375 }
    376 
    377 static const char* rc_memory_type_str(int type) {
    378   switch (type)
    379   {
    380     case RC_MEMORY_TYPE_SAVE_RAM:
    381       return "SRAM";
    382     case RC_MEMORY_TYPE_VIDEO_RAM:
    383       return "VRAM";
    384     case RC_MEMORY_TYPE_UNUSED:
    385       return "UNUSED";
    386     default:
    387       break;
    388   }
    389 
    390   return "SYSTEM RAM";
    391 }
    392 
    393 static void rc_libretro_memory_register_region(rc_libretro_memory_regions_t* regions, int type,
    394                                                uint8_t* data, size_t size, const char* description) {
    395   if (size == 0)
    396     return;
    397 
    398   if (regions->count == (sizeof(regions->size) / sizeof(regions->size[0]))) {
    399     rc_libretro_verbose("Too many memory memory regions to register");
    400     return;
    401   }
    402 
    403   if (!data && regions->count > 0 && !regions->data[regions->count - 1]) {
    404     /* extend null region */
    405     regions->size[regions->count - 1] += size;
    406   }
    407   else if (data && regions->count > 0 &&
    408            data == (regions->data[regions->count - 1] + regions->size[regions->count - 1])) {
    409     /* extend non-null region */
    410     regions->size[regions->count - 1] += size;
    411   }
    412   else {
    413     /* create new region */
    414     regions->data[regions->count] = data;
    415     regions->size[regions->count] = size;
    416     ++regions->count;
    417   }
    418 
    419   regions->total_size += size;
    420 
    421   if (rc_libretro_verbose_message_callback) {
    422     char message[128];
    423     snprintf(message, sizeof(message), "Registered 0x%04X bytes of %s at $%06X (%s)", (unsigned)size,
    424              rc_memory_type_str(type), (unsigned)(regions->total_size - size), description);
    425     rc_libretro_verbose_message_callback(message);
    426   }
    427 }
    428 
    429 static void rc_libretro_memory_init_without_regions(rc_libretro_memory_regions_t* regions,
    430                                                     rc_libretro_get_core_memory_info_func get_core_memory_info) {
    431   /* no regions specified, assume system RAM followed by save RAM */
    432   char description[64];
    433   rc_libretro_core_memory_info_t info;
    434 
    435   snprintf(description, sizeof(description), "offset 0x%06x", 0);
    436 
    437   get_core_memory_info(RETRO_MEMORY_SYSTEM_RAM, &info);
    438   if (info.size)
    439     rc_libretro_memory_register_region(regions, RC_MEMORY_TYPE_SYSTEM_RAM, info.data, info.size, description);
    440 
    441   get_core_memory_info(RETRO_MEMORY_SAVE_RAM, &info);
    442   if (info.size)
    443     rc_libretro_memory_register_region(regions, RC_MEMORY_TYPE_SAVE_RAM, info.data, info.size, description);
    444 }
    445 
    446 static const struct retro_memory_descriptor* rc_libretro_memory_get_descriptor(const struct retro_memory_map* mmap, uint32_t real_address, size_t* offset)
    447 {
    448   const struct retro_memory_descriptor* desc = mmap->descriptors;
    449   const struct retro_memory_descriptor* end = desc + mmap->num_descriptors;
    450 
    451   for (; desc < end; desc++) {
    452     if (desc->select == 0) {
    453       /* if select is 0, attempt to explcitly match the address */
    454       if (real_address >= desc->start && real_address < desc->start + desc->len) {
    455         *offset = real_address - desc->start;
    456         return desc;
    457       }
    458     }
    459     else {
    460       /* otherwise, attempt to match the address by matching the select bits */
    461       /* address is in the block if (addr & select) == (start & select) */
    462       if (((desc->start ^ real_address) & desc->select) == 0) {
    463         /* get the relative offset of the address from the start of the memory block */
    464         uint32_t reduced_address = real_address - (unsigned)desc->start;
    465 
    466         /* remove any bits from the reduced_address that correspond to the bits in the disconnect
    467          * mask and collapse the remaining bits. this code was copied from the mmap_reduce function
    468          * in RetroArch. i'm not exactly sure how it works, but it does. */
    469         uint32_t disconnect_mask = (unsigned)desc->disconnect;
    470         while (disconnect_mask) {
    471           const uint32_t tmp = (disconnect_mask - 1) & ~disconnect_mask;
    472           reduced_address = (reduced_address & tmp) | ((reduced_address >> 1) & ~tmp);
    473           disconnect_mask = (disconnect_mask & (disconnect_mask - 1)) >> 1;
    474         }
    475 
    476         /* calculate the offset within the descriptor */
    477         *offset = reduced_address;
    478 
    479         /* sanity check - make sure the descriptor is large enough to hold the target address */
    480         if (reduced_address < desc->len)
    481           return desc;
    482       }
    483     }
    484   }
    485 
    486   *offset = 0;
    487   return NULL;
    488 }
    489 
    490 static void rc_libretro_memory_init_from_memory_map(rc_libretro_memory_regions_t* regions, const struct retro_memory_map* mmap,
    491                                                     const rc_memory_regions_t* console_regions) {
    492   char description[64];
    493   uint32_t i;
    494   uint8_t* region_start;
    495   uint8_t* desc_start;
    496   size_t desc_size;
    497   size_t offset;
    498 
    499   for (i = 0; i < console_regions->num_regions; ++i) {
    500     const rc_memory_region_t* console_region = &console_regions->region[i];
    501     size_t console_region_size = console_region->end_address - console_region->start_address + 1;
    502     uint32_t real_address = console_region->real_address;
    503     uint32_t disconnect_size = 0;
    504 
    505     while (console_region_size > 0) {
    506       const struct retro_memory_descriptor* desc = rc_libretro_memory_get_descriptor(mmap, real_address, &offset);
    507       if (!desc) {
    508         if (rc_libretro_verbose_message_callback && console_region->type != RC_MEMORY_TYPE_UNUSED) {
    509           snprintf(description, sizeof(description), "Could not map region starting at $%06X",
    510                    (unsigned)(real_address - console_region->real_address + console_region->start_address));
    511           rc_libretro_verbose(description);
    512         }
    513 
    514         if (disconnect_size && console_region_size > disconnect_size) {
    515           rc_libretro_memory_register_region(regions, console_region->type, NULL, disconnect_size, "null filler");
    516           console_region_size -= disconnect_size;
    517           real_address += disconnect_size;
    518           disconnect_size = 0;
    519           continue;
    520         }
    521 
    522         rc_libretro_memory_register_region(regions, console_region->type, NULL, console_region_size, "null filler");
    523         break;
    524       }
    525 
    526       snprintf(description, sizeof(description), "descriptor %u, offset 0x%06X%s",
    527                (unsigned)(desc - mmap->descriptors) + 1, (int)offset, desc->ptr ? "" : " [no pointer]");
    528 
    529       if (desc->ptr) {
    530         desc_start = (uint8_t*)desc->ptr + desc->offset;
    531         region_start = desc_start + offset;
    532       }
    533       else {
    534         region_start = NULL;
    535       }
    536 
    537       desc_size = desc->len - offset;
    538       if (desc->disconnect && desc_size > desc->disconnect) {
    539         /* if we need to extract a disconnect bit, the largest block we can read is up to
    540          * the next time that bit flips */
    541         /* https://stackoverflow.com/questions/12247186/find-the-lowest-set-bit */
    542         disconnect_size = (desc->disconnect & -((int)desc->disconnect));
    543         desc_size = disconnect_size - (real_address & (disconnect_size - 1));
    544       }
    545 
    546       if (console_region_size > desc_size) {
    547         if (desc_size == 0) {
    548           if (rc_libretro_verbose_message_callback && console_region->type != RC_MEMORY_TYPE_UNUSED) {
    549             snprintf(description, sizeof(description), "Could not map region starting at $%06X",
    550                      (unsigned)(real_address - console_region->real_address + console_region->start_address));
    551             rc_libretro_verbose(description);
    552           }
    553 
    554           rc_libretro_memory_register_region(regions, console_region->type, NULL, console_region_size, "null filler");
    555           console_region_size = 0;
    556         }
    557         else {
    558           rc_libretro_memory_register_region(regions, console_region->type, region_start, desc_size, description);
    559           console_region_size -= desc_size;
    560           real_address += (unsigned)desc_size;
    561         }
    562       }
    563       else {
    564         rc_libretro_memory_register_region(regions, console_region->type, region_start, console_region_size, description);
    565         console_region_size = 0;
    566       }
    567     }
    568   }
    569 }
    570 
    571 static uint32_t rc_libretro_memory_console_region_to_ram_type(uint8_t region_type) {
    572   switch (region_type)
    573   {
    574     case RC_MEMORY_TYPE_SAVE_RAM:
    575       return RETRO_MEMORY_SAVE_RAM;
    576     case RC_MEMORY_TYPE_VIDEO_RAM:
    577       return RETRO_MEMORY_VIDEO_RAM;
    578     default:
    579       break;
    580   }
    581 
    582   return RETRO_MEMORY_SYSTEM_RAM;
    583 }
    584 
    585 static void rc_libretro_memory_init_from_unmapped_memory(rc_libretro_memory_regions_t* regions,
    586     rc_libretro_get_core_memory_info_func get_core_memory_info, const rc_memory_regions_t* console_regions) {
    587   char description[64];
    588   uint32_t i, j;
    589   rc_libretro_core_memory_info_t info;
    590   size_t offset;
    591 
    592   for (i = 0; i < console_regions->num_regions; ++i) {
    593     const rc_memory_region_t* console_region = &console_regions->region[i];
    594     const size_t console_region_size = console_region->end_address - console_region->start_address + 1;
    595     const uint32_t type = rc_libretro_memory_console_region_to_ram_type(console_region->type);
    596     uint32_t base_address = 0;
    597 
    598     for (j = 0; j <= i; ++j) {
    599       const rc_memory_region_t* console_region2 = &console_regions->region[j];
    600       if (rc_libretro_memory_console_region_to_ram_type(console_region2->type) == type) {
    601         base_address = console_region2->start_address;
    602         break;
    603       }
    604     }
    605     offset = console_region->start_address - base_address;
    606 
    607     get_core_memory_info(type, &info);
    608 
    609     if (offset < info.size) {
    610       info.size -= offset;
    611 
    612       if (info.data) {
    613         snprintf(description, sizeof(description), "offset 0x%06X", (int)offset);
    614         info.data += offset;
    615       }
    616       else {
    617         snprintf(description, sizeof(description), "null filler");
    618       }
    619     }
    620     else {
    621       if (rc_libretro_verbose_message_callback && console_region->type != RC_MEMORY_TYPE_UNUSED) {
    622         snprintf(description, sizeof(description), "Could not map region starting at $%06X", (unsigned)console_region->start_address);
    623         rc_libretro_verbose(description);
    624       }
    625 
    626       info.data = NULL;
    627       info.size = 0;
    628     }
    629 
    630     if (console_region_size > info.size) {
    631       /* want more than what is available, take what we can and null fill the rest */
    632       rc_libretro_memory_register_region(regions, console_region->type, info.data, info.size, description);
    633       rc_libretro_memory_register_region(regions, console_region->type, NULL, console_region_size - info.size, "null filler");
    634     }
    635     else {
    636       /* only take as much as we need */
    637       rc_libretro_memory_register_region(regions, console_region->type, info.data, console_region_size, description);
    638     }
    639   }
    640 }
    641 
    642 int rc_libretro_memory_init(rc_libretro_memory_regions_t* regions, const struct retro_memory_map* mmap,
    643                             rc_libretro_get_core_memory_info_func get_core_memory_info, uint32_t console_id) {
    644   const rc_memory_regions_t* console_regions = rc_console_memory_regions(console_id);
    645   rc_libretro_memory_regions_t new_regions;
    646   int has_valid_region = 0;
    647   uint32_t i;
    648 
    649   if (!regions)
    650     return 0;
    651 
    652   memset(&new_regions, 0, sizeof(new_regions));
    653 
    654   if (console_regions == NULL || console_regions->num_regions == 0)
    655     rc_libretro_memory_init_without_regions(&new_regions, get_core_memory_info);
    656   else if (mmap && mmap->num_descriptors != 0)
    657     rc_libretro_memory_init_from_memory_map(&new_regions, mmap, console_regions);
    658   else
    659     rc_libretro_memory_init_from_unmapped_memory(&new_regions, get_core_memory_info, console_regions);
    660 
    661   /* determine if any valid regions were found */
    662   for (i = 0; i < new_regions.count; i++) {
    663     if (new_regions.data[i]) {
    664       has_valid_region = 1;
    665       break;
    666     }
    667   }
    668 
    669   memcpy(regions, &new_regions, sizeof(*regions));
    670   return has_valid_region;
    671 }
    672 
    673 void rc_libretro_memory_destroy(rc_libretro_memory_regions_t* regions) {
    674   memset(regions, 0, sizeof(*regions));
    675 }
    676 
    677 void rc_libretro_hash_set_init(struct rc_libretro_hash_set_t* hash_set,
    678                                const char* m3u_path, rc_libretro_get_image_path_func get_image_path) {
    679   char image_path[1024];
    680   char* m3u_contents;
    681   char* ptr;
    682   int64_t file_len;
    683   void* file_handle;
    684   int index = 0;
    685 
    686   memset(hash_set, 0, sizeof(*hash_set));
    687 
    688   if (!rc_path_compare_extension(m3u_path, "m3u"))
    689     return;
    690 
    691   file_handle = rc_file_open(m3u_path);
    692   if (!file_handle) {
    693     rc_hash_error("Could not open playlist");
    694     return;
    695   }
    696 
    697   rc_file_seek(file_handle, 0, SEEK_END);
    698   file_len = rc_file_tell(file_handle);
    699   rc_file_seek(file_handle, 0, SEEK_SET);
    700 
    701   m3u_contents = (char*)malloc((size_t)file_len + 1);
    702   if (m3u_contents) {
    703     rc_file_read(file_handle, m3u_contents, (int)file_len);
    704     m3u_contents[file_len] = '\0';
    705 
    706     rc_file_close(file_handle);
    707 
    708     ptr = m3u_contents;
    709     do
    710     {
    711       /* ignore whitespace */
    712       while (isspace((int)*ptr))
    713         ++ptr;
    714 
    715       if (*ptr == '#') {
    716         /* ignore comment unless it's the special SAVEDISK extension */
    717         if (memcmp(ptr, "#SAVEDISK:", 10) == 0) {
    718           /* get the path to the save disk from the frontend, assign it a bogus hash so
    719            * it doesn't get hashed later */
    720           if (get_image_path(index, image_path, sizeof(image_path))) {
    721             const char save_disk_hash[33] = "[SAVE DISK]";
    722             rc_libretro_hash_set_add(hash_set, image_path, -1, save_disk_hash);
    723             ++index;
    724           }
    725         }
    726       }
    727       else {
    728         /* non-empty line, tally a file */
    729         ++index;
    730       }
    731 
    732       /* find the end of the line */
    733       while (*ptr && *ptr != '\n')
    734         ++ptr;
    735 
    736     } while (*ptr);
    737 
    738     free(m3u_contents);
    739   }
    740 
    741   if (hash_set->entries_count > 0) {
    742     /* at least one save disk was found. make sure the core supports the #SAVEDISK: extension by
    743      * asking for the last expected disk. if it's not found, assume no #SAVEDISK: support */
    744     if (!get_image_path(index - 1, image_path, sizeof(image_path)))
    745       hash_set->entries_count = 0;
    746   }
    747 }
    748 
    749 void rc_libretro_hash_set_destroy(struct rc_libretro_hash_set_t* hash_set) {
    750   if (hash_set->entries)
    751     free(hash_set->entries);
    752   memset(hash_set, 0, sizeof(*hash_set));
    753 }
    754 
    755 static uint32_t rc_libretro_djb2(const char* input)
    756 {
    757   uint32_t result = 5381;
    758   char c;
    759 
    760   while ((c = *input++) != '\0')
    761     result = ((result << 5) + result) + c; /* result = result * 33 + c */
    762 
    763   return result;
    764 }
    765 
    766 void rc_libretro_hash_set_add(struct rc_libretro_hash_set_t* hash_set,
    767                               const char* path, uint32_t game_id, const char hash[33]) {
    768   const uint32_t path_djb2 = (path != NULL) ? rc_libretro_djb2(path) : 0;
    769   struct rc_libretro_hash_entry_t* entry = NULL;
    770   struct rc_libretro_hash_entry_t* scan;
    771   struct rc_libretro_hash_entry_t* stop = hash_set->entries + hash_set->entries_count;;
    772 
    773   if (path_djb2) {
    774     /* attempt to match the path */
    775     for (scan = hash_set->entries; scan < stop; ++scan) {
    776       if (scan->path_djb2 == path_djb2) {
    777         entry = scan;
    778         break;
    779       }
    780     }
    781   }
    782 
    783   if (!entry)
    784   {
    785     /* entry not found, allocate a new one */
    786     if (hash_set->entries_size == 0) {
    787       hash_set->entries_size = 4;
    788       hash_set->entries = (struct rc_libretro_hash_entry_t*)
    789           malloc(hash_set->entries_size * sizeof(struct rc_libretro_hash_entry_t));
    790     }
    791     else if (hash_set->entries_count == hash_set->entries_size) {
    792       hash_set->entries_size += 4;
    793       hash_set->entries = (struct rc_libretro_hash_entry_t*)realloc(hash_set->entries,
    794           hash_set->entries_size * sizeof(struct rc_libretro_hash_entry_t));
    795     }
    796 
    797     if (hash_set->entries == NULL) /* unexpected, but better than crashing */
    798       return;
    799 
    800     entry = hash_set->entries + hash_set->entries_count++;
    801   }
    802 
    803   /* update the entry */
    804   entry->path_djb2 = path_djb2;
    805   entry->game_id = game_id;
    806   memcpy(entry->hash, hash, sizeof(entry->hash));
    807 }
    808 
    809 const char* rc_libretro_hash_set_get_hash(const struct rc_libretro_hash_set_t* hash_set, const char* path)
    810 {
    811   const uint32_t path_djb2 = rc_libretro_djb2(path);
    812   struct rc_libretro_hash_entry_t* scan = hash_set->entries;
    813   struct rc_libretro_hash_entry_t* stop = scan + hash_set->entries_count;
    814   for (; scan < stop; ++scan) {
    815     if (scan->path_djb2 == path_djb2)
    816       return scan->hash;
    817   }
    818 
    819   return NULL;
    820 }
    821 
    822 int rc_libretro_hash_set_get_game_id(const struct rc_libretro_hash_set_t* hash_set, const char* hash)
    823 {
    824   struct rc_libretro_hash_entry_t* scan = hash_set->entries;
    825   struct rc_libretro_hash_entry_t* stop = scan + hash_set->entries_count;
    826   for (; scan < stop; ++scan) {
    827     if (memcmp(scan->hash, hash, sizeof(scan->hash)) == 0)
    828       return scan->game_id;
    829   }
    830 
    831   return 0;
    832 }