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 ®ions->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, ®ions->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, ®ions->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 }