diff --git a/cheevos/cheevos.c b/cheevos/cheevos.c index 66927c90cc..4ce43169be 100644 --- a/cheevos/cheevos.c +++ b/cheevos/cheevos.c @@ -650,7 +650,7 @@ bool rcheevos_unload(void) { CHEEVOS_FREE(rcheevos_locals.menuitems); rcheevos_locals.menuitems = NULL; - rcheevos_locals.menuitem_capacity = + rcheevos_locals.menuitem_capacity = rcheevos_locals.menuitem_count = 0; } #endif @@ -663,13 +663,8 @@ bool rcheevos_unload(void) rcheevos_locals.loaded = false; rcheevos_locals.hardcore_active = false; - } - while (rcheevos_locals.game.hashes) - { - rcheevos_hash_entry_t* hash_entry = rcheevos_locals.game.hashes; - rcheevos_locals.game.hashes = hash_entry->next; - free(hash_entry); + rc_libretro_hash_set_destroy(&rcheevos_locals.game.hashes); } #ifdef HAVE_THREADS @@ -1011,6 +1006,21 @@ void rcheevos_validate_config_settings(void) break; } } + + if (rcheevos_locals.game.console_id && + !rc_libretro_is_system_allowed(system->library_name, rcheevos_locals.game.console_id)) + { + char buffer[256]; + snprintf(buffer, sizeof(buffer), + "Hardcore paused. You cannot earn hardcore achievements for %s using %s", + rc_console_name(rcheevos_locals.game.console_id), system->library_name); + CHEEVOS_LOG(RCHEEVOS_TAG "%s\n", buffer); + rcheevos_pause_hardcore(); + + runloop_msg_queue_push(buffer, 0, 4 * 60, false, NULL, + MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_WARNING); + return; + } } static void rcheevos_runtime_event_handler( @@ -1645,6 +1655,10 @@ static void rcheevos_start_session(void) return; } + /* re-validate the config settings now that we know + * which console_id is active */ + rcheevos_validate_config_settings(); + task = task_init(); task->handler = rcheevos_start_session_async; task->callback = rcheevos_start_session_finish; @@ -1726,7 +1740,9 @@ static void rcheevos_fetch_game_data(void) struct rcheevos_identify_game_data { struct rc_hash_iterator iterator; + char* path; uint8_t* datacopy; + char hash[33]; }; static void rcheevos_identify_game_callback(void* userdata) @@ -1734,47 +1750,82 @@ static void rcheevos_identify_game_callback(void* userdata) struct rcheevos_identify_game_data* data = (struct rcheevos_identify_game_data*)userdata; - if (data) - { - if (rcheevos_locals.game.id == 0) - { - /* previous hash didn't match, try the next one */ - char hash[33]; - if (rc_hash_iterate(hash, &data->iterator)) - { - memcpy(rcheevos_locals.game.hashes->hash, hash, sizeof(hash)); - rcheevos_locals.load_info.hashes_tried++; + rcheevos_locals.load_info.hashes_tried++; - rcheevos_client_identify_game(hash, - rcheevos_identify_game_callback, data); - return; - } + if (rcheevos_locals.game.id == 0) + { + /* previous hash didn't match, try the next one */ + char new_hash[33]; + int found_new_hash; + while ((found_new_hash = rc_hash_iterate(new_hash, &data->iterator)) != 0) + { + if (!rc_libretro_hash_set_get_game_id(&rcheevos_locals.game.hashes, new_hash)) + break; + + CHEEVOS_LOG(RCHEEVOS_TAG "Ignoring [%s]. Already tried.\n", new_hash); } - /* no more hashes generated, free the iterator data */ - if (data->datacopy) - free(data->datacopy); - free(data); + if (found_new_hash) + { + memcpy(data->hash, new_hash, sizeof(data->hash)); + rcheevos_client_identify_game(data->hash, + rcheevos_identify_game_callback, data); + return; + } } - rcheevos_locals.game.hashes->game_id = rcheevos_locals.game.id; + rc_libretro_hash_set_add(&rcheevos_locals.game.hashes, + data->path, rcheevos_locals.game.id, data->hash); + rcheevos_locals.game.hash = + rc_libretro_hash_set_get_hash(&rcheevos_locals.game.hashes, data->path); + + if (data->iterator.path && strcmp(data->iterator.path, data->path) != 0) + { + rc_libretro_hash_set_add(&rcheevos_locals.game.hashes, + data->iterator.path, rcheevos_locals.game.id, data->hash); + rcheevos_locals.game.hash = + rc_libretro_hash_set_get_hash(&rcheevos_locals.game.hashes, data->iterator.path); + } + + /* no more hashes generated, free the iterator data */ + rc_hash_destroy_iterator(&data->iterator); + if (data->datacopy) + free(data->datacopy); + if (data->path) + free(data->path); + free(data); /* hash resolution complete, proceed to fetching game data */ if (rcheevos_end_load_state() == 0) rcheevos_fetch_game_data(); } +static int rcheevos_get_image_path(unsigned index, char* buffer, size_t buffer_size) +{ + rarch_system_info_t* system = &runloop_state_get_ptr()->system; + if (!system->disk_control.cb.get_image_path) + return 0; + + return system->disk_control.cb.get_image_path(index, buffer, buffer_size); +} + static bool rcheevos_identify_game(const struct retro_game_info* info) { struct rcheevos_identify_game_data* data; struct rc_hash_filereader filereader; - struct rc_hash_iterator iterator; size_t len; - char hash[33]; #ifndef DEBUG settings_t* settings = config_get_ptr(); #endif + data = (struct rcheevos_identify_game_data*) + calloc(1, sizeof(struct rcheevos_identify_game_data)); + if (!data) + { + CHEEVOS_LOG(RCHEEVOS_TAG "allocation failed\n"); + return false; + } + /* provide hooks for reading files */ memset(&filereader, 0, sizeof(filereader)); filereader.open = rc_hash_handle_file_open; @@ -1797,59 +1848,44 @@ static bool rcheevos_identify_game(const struct retro_game_info* info) rc_hash_reset_cdreader_hooks(); /* fetch the first hash */ - rc_hash_initialize_iterator(&iterator, + rc_hash_initialize_iterator(&data->iterator, info->path, (uint8_t*)info->data, info->size); - if (!rc_hash_iterate(hash, &iterator)) + if (!rc_hash_iterate(data->hash, &data->iterator)) { CHEEVOS_LOG(RCHEEVOS_TAG "no hashes generated\n"); + rc_hash_destroy_iterator(&data->iterator); + free(data); return false; } - rcheevos_locals.game.hashes = (rcheevos_hash_entry_t*)calloc(1, sizeof(rcheevos_hash_entry_t)); - rcheevos_locals.game.hashes->path_djb2 = djb2_calculate(info->path); - rcheevos_locals.game.hash = rcheevos_locals.game.hashes->hash; - memcpy(rcheevos_locals.game.hashes->hash, hash, sizeof(hash)); - rcheevos_locals.load_info.hashes_tried++; + rc_libretro_hash_set_init(&rcheevos_locals.game.hashes, info->path, rcheevos_get_image_path); + data->path = strdup(info->path); - if (iterator.consoles[iterator.index] == 0) + if (data->iterator.consoles[data->iterator.index] != 0) { - /* no more potential matches, just try the one hash */ - rcheevos_begin_load_state(RCHEEVOS_LOAD_STATE_IDENTIFYING_GAME); - rcheevos_client_identify_game(hash, - rcheevos_identify_game_callback, NULL); - return true; - } - - /* multiple potential matches, clone the data for the next attempt */ - data = (struct rcheevos_identify_game_data*) - calloc(1, sizeof(struct rcheevos_identify_game_data)); - if (!data) - { - CHEEVOS_LOG(RCHEEVOS_TAG "allocation failed\n"); - return false; - } - - if (info->data) - { - len = info->size; - if (len > CHEEVOS_MB(64)) - len = CHEEVOS_MB(64); - - data->datacopy = (uint8_t*)malloc(len); - if (!data->datacopy) + /* multiple potential matches, clone the data for the next attempt */ + if (info->data) { - CHEEVOS_LOG(RCHEEVOS_TAG "allocation failed\n"); - free(data); - return false; + len = info->size; + if (len > CHEEVOS_MB(64)) + len = CHEEVOS_MB(64); + + data->datacopy = (uint8_t*)malloc(len); + if (!data->datacopy) + { + CHEEVOS_LOG(RCHEEVOS_TAG "allocation failed\n"); + rc_hash_destroy_iterator(&data->iterator); + free(data); + return false; + } + + memcpy(data->datacopy, info->data, len); + data->iterator.buffer = data->datacopy; } - - memcpy(data->datacopy, info->data, len); } - memcpy(&data->iterator, &iterator, sizeof(iterator)); - rcheevos_begin_load_state(RCHEEVOS_LOAD_STATE_IDENTIFYING_GAME); - rcheevos_client_identify_game(hash, + rcheevos_client_identify_game(data->hash, rcheevos_identify_game_callback, data); return true; } @@ -2049,59 +2085,64 @@ bool rcheevos_load(const void *data) return true; } -static void rcheevos_add_hash(rcheevos_hash_entry_t* hash_entry) +struct rcheevos_identify_changed_disc_data { - hash_entry->next = rcheevos_locals.game.hashes; - rcheevos_locals.game.hashes = hash_entry; - rcheevos_locals.game.hash = hash_entry->hash; -} + int real_game_id; + char* path; + char hash[33]; +}; static void rcheevos_identify_game_disc_callback(void* userdata) { - rcheevos_hash_entry_t* hash_entry = (rcheevos_hash_entry_t*)userdata; + struct rcheevos_identify_changed_disc_data* changed_disc_data = + (struct rcheevos_identify_changed_disc_data*)userdata; + + /* rcheevos_locals.game.id has the game id for the new hash, swap it with the old game id */ + const int hash_game_id = rcheevos_locals.game.id; + rcheevos_locals.game.id = changed_disc_data->real_game_id; /* rcheevos_client_identify_game will update rcheevos_locals.game.id */ - if (rcheevos_locals.game.id == hash_entry->game_id) + if (rcheevos_locals.game.id == hash_game_id) { CHEEVOS_LOG(RCHEEVOS_TAG "Hash valid for current game\n"); } + else if (hash_game_id != 0) + { + /* when changing discs, if the disc is recognized but belongs to another game, allow it. + * this allows loading known game discs for games that leverage user-provided discs. */ + CHEEVOS_LOG(RCHEEVOS_TAG "Hash identified for game %d\n", hash_game_id); + } else { - /* rcheevos_locals.game.id has the game id for the new hash, swap it with the old game id */ - const int hash_game_id = rcheevos_locals.game.id; - rcheevos_locals.game.id = hash_entry->game_id; - hash_entry->game_id = hash_game_id; - - if (hash_game_id != 0) + CHEEVOS_LOG(RCHEEVOS_TAG "Disc not recognized\n"); + if (rcheevos_hardcore_active()) { - /* when changing discs, if the disc is recognized but belongs to another game, allow it. - * this allows loading known game discs for games that leverage user-provided discs. */ - CHEEVOS_LOG(RCHEEVOS_TAG "Hash identified for game %d\n", hash_game_id); - } - else - { - CHEEVOS_LOG(RCHEEVOS_TAG "Disc not recognized\n"); - if (rcheevos_hardcore_active()) - { - /* don't allow unknown game discs in hardcore. - * assume it's a modified version of the base game. */ - runloop_msg_queue_push("Hardcore paused. Game disc unrecognized.", 0, 5 * 60, false, NULL, - MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_ERROR); - rcheevos_pause_hardcore(); - } + /* don't allow unknown game discs in hardcore. + * assume it's a modified version of the base game. */ + runloop_msg_queue_push("Hardcore paused. Game disc unrecognized.", 0, 5 * 60, false, NULL, + MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_ERROR); + rcheevos_pause_hardcore(); } } /* disc is valid, add it to the known disk list */ - rcheevos_add_hash(hash_entry); + rc_libretro_hash_set_add(&rcheevos_locals.game.hashes, + changed_disc_data->path, hash_game_id, changed_disc_data->hash); + + rcheevos_locals.game.hash = + rc_libretro_hash_set_get_hash(&rcheevos_locals.game.hashes, changed_disc_data->hash); + + free(changed_disc_data->path); + free(changed_disc_data); } static void rcheevos_identify_initial_disc_callback(void* userdata) { - rcheevos_hash_entry_t* hash_entry = (rcheevos_hash_entry_t*)userdata; + struct rcheevos_identify_changed_disc_data* changed_disc_data = + (struct rcheevos_identify_changed_disc_data*)userdata; /* rcheevos_client_identify_game will update rcheevos_locals.game.id */ - if (rcheevos_locals.game.id != hash_entry->game_id) + if (rcheevos_locals.game.id != changed_disc_data->real_game_id) { if (rcheevos_locals.game.id == 0) { @@ -2116,15 +2157,22 @@ static void rcheevos_identify_initial_disc_callback(void* userdata) MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_ERROR); } + rcheevos_locals.game.hash = NULL; rcheevos_unload(); + } + else + { + /* disc is valid, add it to the known disk list */ + CHEEVOS_LOG(RCHEEVOS_TAG "Hash valid for current game\n"); + rc_libretro_hash_set_add(&rcheevos_locals.game.hashes, + changed_disc_data->path, rcheevos_locals.game.id, changed_disc_data->hash); - free(hash_entry); - return; + rcheevos_locals.game.hash = + rc_libretro_hash_set_get_hash(&rcheevos_locals.game.hashes, changed_disc_data->hash); } - /* disc is valid, add it to the known disk list */ - CHEEVOS_LOG(RCHEEVOS_TAG "Hash valid for current game\n"); - rcheevos_add_hash(hash_entry); + free(changed_disc_data->path); + free(changed_disc_data); } static void rcheevos_validate_initial_disc_handler(retro_task_t* task) @@ -2154,22 +2202,21 @@ static void rcheevos_validate_initial_disc_handler(retro_task_t* task) void rcheevos_change_disc(const char* new_disc_path, bool initial_disc) { - rcheevos_hash_entry_t* hash_entry = rcheevos_locals.game.hashes; - rcheevos_hash_entry_t* hash_entry2; - const uint32_t path_djb2 = djb2_calculate(new_disc_path); + struct rcheevos_identify_changed_disc_data* data; + char hash[33]; + int hash_game_id; /* no game loaded */ if (rcheevos_locals.game.id == 0) return; /* see if we've already identified this file */ - for (; hash_entry; hash_entry = hash_entry->next) + rcheevos_locals.game.hash = + rc_libretro_hash_set_get_hash(&rcheevos_locals.game.hashes, new_disc_path); + if (rcheevos_locals.game.hash) { - if (hash_entry->path_djb2 == path_djb2) - { - rcheevos_locals.game.hash = hash_entry->hash; - return; - } + CHEEVOS_LOG(RCHEEVOS_TAG "Switched to known hash: %s\n", rcheevos_locals.game.hash); + return; } /* don't check the disc until the game is done loading */ @@ -2185,35 +2232,45 @@ void rcheevos_change_disc(const char* new_disc_path, bool initial_disc) } /* attempt to identify the file */ - hash_entry = (rcheevos_hash_entry_t*)calloc(1, sizeof(rcheevos_hash_entry_t)); - hash_entry->path_djb2 = path_djb2; - - if (!rc_hash_generate_from_file(hash_entry->hash, rcheevos_locals.game.console_id, new_disc_path)) + if (rc_hash_generate_from_file(hash, rcheevos_locals.game.console_id, new_disc_path)) + { + /* check to see if the hash is already known */ + hash_game_id = rc_libretro_hash_set_get_game_id(&rcheevos_locals.game.hashes, hash); + if (hash_game_id) + { + /* hash identical to some other file - probably the first disc matching the m3u. */ + CHEEVOS_LOG(RCHEEVOS_TAG "Hash valid for current game\n"); + } + } + else { /* when changing discs, if the disc is not supported by the system, allow it. this is * primarily for games that support user-provided audio CDs, but does allow using discs * from other systems for games that leverage user-provided discs. */ CHEEVOS_LOG(RCHEEVOS_TAG "No hash generated\n"); - rcheevos_add_hash(hash_entry); + hash_game_id = -1; + strcpy(hash, "[NO HASH]"); + } + + if (hash_game_id) + { + /* we know how to handle this disc. no need to call the server */ + rc_libretro_hash_set_add(&rcheevos_locals.game.hashes, new_disc_path, hash_game_id, hash); + rcheevos_locals.game.hash = + rc_libretro_hash_set_get_hash(&rcheevos_locals.game.hashes, new_disc_path); return; } - /* assume it's for the same game. we'll validate in the callback */ - hash_entry->game_id = rcheevos_locals.game.id; - - /* check to see if the hash is already known - may have generated it for the m3u */ - for (hash_entry2 = rcheevos_locals.game.hashes; hash_entry2; hash_entry2 = hash_entry2->next) - { - if (string_is_equal(hash_entry->hash, hash_entry2->hash)) - { - CHEEVOS_LOG(RCHEEVOS_TAG "Hash valid for current game\n"); - rcheevos_add_hash(hash_entry); - return; - } - } - /* call the server to make sure the hash is valid for the loaded game */ - rcheevos_client_identify_game(hash_entry->hash, - initial_disc ? rcheevos_identify_initial_disc_callback : - rcheevos_identify_game_disc_callback, hash_entry); + data = (struct rcheevos_identify_changed_disc_data*) + calloc(1, sizeof(struct rcheevos_identify_changed_disc_data)); + if (data) { + data->real_game_id = rcheevos_locals.game.id; + data->path = strdup(new_disc_path); + memcpy(data->hash, hash, sizeof(data->hash)); + + rcheevos_client_identify_game(data->hash, + initial_disc ? rcheevos_identify_initial_disc_callback : + rcheevos_identify_game_disc_callback, data); + } } diff --git a/cheevos/cheevos_locals.h b/cheevos/cheevos_locals.h index 4f2b7983d1..7d64ed3933 100644 --- a/cheevos/cheevos_locals.h +++ b/cheevos/cheevos_locals.h @@ -120,24 +120,16 @@ typedef struct rcheevos_load_info_t #endif } rcheevos_load_info_t; -typedef struct rcheevos_hash_entry_t -{ - uint32_t path_djb2; - int game_id; - struct rcheevos_hash_entry_t* next; - char hash[33]; -} rcheevos_hash_entry_t; - typedef struct rcheevos_game_info_t { int id; int console_id; char* title; char badge_name[16]; - char* hash; + const char* hash; bool mastery_placard_shown; - rcheevos_hash_entry_t* hashes; + rc_libretro_hash_set_t hashes; rcheevos_racheevo_t* achievements; rcheevos_ralboard_t* leaderboards; diff --git a/deps/rcheevos/include/rc_consoles.h b/deps/rcheevos/include/rc_consoles.h index 4842276269..6863485c7c 100644 --- a/deps/rcheevos/include/rc_consoles.h +++ b/deps/rcheevos/include/rc_consoles.h @@ -81,6 +81,7 @@ enum { RC_CONSOLE_MEGADUCK = 69, RC_CONSOLE_ZEEBO = 70, RC_CONSOLE_ARDUBOY = 71, + RC_CONSOLE_WASM4 = 72, RC_CONSOLE_HUBS = 100, RC_CONSOLE_EVENTS = 101 diff --git a/deps/rcheevos/src/rcheevos/consoleinfo.c b/deps/rcheevos/src/rcheevos/consoleinfo.c index d7f21fefdb..94c63e230d 100644 --- a/deps/rcheevos/src/rcheevos/consoleinfo.c +++ b/deps/rcheevos/src/rcheevos/consoleinfo.c @@ -201,6 +201,9 @@ const char* rc_console_name(int console_id) case RC_CONSOLE_VIRTUAL_BOY: return "Virtual Boy"; + case RC_CONSOLE_WASM4: + return "WASM-4"; + case RC_CONSOLE_WII: return "Wii"; @@ -242,6 +245,14 @@ static const rc_memory_region_t _rc_memory_regions_3do[] = { }; static const rc_memory_regions_t rc_memory_regions_3do = { _rc_memory_regions_3do, 1 }; +/* ===== Amiga ===== */ +/* http://amigadev.elowar.com/read/ADCD_2.1/Hardware_Manual_guide/node00D3.html */ +static const rc_memory_region_t _rc_memory_regions_amiga[] = { + { 0x000000U, 0x07FFFFU, 0x000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "Main RAM" }, /* 512KB main RAM */ + { 0x080000U, 0x0FFFFFU, 0x080000U, RC_MEMORY_TYPE_SYSTEM_RAM, "Extended RAM" }, /* 512KB extended RAM */ +}; +static const rc_memory_regions_t rc_memory_regions_amiga = { _rc_memory_regions_amiga, 2 }; + /* ===== Amstrad CPC ===== */ /* http://www.cpcalive.com/docs/amstrad_cpc_6128_memory_map.html */ /* https://www.cpcwiki.eu/index.php/File:AWMG_page151.jpg */ @@ -324,6 +335,20 @@ static const rc_memory_region_t _rc_memory_regions_colecovision[] = { }; static const rc_memory_regions_t rc_memory_regions_colecovision = { _rc_memory_regions_colecovision, 1 }; +/* ===== Commodore 64 ===== */ +/* https://www.c64-wiki.com/wiki/Memory_Map */ +/* https://sta.c64.org/cbm64mem.html */ +static const rc_memory_region_t _rc_memory_regions_c64[] = { + { 0x000000U, 0x0003FFU, 0x000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "Kernel RAM" }, + { 0x000400U, 0x0007FFU, 0x000400U, RC_MEMORY_TYPE_VIDEO_RAM, "Screen RAM" }, + { 0x000800U, 0x009FFFU, 0x000800U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }, /* BASIC Program Storage Area */ + { 0x00A000U, 0x00BFFFU, 0x00A000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }, /* Machine Language Storage Area / BASIC ROM Area */ + { 0x00C000U, 0x00CFFFU, 0x00C000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }, /* Machine Language Storage Area */ + { 0x00D000U, 0x00DFFFU, 0x00D000U, RC_MEMORY_TYPE_SYSTEM_RAM, "I/O Area" }, /* also Character ROM */ + { 0x00E000U, 0x00FFFFU, 0x00E000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }, /* Machine Language Storage Area / Kernal ROM */ +}; +static const rc_memory_regions_t rc_memory_regions_c64 = { _rc_memory_regions_c64, 7 }; + /* ===== Dreamcast ===== */ /* http://archiv.sega-dc.de/munkeechuff/hardware/Memory.html */ static const rc_memory_region_t _rc_memory_regions_dreamcast[] = { @@ -331,6 +356,21 @@ static const rc_memory_region_t _rc_memory_regions_dreamcast[] = { }; static const rc_memory_regions_t rc_memory_regions_dreamcast = { _rc_memory_regions_dreamcast, 1 }; +/* ===== Fairchild Channel F ===== */ +static const rc_memory_region_t _rc_memory_regions_fairchild_channel_f[] = { + /* "System RAM" is actually just a bunch of registers internal to CPU so all carts have it. + * "Video RAM" is part of the console so it's always available but it is write-only by the ROMs. + * "Cartridge RAM" is the cart BUS. Most carts only have ROMs on this bus. Exception are + * German Schach and homebrew carts that have 2K of RAM there in addition to ROM. + * "F2102 RAM" is used by Maze for 1K of RAM. + * https://discord.com/channels/310192285306454017/645777658319208448/967001438087708714 */ + { 0x00000000U, 0x0000003FU, 0x00100000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }, + { 0x00000040U, 0x0000083FU, 0x00300000U, RC_MEMORY_TYPE_VIDEO_RAM, "Video RAM" }, + { 0x00000840U, 0x0001083FU, 0x00000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "Cartridge RAM" }, + { 0x00010840U, 0x00010C3FU, 0x00200000U, RC_MEMORY_TYPE_SYSTEM_RAM, "F2102 RAM" } +}; +static const rc_memory_regions_t rc_memory_regions_fairchild_channel_f = { _rc_memory_regions_fairchild_channel_f, 4 }; + /* ===== GameBoy / GameBoy Color ===== */ static const rc_memory_region_t _rc_memory_regions_gameboy[] = { { 0x000000U, 0x0000FFU, 0x000000U, RC_MEMORY_TYPE_HARDWARE_CONTROLLER, "Interrupt vector" }, @@ -553,9 +593,10 @@ static const rc_memory_regions_t rc_memory_regions_playstation = { _rc_memory_re /* https://psi-rockin.github.io/ps2tek/ */ static const rc_memory_region_t _rc_memory_regions_playstation2[] = { { 0x00000000U, 0x000FFFFFU, 0x00000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "Kernel RAM" }, - { 0x00100000U, 0x01FFFFFFU, 0x00100000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" } + { 0x00100000U, 0x01FFFFFFU, 0x00100000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }, + { 0x02000000U, 0x02003FFFU, 0x70000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "Scratchpad RAM" }, }; -static const rc_memory_regions_t rc_memory_regions_playstation2 = { _rc_memory_regions_playstation2, 2 }; +static const rc_memory_regions_t rc_memory_regions_playstation2 = { _rc_memory_regions_playstation2, 3 }; /* ===== PlayStation Portable ===== */ /* https://github.com/uofw/upspd/wiki/Memory-map */ @@ -670,6 +711,17 @@ static const rc_memory_region_t _rc_memory_regions_watara_supervision[] = { }; static const rc_memory_regions_t rc_memory_regions_watara_supervision = { _rc_memory_regions_watara_supervision, 3 }; +/* ===== WASM-4 ===== */ +/* fantasy console that runs specifically designed WebAssembly games */ +/* https://github.com/aduros/wasm4/blob/main/site/docs/intro.md#hardware-specs */ +static const rc_memory_region_t _rc_memory_regions_wasm4[] = { + { 0x000000U, 0x00FFFFU, 0x00000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }, + /* Persistent storage is not directly accessible from the game. It has to be loaded into System RAM first + { 0x010000U, 0x0103FFU, 0x80000000U, RC_MEMORY_TYPE_SAVE_RAM, "Disk Storage"} + */ +}; +static const rc_memory_regions_t rc_memory_regions_wasm4 = { _rc_memory_regions_wasm4, 1 }; + /* ===== WonderSwan ===== */ /* http://daifukkat.su/docs/wsman/#ovr_memmap */ static const rc_memory_region_t _rc_memory_regions_wonderswan[] = { @@ -697,6 +749,9 @@ const rc_memory_regions_t* rc_console_memory_regions(int console_id) case RC_CONSOLE_3DO: return &rc_memory_regions_3do; + case RC_CONSOLE_AMIGA: + return &rc_memory_regions_amiga; + case RC_CONSOLE_AMSTRAD_PC: return &rc_memory_regions_amstrad_pc; @@ -721,9 +776,15 @@ const rc_memory_regions_t* rc_console_memory_regions(int console_id) case RC_CONSOLE_COLECOVISION: return &rc_memory_regions_colecovision; + case RC_CONSOLE_COMMODORE_64: + return &rc_memory_regions_c64; + case RC_CONSOLE_DREAMCAST: return &rc_memory_regions_dreamcast; + case RC_CONSOLE_FAIRCHILD_CHANNEL_F: + return &rc_memory_regions_fairchild_channel_f; + case RC_CONSOLE_MEGADUCK: case RC_CONSOLE_GAMEBOY: return &rc_memory_regions_gameboy; @@ -821,6 +882,9 @@ const rc_memory_regions_t* rc_console_memory_regions(int console_id) case RC_CONSOLE_VIRTUAL_BOY: return &rc_memory_regions_virtualboy; + case RC_CONSOLE_WASM4: + return &rc_memory_regions_wasm4; + case RC_CONSOLE_WONDERSWAN: return &rc_memory_regions_wonderswan; diff --git a/deps/rcheevos/src/rcheevos/rc_libretro.c b/deps/rcheevos/src/rcheevos/rc_libretro.c index 0afaf5475b..eb01ceedd4 100644 --- a/deps/rcheevos/src/rcheevos/rc_libretro.c +++ b/deps/rcheevos/src/rcheevos/rc_libretro.c @@ -14,6 +14,16 @@ #include #include +/* internal helper functions in hash.c */ +extern void* rc_file_open(const char* path); +extern void rc_file_seek(void* file_handle, int64_t offset, int origin); +extern int64_t rc_file_tell(void* file_handle); +extern size_t rc_file_read(void* file_handle, void* buffer, int requested_bytes); +extern void rc_file_close(void* file_handle); +extern int rc_path_compare_extension(const char* path, const char* ext); +extern int rc_hash_error(const char* message); + + static rc_libretro_message_callback rc_libretro_verbose_message_callback = NULL; /* a value that starts with a comma is a CSV. @@ -229,6 +239,41 @@ const rc_disallowed_setting_t* rc_libretro_get_disallowed_settings(const char* l return NULL; } +typedef struct rc_disallowed_core_systems_t +{ + const char* library_name; + const int disallowed_consoles[4]; +} rc_disallowed_core_systems_t; + +static const rc_disallowed_core_systems_t rc_disallowed_core_systems[] = { + /* https://github.com/libretro/Mesen-S/issues/8 */ + { "Mesen-S", { RC_CONSOLE_GAMEBOY, RC_CONSOLE_GAMEBOY_COLOR, 0 }}, + { NULL, { 0 } } +}; + +int rc_libretro_is_system_allowed(const char* library_name, int console_id) { + const rc_disallowed_core_systems_t* core_filter = rc_disallowed_core_systems; + size_t library_name_length; + size_t i; + + if (!library_name || !library_name[0]) + return 1; + + library_name_length = strlen(library_name) + 1; + while (core_filter->library_name) { + if (memcmp(core_filter->library_name, library_name, library_name_length) == 0) { + for (i = 0; i < sizeof(core_filter->disallowed_consoles) / sizeof(core_filter->disallowed_consoles[0]); ++i) { + if (core_filter->disallowed_consoles[i] == console_id) + return 0; + } + break; + } + + ++core_filter; + } + + return 1; +} unsigned char* rc_libretro_memory_find(const rc_libretro_memory_regions_t* regions, unsigned address) { unsigned i; @@ -556,3 +601,168 @@ int rc_libretro_memory_init(rc_libretro_memory_regions_t* regions, const struct void rc_libretro_memory_destroy(rc_libretro_memory_regions_t* regions) { memset(regions, 0, sizeof(*regions)); } + +void rc_libretro_hash_set_init(struct rc_libretro_hash_set_t* hash_set, + const char* m3u_path, rc_libretro_get_image_path_func get_image_path) { + char image_path[1024]; + char* m3u_contents; + char* ptr; + size_t file_len; + void* file_handle; + int index = 0; + + memset(hash_set, 0, sizeof(*hash_set)); + + if (!rc_path_compare_extension(m3u_path, "m3u")) + return; + + file_handle = rc_file_open(m3u_path); + if (!file_handle) + { + rc_hash_error("Could not open playlist"); + return; + } + + rc_file_seek(file_handle, 0, SEEK_END); + file_len = rc_file_tell(file_handle); + rc_file_seek(file_handle, 0, SEEK_SET); + + m3u_contents = (char*)malloc(file_len + 1); + rc_file_read(file_handle, m3u_contents, (int)file_len); + m3u_contents[file_len] = '\0'; + + rc_file_close(file_handle); + + ptr = m3u_contents; + do + { + /* ignore whitespace */ + while (isspace((int)*ptr)) + ++ptr; + + if (*ptr == '#') + { + /* ignore comment unless it's the special SAVEDISK extension */ + if (memcmp(ptr, "#SAVEDISK:", 10) == 0) + { + /* get the path to the save disk from the frontend, assign it a bogus hash so + * it doesn't get hashed later */ + if (get_image_path(index, image_path, sizeof(image_path))) + { + const char save_disk_hash[33] = "[SAVE DISK]"; + rc_libretro_hash_set_add(hash_set, image_path, -1, save_disk_hash); + ++index; + } + } + } + else + { + /* non-empty line, tally a file */ + ++index; + } + + /* find the end of the line */ + while (*ptr && *ptr != '\n') + ++ptr; + + } while (*ptr); + + free(m3u_contents); + + if (hash_set->entries_count > 0) + { + /* at least one save disk was found. make sure the core supports the #SAVEDISK: extension by + * asking for the last expected disk. if it's not found, assume no #SAVEDISK: support */ + if (!get_image_path(index - 1, image_path, sizeof(image_path))) + hash_set->entries_count = 0; + } +} + +void rc_libretro_hash_set_destroy(struct rc_libretro_hash_set_t* hash_set) { + if (hash_set->entries) + free(hash_set->entries); + memset(hash_set, 0, sizeof(*hash_set)); +} + +static unsigned rc_libretro_djb2(const char* input) +{ + unsigned result = 5381; + char c; + + while ((c = *input++) != '\0') + result = ((result << 5) + result) + c; /* result = result * 33 + c */ + + return result; +} + +void rc_libretro_hash_set_add(struct rc_libretro_hash_set_t* hash_set, + const char* path, int game_id, const char hash[33]) { + const unsigned path_djb2 = (path != NULL) ? rc_libretro_djb2(path) : 0; + struct rc_libretro_hash_entry_t* entry = NULL; + struct rc_libretro_hash_entry_t* scan; + struct rc_libretro_hash_entry_t* stop = hash_set->entries + hash_set->entries_count;; + + if (path_djb2) + { + /* attempt to match the path */ + for (scan = hash_set->entries; scan < stop; ++scan) + { + if (scan->path_djb2 == path_djb2) + { + entry = scan; + break; + } + } + } + + if (!entry) + { + /* entry not found, allocate a new one */ + if (hash_set->entries_size == 0) + { + hash_set->entries_size = 4; + hash_set->entries = (struct rc_libretro_hash_entry_t*) + malloc(hash_set->entries_size * sizeof(struct rc_libretro_hash_entry_t)); + } + else if (hash_set->entries_count == hash_set->entries_size) + { + hash_set->entries_size += 4; + hash_set->entries = (struct rc_libretro_hash_entry_t*)realloc(hash_set->entries, + hash_set->entries_size * sizeof(struct rc_libretro_hash_entry_t)); + } + + entry = hash_set->entries + hash_set->entries_count++; + } + + /* update the entry */ + entry->path_djb2 = path_djb2; + entry->game_id = game_id; + memcpy(entry->hash, hash, sizeof(entry->hash)); +} + +const char* rc_libretro_hash_set_get_hash(const struct rc_libretro_hash_set_t* hash_set, const char* path) +{ + const unsigned path_djb2 = rc_libretro_djb2(path); + struct rc_libretro_hash_entry_t* scan = hash_set->entries; + struct rc_libretro_hash_entry_t* stop = scan + hash_set->entries_count; + for (; scan < stop; ++scan) + { + if (scan->path_djb2 == path_djb2) + return scan->hash; + } + + return NULL; +} + +int rc_libretro_hash_set_get_game_id(const struct rc_libretro_hash_set_t* hash_set, const char* hash) +{ + struct rc_libretro_hash_entry_t* scan = hash_set->entries; + struct rc_libretro_hash_entry_t* stop = scan + hash_set->entries_count; + for (; scan < stop; ++scan) + { + if (memcmp(scan->hash, hash, sizeof(scan->hash)) == 0) + return scan->game_id; + } + + return 0; +} diff --git a/deps/rcheevos/src/rcheevos/rc_libretro.h b/deps/rcheevos/src/rcheevos/rc_libretro.h index 97becbb9eb..553bcc5b53 100644 --- a/deps/rcheevos/src/rcheevos/rc_libretro.h +++ b/deps/rcheevos/src/rcheevos/rc_libretro.h @@ -21,6 +21,7 @@ typedef struct rc_disallowed_setting_t const rc_disallowed_setting_t* rc_libretro_get_disallowed_settings(const char* library_name); int rc_libretro_is_setting_allowed(const rc_disallowed_setting_t* disallowed_settings, const char* setting, const char* value); +int rc_libretro_is_system_allowed(const char* library_name, int console_id); /*****************************************************************************\ | Memory Mapping | @@ -53,6 +54,35 @@ void rc_libretro_memory_destroy(rc_libretro_memory_regions_t* regions); unsigned char* rc_libretro_memory_find(const rc_libretro_memory_regions_t* regions, unsigned address); +/*****************************************************************************\ +| Disk Identification | +\*****************************************************************************/ + +typedef struct rc_libretro_hash_entry_t +{ + uint32_t path_djb2; + int game_id; + char hash[33]; +} rc_libretro_hash_entry_t; + +typedef struct rc_libretro_hash_set_t +{ + struct rc_libretro_hash_entry_t* entries; + uint16_t entries_count; + uint16_t entries_size; +} rc_libretro_hash_set_t; + +typedef int (*rc_libretro_get_image_path_func)(unsigned index, char* buffer, size_t buffer_size); + +void rc_libretro_hash_set_init(struct rc_libretro_hash_set_t* hash_set, + const char* m3u_path, rc_libretro_get_image_path_func get_image_path); +void rc_libretro_hash_set_destroy(struct rc_libretro_hash_set_t* hash_set); + +void rc_libretro_hash_set_add(struct rc_libretro_hash_set_t* hash_set, + const char* path, int game_id, const char hash[33]); +const char* rc_libretro_hash_set_get_hash(const struct rc_libretro_hash_set_t* hash_set, const char* path); +int rc_libretro_hash_set_get_game_id(const struct rc_libretro_hash_set_t* hash_set, const char* hash); + #ifdef __cplusplus } #endif diff --git a/deps/rcheevos/src/rcheevos/trigger.c b/deps/rcheevos/src/rcheevos/trigger.c index 0a9d705d8d..dda918b536 100644 --- a/deps/rcheevos/src/rcheevos/trigger.c +++ b/deps/rcheevos/src/rcheevos/trigger.c @@ -96,16 +96,29 @@ int rc_trigger_state_active(int state) } } +static int rc_condset_is_measured_from_hitcount(const rc_condset_t* condset, unsigned measured_value) +{ + const rc_condition_t* condition; + for (condition = condset->conditions; condition; condition = condition->next) { + if (condition->type == RC_CONDITION_MEASURED && condition->required_hits && + condition->current_hits == measured_value) { + return 1; + } + } + + return 0; +} + static void rc_reset_trigger_hitcounts(rc_trigger_t* self) { rc_condset_t* condset; - if (self->requirement != 0) { + if (self->requirement) { rc_reset_condset(self->requirement); } condset = self->alternative; - while (condset != 0) { + while (condset) { rc_reset_condset(condset); condset = condset->next; } @@ -168,7 +181,7 @@ int rc_evaluate_trigger(rc_trigger_t* self, rc_peek_t peek, void* ud, lua_State* sub_primed |= eval_state.primed; condset = condset->next; - } while (condset != 0); + } while (condset); /* to trigger, the core must be true and at least one alt must be true */ ret &= sub; @@ -192,13 +205,31 @@ int rc_evaluate_trigger(rc_trigger_t* self, rc_peek_t peek, void* ud, lua_State* return RC_TRIGGER_STATE_WAITING; } + /* if any ResetIf condition was true, reset the hit counts */ if (eval_state.was_reset) { - /* if any ResetIf condition was true, reset the hit counts */ - rc_reset_trigger_hitcounts(self); - - /* if the measured value came from a hit count, reset it too */ - if (eval_state.measured_from_hits) + /* if the measured value came from a hit count, reset it. do this before calling + * rc_reset_trigger_hitcounts in case we need to call rc_condset_is_measured_from_hitcount */ + if (eval_state.measured_from_hits) { self->measured_value = 0; + } + else if (is_paused && self->measured_value) { + /* if the measured value is in a paused group, measured_from_hits won't have been set. + * attempt to determine if it should have been */ + if (self->requirement && self->requirement->is_paused && + rc_condset_is_measured_from_hitcount(self->requirement, self->measured_value)) { + self->measured_value = 0; + } + else { + for (condset = self->alternative; condset; condset = condset->next) { + if (condset->is_paused && rc_condset_is_measured_from_hitcount(condset, self->measured_value)) { + self->measured_value = 0; + break; + } + } + } + } + + rc_reset_trigger_hitcounts(self); /* if there were hit counts to clear, return RESET, but don't change the state */ if (self->has_hits) { @@ -206,7 +237,7 @@ int rc_evaluate_trigger(rc_trigger_t* self, rc_peek_t peek, void* ud, lua_State* /* cannot be PRIMED while ResetIf is true */ if (self->state == RC_TRIGGER_STATE_PRIMED) - self->state = RC_TRIGGER_STATE_ACTIVE; + self->state = RC_TRIGGER_STATE_ACTIVE; return RC_TRIGGER_STATE_RESET; } diff --git a/deps/rcheevos/src/rhash/cdreader.c b/deps/rcheevos/src/rhash/cdreader.c index 27c93c7762..113f155933 100644 --- a/deps/rcheevos/src/rhash/cdreader.c +++ b/deps/rcheevos/src/rhash/cdreader.c @@ -563,32 +563,34 @@ static void* cdreader_open_cue_track(const char* path, uint32_t track) static void* cdreader_open_gdi_track(const char* path, uint32_t track) { + void* file_handle; char buffer[1024]; char mode[16] = "MODE1/"; char sector_size[16]; char file[256]; int64_t track_size; int track_type; - char *ptr, *ptr2, *end; + char* bin_path = ""; + uint32_t current_track = 0; + char* ptr, *ptr2, *end; + int lba = 0; + uint32_t largest_track = 0; + int64_t largest_track_size = 0; char largest_track_file[256]; char largest_track_sector_size[16]; - char *bin_path = NULL; - uint32_t current_track = 0; - int lba = 0; - uint32_t largest_track = 0; - int64_t largest_track_size = 0; - int largest_track_lba = 0; + int largest_track_lba = 0; - int found = 0; - size_t num_read = 0; - int64_t file_offset = 0; - struct cdrom_t* cdrom = NULL; - void *file_handle = rc_file_open(path); + int found = 0; + size_t num_read = 0; + int64_t file_offset = 0; + struct cdrom_t* cdrom = NULL; + + file_handle = rc_file_open(path); if (!file_handle) return NULL; - file[0] = '\0'; + file[0] = '\0'; do { num_read = rc_file_read(file_handle, buffer, sizeof(buffer) - 1); diff --git a/deps/rcheevos/src/rhash/hash.c b/deps/rcheevos/src/rhash/hash.c index 9db64fe025..a563db05d2 100644 --- a/deps/rcheevos/src/rhash/hash.c +++ b/deps/rcheevos/src/rhash/hash.c @@ -1609,6 +1609,8 @@ int rc_hash_generate_from_buffer(char hash[33], int console_id, const uint8_t* b case RC_CONSOLE_ATARI_2600: case RC_CONSOLE_ATARI_JAGUAR: case RC_CONSOLE_COLECOVISION: + case RC_CONSOLE_COMMODORE_64: + case RC_CONSOLE_FAIRCHILD_CHANNEL_F: case RC_CONSOLE_GAMEBOY: case RC_CONSOLE_GAMEBOY_ADVANCE: case RC_CONSOLE_GAMEBOY_COLOR: @@ -1629,6 +1631,7 @@ int rc_hash_generate_from_buffer(char hash[33], int console_id, const uint8_t* b case RC_CONSOLE_TIC80: case RC_CONSOLE_VECTREX: case RC_CONSOLE_VIRTUAL_BOY: + case RC_CONSOLE_WASM4: case RC_CONSOLE_WONDERSWAN: return rc_hash_buffer(hash, buffer, buffer_size); @@ -1898,6 +1901,7 @@ int rc_hash_generate_from_file(char hash[33], int console_id, const char* path) case RC_CONSOLE_ATARI_2600: case RC_CONSOLE_ATARI_JAGUAR: case RC_CONSOLE_COLECOVISION: + case RC_CONSOLE_FAIRCHILD_CHANNEL_F: case RC_CONSOLE_GAMEBOY: case RC_CONSOLE_GAMEBOY_ADVANCE: case RC_CONSOLE_GAMEBOY_COLOR: @@ -1916,12 +1920,14 @@ int rc_hash_generate_from_file(char hash[33], int console_id, const char* path) case RC_CONSOLE_TIC80: case RC_CONSOLE_VECTREX: case RC_CONSOLE_VIRTUAL_BOY: + case RC_CONSOLE_WASM4: case RC_CONSOLE_WONDERSWAN: /* generic whole-file hash - don't buffer */ return rc_hash_whole_file(hash, path); case RC_CONSOLE_AMSTRAD_PC: case RC_CONSOLE_APPLE_II: + case RC_CONSOLE_COMMODORE_64: case RC_CONSOLE_MSX: case RC_CONSOLE_PC8800: /* generic whole-file hash with m3u support - don't buffer */ @@ -2132,7 +2138,7 @@ void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char* } } - /* bin is associated with MegaDrive, Sega32X, Atari 2600, Watara Supervision, and MegaDuck. + /* bin is associated with MegaDrive, Sega32X, Atari 2600, Watara Supervision, MegaDuck, and Fairchild Channel F. * Since they all use the same hashing algorithm, only specify one of them */ iterator->consoles[0] = RC_CONSOLE_MEGA_DRIVE; } @@ -2173,6 +2179,10 @@ void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char* { iterator->consoles[0] = RC_CONSOLE_MSX; } + else if (rc_path_compare_extension(ext, "chf")) + { + iterator->consoles[0] = RC_CONSOLE_FAIRCHILD_CHANNEL_F; + } break; case 'd': @@ -2180,6 +2190,10 @@ void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char* { rc_hash_initialize_dsk_iterator(iterator, path); } + else if (rc_path_compare_extension(ext, "d64")) + { + iterator->consoles[0] = RC_CONSOLE_COMMODORE_64; + } else if (rc_path_compare_extension(ext, "d88")) { iterator->consoles[0] = RC_CONSOLE_PC8800; @@ -2320,6 +2334,11 @@ void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char* { iterator->consoles[0] = RC_CONSOLE_NEOGEO_POCKET; } + else if (rc_path_compare_extension(ext, "nib")) + { + /* also Apple II, but both are full-file hashes */ + iterator->consoles[0] = RC_CONSOLE_COMMODORE_64; + } break; case 'p': @@ -2332,8 +2351,9 @@ void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char* case 'r': if (rc_path_compare_extension(ext, "rom")) { + /* rom is associated with MSX, Thomson TO-8, and Fairchild Channel F. + * Since they all use the same hashing algorithm, only specify one of them */ iterator->consoles[0] = RC_CONSOLE_MSX; - iterator->consoles[1] = RC_CONSOLE_THOMSONTO8; /* cartridge */ } if (rc_path_compare_extension(ext, "ri")) { @@ -2393,6 +2413,10 @@ void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char* { iterator->consoles[0] = RC_CONSOLE_WONDERSWAN; } + else if (rc_path_compare_extension(ext, "wasm")) + { + iterator->consoles[0] = RC_CONSOLE_WASM4; + } else if (rc_path_compare_extension(ext, "woz")) { iterator->consoles[0] = RC_CONSOLE_APPLE_II; diff --git a/disk_control_interface.c b/disk_control_interface.c index bd89be1808..95488421d6 100644 --- a/disk_control_interface.c +++ b/disk_control_interface.c @@ -331,7 +331,16 @@ bool disk_control_set_eject_state( #ifdef HAVE_CHEEVOS if (!error && !eject) - rcheevos_change_disc(disk_control->index_record.image_path, false); + { + if (disk_control->cb.get_image_index && disk_control->cb.get_image_path) + { + char image_path[PATH_MAX_LENGTH] = ""; + unsigned image_index = disk_control->cb.get_image_index(); + + if (disk_control->cb.get_image_path(image_index, image_path, sizeof(image_path))) + rcheevos_change_disc(image_path, false); + } + } #endif return !error;