diff --git a/command.c b/command.c index 7c19d957c9..ee99e70747 100644 --- a/command.c +++ b/command.c @@ -1088,6 +1088,8 @@ bool command_event_save_auto_state( return false; if (current_core_type == CORE_TYPE_DUMMY) return false; + if (!core_info_current_supports_savestate()) + return false; if (string_is_empty(path_basename(path_get(RARCH_PATH_BASENAME)))) return false; @@ -1148,6 +1150,9 @@ bool command_event_load_entry_state(void) runloop_state_t *runloop_st = runloop_state_get_ptr(); bool ret = false; + if (!core_info_current_supports_savestate()) + return false; + #ifdef HAVE_CHEEVOS if (rcheevos_hardcore_active()) return false; @@ -1187,6 +1192,10 @@ void command_event_load_auto_state(void) char savestate_name_auto[PATH_MAX_LENGTH]; runloop_state_t *runloop_st = runloop_state_get_ptr(); bool ret = false; + + if (!core_info_current_supports_savestate()) + return; + #ifdef HAVE_CHEEVOS if (rcheevos_hardcore_active()) return; @@ -1551,16 +1560,22 @@ bool command_event_main_state(unsigned cmd) char msg[128]; char state_path[16384]; settings_t *settings = config_get_ptr(); + bool savestates_enabled = core_info_current_supports_savestate(); bool ret = false; bool push_msg = true; state_path[0] = msg[0] = '\0'; - retroarch_get_current_savestate_path(state_path, sizeof(state_path)); + if (savestates_enabled) + { + retroarch_get_current_savestate_path(state_path, + sizeof(state_path)); - core_serialize_size(&info); + core_serialize_size(&info); + savestates_enabled = (info.size > 0); + } - if (info.size) + if (savestates_enabled) { switch (cmd) { diff --git a/core_info.c b/core_info.c index b5d082ab18..1ace5defbb 100644 --- a/core_info.c +++ b/core_info.c @@ -48,6 +48,7 @@ /* Core Info Cache START */ /*************************/ +#define CORE_INFO_CACHE_VERSION "1.1" #define CORE_INFO_CACHE_DEFAULT_CAPACITY 8 /* TODO/FIXME: Apparently rzip compression is an issue on UWP */ @@ -60,6 +61,7 @@ typedef struct core_info_t *items; size_t length; size_t capacity; + char *version; bool refresh; } core_info_cache_list_t; @@ -103,7 +105,9 @@ static bool CCJSONObjectMemberHandler(void *context, { CCJSONContext *pCtx = (CCJSONContext *)context; - if ((pCtx->object_depth == 2) && (pCtx->array_depth == 1) && length) + if ((pCtx->object_depth == 2) && + (pCtx->array_depth == 1) && + length) { pCtx->current_string_val = NULL; pCtx->current_string_list_val = NULL; @@ -199,26 +203,29 @@ static bool CCJSONObjectMemberHandler(void *context, } else if (string_is_equal(pValue, "supports_no_game")) pCtx->current_entry_bool_val = &pCtx->core_info->supports_no_game; + else if (string_is_equal(pValue, "savestate_support_level")) + pCtx->current_entry_uint_val = &pCtx->core_info->savestate_support_level; break; } } - else if ((pCtx->object_depth == 3) - && (pCtx->array_depth == 1) && length) + else if ((pCtx->object_depth == 3) && + (pCtx->array_depth == 1) && + length) { pCtx->current_string_val = NULL; pCtx->current_entry_uint_val = NULL; if (pCtx->to_core_file_id) { - if (string_is_equal(pValue, "str")) + if (string_is_equal(pValue, "str")) pCtx->current_string_val = &pCtx->core_info->core_file_id.str; else if (string_is_equal(pValue, "hash")) pCtx->current_entry_uint_val = &pCtx->core_info->core_file_id.hash; } } - else if ((pCtx->object_depth == 3) - && (pCtx->array_depth == 2) - && length) + else if ((pCtx->object_depth == 3) && + (pCtx->array_depth == 2) && + length) { pCtx->current_string_val = NULL; pCtx->current_entry_bool_val = NULL; @@ -227,7 +234,7 @@ static bool CCJSONObjectMemberHandler(void *context, { size_t firmware_idx = pCtx->core_info->firmware_count - 1; - if (string_is_equal(pValue, "path")) + if (string_is_equal(pValue, "path")) pCtx->current_string_val = &pCtx->core_info->firmware[firmware_idx].path; else if (string_is_equal(pValue, "desc")) pCtx->current_string_val = &pCtx->core_info->firmware[firmware_idx].desc; @@ -235,6 +242,15 @@ static bool CCJSONObjectMemberHandler(void *context, pCtx->current_entry_bool_val = &pCtx->core_info->firmware[firmware_idx].optional; } } + else if ((pCtx->object_depth == 1) && + (pCtx->array_depth == 0) && + length) + { + pCtx->current_string_val = NULL; + + if (string_is_equal(pValue, "version")) + pCtx->current_string_val = &pCtx->core_info_cache_list->version; + } return true; } @@ -319,6 +335,11 @@ static bool CCJSONStartObjectHandler(void *context) pCtx->core_info = (core_info_t*)calloc(1, sizeof(core_info_t)); if (!pCtx->core_info) return false; + + /* Assume all cores have 'full' savestate support + * by default */ + pCtx->core_info->savestate_support_level = + CORE_INFO_SAVESTATE_DETERMINISTIC; } else if ((pCtx->object_depth == 3) && (pCtx->array_depth == 2)) { @@ -448,6 +469,7 @@ static void core_info_copy(core_info_t *src, core_info_t *dst) ? strdup(src->core_file_id.str) : NULL; dst->core_file_id.hash = src->core_file_id.hash; + dst->savestate_support_level = src->savestate_support_level; dst->has_info = src->has_info; dst->supports_no_game = src->supports_no_game; dst->database_match_archive_member = src->database_match_archive_member; @@ -542,6 +564,7 @@ static void core_info_transfer(core_info_t *src, core_info_t *dst) src->core_file_id.str = NULL; dst->core_file_id.hash = src->core_file_id.hash; + dst->savestate_support_level = src->savestate_support_level; dst->has_info = src->has_info; dst->supports_no_game = src->supports_no_game; dst->database_match_archive_member = src->database_match_archive_member; @@ -565,6 +588,10 @@ static void core_info_cache_list_free( } free(core_info_cache_list->items); + + if (core_info_cache_list->version) + free(core_info_cache_list->version); + free(core_info_cache_list); } @@ -657,6 +684,7 @@ static core_info_cache_list_t *core_info_cache_list_new(void) core_info_cache_list->capacity = CORE_INFO_CACHE_DEFAULT_CAPACITY; core_info_cache_list->refresh = false; + core_info_cache_list->version = NULL; return core_info_cache_list; } @@ -758,6 +786,21 @@ static core_info_cache_list_t *core_info_cache_read(const char *info_dir) free(context.core_info); } + /* If info cache file has the wrong version + * number, discard it */ + if (string_is_empty(core_info_cache_list->version) || + !string_is_equal(core_info_cache_list->version, + CORE_INFO_CACHE_VERSION)) + { + RARCH_WARN("[Core Info] Core info cache has invalid version" + " - forcing refresh (required v%s, found v%s)\n", + CORE_INFO_CACHE_VERSION, + core_info_cache_list->version); + + core_info_cache_list_free(context.core_info_cache_list); + core_info_cache_list = core_info_cache_list_new(); + } + end: intfstream_close(file); free(file); @@ -822,7 +865,7 @@ static bool core_info_cache_write(core_info_cache_list_t *list, const char *info rjsonwriter_add_string(writer, "version"); rjsonwriter_add_colon(writer); rjsonwriter_add_space(writer); - rjsonwriter_add_string(writer, "1.0"); + rjsonwriter_add_string(writer, CORE_INFO_CACHE_VERSION); rjsonwriter_add_comma(writer); rjsonwriter_add_newline(writer); rjsonwriter_add_spaces(writer, 2); @@ -1051,6 +1094,14 @@ static bool core_info_cache_write(core_info_cache_list_t *list, const char *info rjsonwriter_add_comma(writer); rjsonwriter_add_newline(writer); + rjsonwriter_add_spaces(writer, 6); + rjsonwriter_add_string(writer, "savestate_support_level"); + rjsonwriter_add_colon(writer); + rjsonwriter_add_space(writer); + rjsonwriter_add_unsigned(writer, info->savestate_support_level); + rjsonwriter_add_comma(writer); + rjsonwriter_add_newline(writer); + rjsonwriter_add_spaces(writer, 6); rjsonwriter_add_string(writer, "has_info"); rjsonwriter_add_colon(writer); @@ -1703,6 +1754,40 @@ static void core_info_parse_config_file( &tmp_bool)) info->is_experimental = tmp_bool; + + /* Savestate support level is slightly more complex, + * since it is a value derived from two configuration + * parameters */ + + /* > Assume all cores have 'full' savestate support + * by default */ + info->savestate_support_level = + CORE_INFO_SAVESTATE_DETERMINISTIC; + + /* > Check whether savestate functionality is defined + * in the info file */ + if (config_get_bool(conf, "savestate", &tmp_bool)) + { + if (tmp_bool) + { + /* Check if savestate features are defined */ + entry = config_get_entry(conf, "savestate_features"); + + if (entry && !string_is_empty(entry->value)) + { + if (string_is_equal(entry->value, "basic")) + info->savestate_support_level = + CORE_INFO_SAVESTATE_BASIC; + else if (string_is_equal(entry->value, "serialized")) + info->savestate_support_level = + CORE_INFO_SAVESTATE_SERIALIZED; + } + } + else + info->savestate_support_level = + CORE_INFO_SAVESTATE_DISABLED; + } + core_info_resolve_firmware(info, conf); info->has_info = true; @@ -2097,6 +2182,7 @@ bool core_info_init_current_core(void) current->is_experimental = false; current->is_locked = false; current->firmware_count = 0; + current->savestate_support_level = CORE_INFO_SAVESTATE_DETERMINISTIC; current->path = NULL; current->display_name = NULL; current->display_version = NULL; @@ -2916,6 +3002,62 @@ bool core_info_hw_api_supported(core_info_t *info) #endif } +bool core_info_current_supports_savestate(void) +{ + core_info_state_t *p_coreinfo = &core_info_st; + + /* If no core is currently loaded, assume + * by default that all savestate functionality + * is supported */ + if (!p_coreinfo->current) + return true; + + return p_coreinfo->current->savestate_support_level >= + CORE_INFO_SAVESTATE_BASIC; +} + +bool core_info_current_supports_rewind(void) +{ + core_info_state_t *p_coreinfo = &core_info_st; + + /* If no core is currently loaded, assume + * by default that all savestate functionality + * is supported */ + if (!p_coreinfo->current) + return true; + + return p_coreinfo->current->savestate_support_level >= + CORE_INFO_SAVESTATE_SERIALIZED; +} + +bool core_info_current_supports_netplay(void) +{ + core_info_state_t *p_coreinfo = &core_info_st; + + /* If no core is currently loaded, assume + * by default that all savestate functionality + * is supported */ + if (!p_coreinfo->current) + return true; + + return p_coreinfo->current->savestate_support_level >= + CORE_INFO_SAVESTATE_DETERMINISTIC; +} + +bool core_info_current_supports_runahead(void) +{ + core_info_state_t *p_coreinfo = &core_info_st; + + /* If no core is currently loaded, assume + * by default that all savestate functionality + * is supported */ + if (!p_coreinfo->current) + return true; + + return p_coreinfo->current->savestate_support_level >= + CORE_INFO_SAVESTATE_DETERMINISTIC; +} + /* Sets 'locked' status of specified core * > Returns true if successful * > Like all functions that access the cached diff --git a/core_info.h b/core_info.h index f6c034b3e7..4fe4425757 100644 --- a/core_info.h +++ b/core_info.h @@ -25,6 +25,23 @@ RETRO_BEGIN_DECLS +/* Defines the levels of savestate support + * that may be offered by a core: + * - serialized: rewind + * - deterministic: netplay/runahead + * Thus: + * (level < CORE_INFO_SAVESTATE_BASIC) + * -> no savestate support + * (level < CORE_INFO_SAVESTATE_SERIALIZED) + * -> no rewind/netplay/runahead + * (level < CORE_INFO_SAVESTATE_DETERMINISTIC) + * -> no netplay/runahead + */ +#define CORE_INFO_SAVESTATE_DISABLED 0 +#define CORE_INFO_SAVESTATE_BASIC 1 +#define CORE_INFO_SAVESTATE_SERIALIZED 2 +#define CORE_INFO_SAVESTATE_DETERMINISTIC 3 + enum core_info_list_qsort_type { CORE_INFO_LIST_SORT_PATH = 0, @@ -84,6 +101,7 @@ typedef struct core_info_firmware_t *firmware; core_file_id_t core_file_id; /* ptr alignment */ size_t firmware_count; + uint32_t savestate_support_level; bool has_info; bool supports_no_game; bool database_match_archive_member; @@ -184,6 +202,16 @@ bool core_info_list_get_info(core_info_list_t *core_info_list, bool core_info_hw_api_supported(core_info_t *info); +/* Convenience wrapper functions used to interpret + * the 'savestate_support_level' parameter of + * the currently loaded core. If no core is + * loaded, will return 'true' (since full + * savestate functionality is assumed by default) */ +bool core_info_current_supports_savestate(void); +bool core_info_current_supports_rewind(void); +bool core_info_current_supports_netplay(void); +bool core_info_current_supports_runahead(void); + /* Sets 'locked' status of specified core * > Returns true if successful * > Like all functions that access the cached diff --git a/intl/msg_hash_us.h b/intl/msg_hash_us.h index f16edf6fc7..302ec1899a 100644 --- a/intl/msg_hash_us.h +++ b/intl/msg_hash_us.h @@ -467,6 +467,26 @@ MSG_HASH( MENU_ENUM_LABEL_VALUE_CORE_INFO_REQUIRED_HW_API, "Required Graphics API" ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_CORE_INFO_SAVESTATE_SUPPORT_LEVEL, + "Save State Support" + ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_CORE_INFO_SAVESTATE_DISABLED, + "None" + ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_CORE_INFO_SAVESTATE_BASIC, + "Basic (Save/Load)" + ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_CORE_INFO_SAVESTATE_SERIALIZED, + "Serialized (Save/Load, Rewind)" + ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_CORE_INFO_SAVESTATE_DETERMINISTIC, + "Deterministic (Save/Load, Rewind, Run-Ahead, Netplay)" + ) MSG_HASH( MENU_ENUM_LABEL_VALUE_CORE_INFO_FIRMWARE, "Firmware" diff --git a/menu/menu_displaylist.c b/menu/menu_displaylist.c index 55da0f5612..de4b740d11 100644 --- a/menu/menu_displaylist.c +++ b/menu/menu_displaylist.c @@ -460,13 +460,14 @@ static int menu_displaylist_parse_core_info(menu_displaylist_info_t *info, settings_t *settings) { char tmp[PATH_MAX_LENGTH]; - unsigned i, count = 0; - core_info_t *core_info = NULL; - const char *core_path = NULL; + unsigned i, count = 0; + core_info_t *core_info = NULL; + const char *core_path = NULL; + const char *savestate_support = NULL; #if !(defined(__WINRT__) || defined(WINAPI_FAMILY) && WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP) - bool kiosk_mode_enable = settings->bools.kiosk_mode_enable; + bool kiosk_mode_enable = settings->bools.kiosk_mode_enable; #if defined(HAVE_NETWORKING) && defined(HAVE_ONLINE_UPDATER) - bool menu_show_core_updater = settings->bools.menu_show_core_updater; + bool menu_show_core_updater = settings->bools.menu_show_core_updater; #endif #endif @@ -615,6 +616,39 @@ static int menu_displaylist_parse_core_info(menu_displaylist_info_t *info, count++; } + switch (core_info->savestate_support_level) + { + case CORE_INFO_SAVESTATE_BASIC: + savestate_support = msg_hash_to_str( + MENU_ENUM_LABEL_VALUE_CORE_INFO_SAVESTATE_BASIC); + break; + case CORE_INFO_SAVESTATE_SERIALIZED: + savestate_support = msg_hash_to_str( + MENU_ENUM_LABEL_VALUE_CORE_INFO_SAVESTATE_SERIALIZED); + break; + case CORE_INFO_SAVESTATE_DETERMINISTIC: + savestate_support = msg_hash_to_str( + MENU_ENUM_LABEL_VALUE_CORE_INFO_SAVESTATE_DETERMINISTIC); + break; + default: + if (core_info->savestate_support_level > + CORE_INFO_SAVESTATE_DETERMINISTIC) + savestate_support = msg_hash_to_str( + MENU_ENUM_LABEL_VALUE_CORE_INFO_SAVESTATE_DETERMINISTIC); + else + savestate_support = msg_hash_to_str( + MENU_ENUM_LABEL_VALUE_CORE_INFO_SAVESTATE_DISABLED); + break; + } + fill_pathname_join_concat_noext(tmp, + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_CORE_INFO_SAVESTATE_SUPPORT_LEVEL), + ": ", + savestate_support, + sizeof(tmp)); + if (menu_entries_append_enum(info->list, tmp, "", + MENU_ENUM_LABEL_CORE_INFO_ENTRY, MENU_SETTINGS_CORE_INFO_NONE, 0, 0)) + count++; + if (core_info->firmware_count > 0) { core_info_ctx_firmware_t firmware_info; @@ -2872,6 +2906,7 @@ static int menu_displaylist_parse_load_content_settings( #endif bool quickmenu_show_resume_content = settings->bools.quick_menu_show_resume_content; bool quickmenu_show_restart_content = settings->bools.quick_menu_show_restart_content; + bool savestates_enabled = core_info_current_supports_savestate(); rarch_system_info_t *system = &runloop_state_get_ptr()->system; if (quickmenu_show_resume_content) @@ -2920,7 +2955,8 @@ static int menu_displaylist_parse_load_content_settings( } #endif - if (settings->bools.quick_menu_show_save_load_state) + if (savestates_enabled && + settings->bools.quick_menu_show_save_load_state) { if (MENU_DISPLAYLIST_PARSE_SETTINGS_ENUM(list, MENU_ENUM_LABEL_STATE_SLOT, PARSE_ONLY_INT, true) == 0) @@ -2941,7 +2977,8 @@ static int menu_displaylist_parse_load_content_settings( count++; } - if (settings->bools.quick_menu_show_save_load_state && + if (savestates_enabled && + settings->bools.quick_menu_show_save_load_state && settings->bools.quick_menu_show_undo_save_load_state) { #ifdef HAVE_CHEEVOS diff --git a/msg_hash.h b/msg_hash.h index 2a81cb43c9..14964ebdbf 100644 --- a/msg_hash.h +++ b/msg_hash.h @@ -2594,6 +2594,12 @@ enum msg_hash_enums MENU_ENUM_LABEL_VALUE_CORE_INFO_FIRMWARE, MENU_ENUM_LABEL_VALUE_CORE_INFO_REQUIRED_HW_API, + MENU_ENUM_LABEL_VALUE_CORE_INFO_SAVESTATE_SUPPORT_LEVEL, + MENU_ENUM_LABEL_VALUE_CORE_INFO_SAVESTATE_DISABLED, + MENU_ENUM_LABEL_VALUE_CORE_INFO_SAVESTATE_BASIC, + MENU_ENUM_LABEL_VALUE_CORE_INFO_SAVESTATE_SERIALIZED, + MENU_ENUM_LABEL_VALUE_CORE_INFO_SAVESTATE_DETERMINISTIC, + /* System information */ MENU_ENUM_LABEL_VALUE_SYSTEM_INFO_LAKKA_VERSION, diff --git a/tasks/task_save.c b/tasks/task_save.c index 8871b11742..a16188c1de 100644 --- a/tasks/task_save.c +++ b/tasks/task_save.c @@ -39,7 +39,7 @@ #include