diff --git a/menu/cbs/menu_cbs_cancel.c b/menu/cbs/menu_cbs_cancel.c index 8d253cf919..a240dd5355 100644 --- a/menu/cbs/menu_cbs_cancel.c +++ b/menu/cbs/menu_cbs_cancel.c @@ -96,6 +96,7 @@ static int action_cancel_contentless_core(const char *path, const char *label, unsigned type, size_t idx) { menu_state_get_ptr()->contentless_core_ptr = 0; + menu_contentless_cores_flush_runtime(); return action_cancel_pop_default(path, label, type, idx) ; } diff --git a/menu/cbs/menu_cbs_sublabel.c b/menu/cbs/menu_cbs_sublabel.c index 5e2a787f4a..7f9762ca52 100644 --- a/menu/cbs/menu_cbs_sublabel.c +++ b/menu/cbs/menu_cbs_sublabel.c @@ -95,6 +95,111 @@ static int menu_action_sublabel_file_browser_core(file_list_t *list, unsigned ty return 1; } +static int menu_action_sublabel_contentless_core(file_list_t *list, + unsigned type, unsigned i, const char *label, const char *path, char *s, size_t len) +{ + const char *core_path = path; + core_info_t *core_info = NULL; + const contentless_core_info_entry_t *entry = NULL; + const char *menu_ident = menu_driver_ident(); + bool display_licenses = true; + bool display_runtime = true; + settings_t *settings = config_get_ptr(); + bool playlist_show_sublabels = settings->bools.playlist_show_sublabels; + unsigned playlist_sublabel_runtime_type = settings->uints.playlist_sublabel_runtime_type; + bool content_runtime_log = settings->bools.content_runtime_log; + bool content_runtime_log_aggregate = settings->bools.content_runtime_log_aggregate; + const char *directory_runtime_log = settings->paths.directory_runtime_log; + const char *directory_playlist = settings->paths.directory_playlist; + enum playlist_sublabel_last_played_style_type + playlist_sublabel_last_played_style = + (enum playlist_sublabel_last_played_style_type) + settings->uints.playlist_sublabel_last_played_style; + enum playlist_sublabel_last_played_date_separator_type + menu_timedate_date_separator = + (enum playlist_sublabel_last_played_date_separator_type) + settings->uints.menu_timedate_date_separator; + + if (!playlist_show_sublabels) + return 0; + + /* Search for specified core */ + if (!core_info_find(core_path, &core_info) || + !core_info->supports_no_game) + return 1; + + /* Get corresponding contentless core info entry */ + menu_contentless_cores_get_info(core_info->core_file_id.str, + &entry); + + if (!entry) + return 1; + + /* Determine which info we need to display */ + + /* > Runtime info is always omitted when using Ozone + * > Check if required runtime log is enabled */ + if (((playlist_sublabel_runtime_type == PLAYLIST_RUNTIME_PER_CORE) && + !content_runtime_log) || + ((playlist_sublabel_runtime_type == PLAYLIST_RUNTIME_AGGREGATE) && + !content_runtime_log_aggregate) || + string_is_equal(menu_ident, "ozone")) + display_runtime = false; + + /* > License info is always displayed unless + * we are using GLUI with runtime info enabled */ + if (display_runtime && string_is_equal(menu_ident, "glui")) + display_licenses = false; + + if (display_licenses) + strlcpy(s, entry->licenses_str, len); + + if (display_runtime) + { + /* Check whether runtime info should be loaded + * from log file */ + if (entry->runtime.status == CONTENTLESS_CORE_RUNTIME_UNKNOWN) + runtime_update_contentless_core( + core_path, + directory_runtime_log, + directory_playlist, + (playlist_sublabel_runtime_type == PLAYLIST_RUNTIME_PER_CORE), + playlist_sublabel_last_played_style, + menu_timedate_date_separator); + + /* Check whether runtime info is valid */ + if (entry->runtime.status == CONTENTLESS_CORE_RUNTIME_VALID) + { + size_t n = 0; + char tmp[64]; + + tmp[0] = '\0'; + + if (display_licenses) + { + tmp[0 ] = '\n'; + tmp[1 ] = '\0'; + } + n = strlcat(tmp, entry->runtime.runtime_str, sizeof(tmp)); + + if (n < 64 - 1) + { + tmp[n ] = '\n'; + tmp[n+1] = '\0'; + n = strlcat(tmp, entry->runtime.last_played_str, sizeof(tmp)); + } + + if (n >= 64) + n = 0; /* Silence GCC warnings... */ + (void)n; + if (!string_is_empty(tmp)) + strlcat(s, tmp, len); + } + } + + return 0; +} + #ifdef HAVE_CHEEVOS static int menu_action_sublabel_achievement_pause_menu(file_list_t* list, unsigned type, unsigned i, const char* label, const char* path, char* s, size_t len) @@ -1551,17 +1656,18 @@ static int action_bind_sublabel_playlist_entry( size_t n = 0; char tmp[64]; - tmp[0 ] = '\n'; - tmp[1 ] = '\0'; - - n = strlcat(tmp, entry->runtime_str, sizeof(tmp)); - - tmp[n ] = '\n'; - tmp[n+1] = '\0'; - /* Runtime/last played strings are now cached in the * playlist, so we can add both in one go */ - n = strlcat(tmp, entry->last_played_str, sizeof(tmp)); + tmp[0 ] = '\n'; + tmp[1 ] = '\0'; + n = strlcat(tmp, entry->runtime_str, sizeof(tmp)); + + if (n < 64 - 1) + { + tmp[n ] = '\n'; + tmp[n+1] = '\0'; + n = strlcat(tmp, entry->last_played_str, sizeof(tmp)); + } if (n >= 64) n = 0; /* Silence GCC warnings... */ @@ -1970,9 +2076,11 @@ int menu_cbs_init_bind_sublabel(menu_file_list_cbs_t *cbs, { case MENU_ENUM_LABEL_FILE_BROWSER_CORE: case MENU_ENUM_LABEL_CORE_MANAGER_ENTRY: - case MENU_ENUM_LABEL_CONTENTLESS_CORE: BIND_ACTION_SUBLABEL(cbs, menu_action_sublabel_file_browser_core); break; + case MENU_ENUM_LABEL_CONTENTLESS_CORE: + BIND_ACTION_SUBLABEL(cbs, menu_action_sublabel_contentless_core); + break; #ifdef HAVE_NETWORKING case MENU_ENUM_LABEL_CORE_UPDATER_ENTRY: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_core_updater_entry); diff --git a/menu/menu_contentless_cores.c b/menu/menu_contentless_cores.c index b170ac2933..1b3aef028c 100644 --- a/menu/menu_contentless_cores.c +++ b/menu/menu_contentless_cores.c @@ -1,5 +1,5 @@ /* RetroArch - A frontend for libretro. - * Copyright (C) 2011-2020 - Daniel De Matteis + * Copyright (C) 2011-2022 - Daniel De Matteis * Copyright (C) 2019-2022 - James Leaver * * RetroArch is free software: you can redistribute it and/or modify it under the terms @@ -37,12 +37,197 @@ typedef struct typedef struct { + contentless_core_info_entry_t **info_entries; contentless_core_icons_t *icons; bool icons_enabled; } contentless_cores_state_t; static contentless_cores_state_t *contentless_cores_state = NULL; +static void contentless_cores_free_runtime_info( + contentless_core_runtime_info_t *runtime_info) +{ + if (!runtime_info) + return; + + if (runtime_info->runtime_str) + { + free(runtime_info->runtime_str); + runtime_info->runtime_str = NULL; + } + + if (runtime_info->last_played_str) + { + free(runtime_info->last_played_str); + runtime_info->last_played_str = NULL; + } + + runtime_info->status = CONTENTLESS_CORE_RUNTIME_UNKNOWN; +} + +static void contentless_cores_free_info_entries( + contentless_cores_state_t *state) +{ + size_t i, cap; + + if (!state || !state->info_entries) + return; + + for (i = 0, cap = RHMAP_CAP(state->info_entries); i != cap; i++) + { + if (RHMAP_KEY(state->info_entries, i)) + { + contentless_core_info_entry_t *entry = state->info_entries[i]; + + if (!entry) + continue; + + if (entry->licenses_str) + free(entry->licenses_str); + + contentless_cores_free_runtime_info(&entry->runtime); + + free(entry); + } + } + + RHMAP_FREE(state->info_entries); +} + +static void contentless_cores_init_info_entries( + contentless_cores_state_t *state) +{ + core_info_list_t *core_info_list = NULL; + size_t i; + + if (!state) + return; + + /* Free any existing entries */ + contentless_cores_free_info_entries(state); + + /* Create an entry for each contentless core */ + core_info_get_list(&core_info_list); + + if (!core_info_list) + return; + + for (i = 0; i < core_info_list->count; i++) + { + core_info_t *core_info = core_info_get(core_info_list, i); + + if (core_info && + core_info->supports_no_game) + { + contentless_core_info_entry_t *entry = + (contentless_core_info_entry_t*)malloc(sizeof(*entry)); + char licenses_str[MENU_SUBLABEL_MAX_LENGTH]; + + licenses_str[0] = '\0'; + + /* Populate licences string */ + if (core_info->licenses_list) + { + char tmp_str[MENU_SUBLABEL_MAX_LENGTH]; + + tmp_str[0] = '\0'; + + string_list_join_concat(tmp_str, sizeof(tmp_str), + core_info->licenses_list, ", "); + snprintf(licenses_str, sizeof(licenses_str), "%s: %s", + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_CORE_INFO_LICENSES), + tmp_str); + } + /* No license found - set to N/A */ + else + snprintf(licenses_str, sizeof(licenses_str), "%s: %s", + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_CORE_INFO_LICENSES), + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NOT_AVAILABLE)); + + entry->licenses_str = strdup(licenses_str); + + /* Initialise runtime info */ + entry->runtime.runtime_str = NULL; + entry->runtime.last_played_str = NULL; + entry->runtime.status = CONTENTLESS_CORE_RUNTIME_UNKNOWN; + + /* Add entry to hash map */ + RHMAP_SET_STR(state->info_entries, core_info->core_file_id.str, entry); + } + } +} + +void menu_contentless_cores_set_runtime(const char *core_id, + const contentless_core_runtime_info_t *runtime_info) +{ + contentless_core_info_entry_t *info_entry = NULL; + + if (!contentless_cores_state || + !contentless_cores_state->info_entries || + !runtime_info || + string_is_empty(core_id)) + return; + + info_entry = RHMAP_GET_STR(contentless_cores_state->info_entries, core_id); + + if (!info_entry) + return; + + if (!string_is_empty(runtime_info->runtime_str)) + { + if (info_entry->runtime.runtime_str) + free(info_entry->runtime.runtime_str); + + info_entry->runtime.runtime_str = strdup(runtime_info->runtime_str); + } + + if (!string_is_empty(runtime_info->last_played_str)) + { + if (info_entry->runtime.last_played_str) + free(info_entry->runtime.last_played_str); + + info_entry->runtime.last_played_str = strdup(runtime_info->last_played_str); + } + + info_entry->runtime.status = runtime_info->status; +} + +void menu_contentless_cores_get_info(const char *core_id, + const contentless_core_info_entry_t **info) +{ + if (!info) + return; + + if (!contentless_cores_state || + !contentless_cores_state->info_entries || + string_is_empty(core_id)) + *info = NULL; + + *info = RHMAP_GET_STR(contentless_cores_state->info_entries, core_id); +} + +void menu_contentless_cores_flush_runtime(void) +{ + contentless_cores_state_t *state = contentless_cores_state; + size_t i, cap; + + if (!state || !state->info_entries) + return; + + for (i = 0, cap = RHMAP_CAP(state->info_entries); i != cap; i++) + { + if (RHMAP_KEY(state->info_entries, i)) + { + contentless_core_info_entry_t *entry = state->info_entries[i]; + + if (!entry) + continue; + + contentless_cores_free_runtime_info(&entry->runtime); + } + } +} + static void contentless_cores_unload_icons(contentless_cores_state_t *state) { size_t i, cap; @@ -213,6 +398,7 @@ void menu_contentless_cores_free(void) if (!contentless_cores_state) return; + contentless_cores_free_info_entries(contentless_cores_state); contentless_cores_unload_icons(contentless_cores_state); free(contentless_cores_state); contentless_cores_state = NULL; @@ -276,7 +462,7 @@ unsigned menu_displaylist_contentless_cores(file_list_t *list, settings_t *setti } } - /* Initialise icons, if required */ + /* Initialise global state, if required */ if (!contentless_cores_state && (count > 0)) { contentless_cores_state = (contentless_cores_state_t*)calloc(1, @@ -287,6 +473,7 @@ unsigned menu_displaylist_contentless_cores(file_list_t *list, settings_t *setti contentless_cores_state->icons_enabled = !string_is_equal(menu_driver_ident(), "rgui"); + contentless_cores_init_info_entries(contentless_cores_state); contentless_cores_load_icons(contentless_cores_state); } diff --git a/menu/menu_displaylist.c b/menu/menu_displaylist.c index 30c5ed56ce..02c958fa09 100644 --- a/menu/menu_displaylist.c +++ b/menu/menu_displaylist.c @@ -4186,26 +4186,53 @@ static unsigned menu_displaylist_parse_content_information( const char *content_path = NULL; const char *core_path = NULL; const char *db_name = NULL; + bool playlist_origin = true; bool playlist_valid = false; + const char *origin_label = NULL; + struct menu_state *menu_st = menu_state_get_ptr(); + file_list_t *list = NULL; unsigned count = 0; bool content_loaded = !retroarch_ctl(RARCH_CTL_IS_DUMMY_CORE, NULL) + && !string_is_empty(loaded_content_path) && string_is_equal(menu->deferred_path, loaded_content_path); + bool core_supports_no_game = false; core_name[0] = '\0'; - /* If content is currently running, have to make sure - * we have a valid playlist to work with - * (if content is not running, then playlist will always - * be valid provided that playlist_get_cached() does not - * return NULL) */ - if (content_loaded) + /* Check the origin menu from which the information + * entry was selected + * > Can only assume a valid playlist if the origin + * was an actual playlist - i.e. cached playlist is + * dubious if information was selected from + * 'Main Menu > Quick Menu' or 'Standalone Cores > + * Quick Menu' */ + if (menu_st->entries.list) + list = MENU_LIST_GET(menu_st->entries.list, 0); + if (list && (list->size > 2)) { - if (!string_is_empty(loaded_content_path) && !string_is_empty(loaded_core_path)) + file_list_get_at_offset(list, list->size - 3, NULL, + &origin_label, NULL, NULL); + + if (string_is_equal(origin_label, msg_hash_to_str(MENU_ENUM_LABEL_MAIN_MENU)) || + string_is_equal(origin_label, msg_hash_to_str(MENU_ENUM_LABEL_CONTENTLESS_CORES_TAB)) || + string_is_equal(origin_label, msg_hash_to_str(MENU_ENUM_LABEL_DEFERRED_CONTENTLESS_CORES_LIST))) + playlist_origin = false; + } + + /* If origin menu was a playlist, may rely on + * return value from playlist_get_cached() */ + if (playlist_origin) + playlist_valid = !!playlist; + else + { + /* If origin menu was not a playlist, then + * check currently loaded content against + * last cached playlist */ + if (content_loaded && + !string_is_empty(loaded_core_path)) playlist_valid = playlist_index_is_valid( playlist, idx, loaded_content_path, loaded_core_path); } - else if (playlist) - playlist_valid = true; if (playlist_valid) { @@ -4236,39 +4263,48 @@ static unsigned menu_displaylist_parse_content_information( core_path = loaded_core_path; if (core_info_find(core_path, &core_info)) + { + core_supports_no_game = core_info->supports_no_game; + if (!string_is_empty(core_info->display_name)) strlcpy(core_name, core_info->display_name, sizeof(core_name)); + } } - /* Content label */ - tmp[0] = '\0'; - snprintf(tmp, sizeof(tmp), - "%s: %s", - msg_hash_to_str(MENU_ENUM_LABEL_VALUE_CONTENT_INFO_LABEL), - !string_is_empty(content_label) - ? content_label - : msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NOT_AVAILABLE) - ); - if (menu_entries_append_enum(info->list, tmp, - msg_hash_to_str(MENU_ENUM_LABEL_CONTENT_INFO_LABEL), - MENU_ENUM_LABEL_CONTENT_INFO_LABEL, - 0, 0, 0)) - count++; + /* If content path is empty and core supports + * contentless operation, skip label/path entries */ + if (!(core_supports_no_game && string_is_empty(content_path))) + { + /* Content label */ + tmp[0] = '\0'; + snprintf(tmp, sizeof(tmp), + "%s: %s", + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_CONTENT_INFO_LABEL), + !string_is_empty(content_label) + ? content_label + : msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NOT_AVAILABLE) + ); + if (menu_entries_append_enum(info->list, tmp, + msg_hash_to_str(MENU_ENUM_LABEL_CONTENT_INFO_LABEL), + MENU_ENUM_LABEL_CONTENT_INFO_LABEL, + 0, 0, 0)) + count++; - /* Content path */ - tmp[0] = '\0'; - snprintf(tmp, sizeof(tmp), - "%s: %s", - msg_hash_to_str(MENU_ENUM_LABEL_VALUE_CONTENT_INFO_PATH), - !string_is_empty(content_path) - ? content_path - : msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NOT_AVAILABLE) - ); - if (menu_entries_append_enum(info->list, tmp, - msg_hash_to_str(MENU_ENUM_LABEL_CONTENT_INFO_PATH), - MENU_ENUM_LABEL_CONTENT_INFO_PATH, - 0, 0, 0)) - count++; + /* Content path */ + tmp[0] = '\0'; + snprintf(tmp, sizeof(tmp), + "%s: %s", + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_CONTENT_INFO_PATH), + !string_is_empty(content_path) + ? content_path + : msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NOT_AVAILABLE) + ); + if (menu_entries_append_enum(info->list, tmp, + msg_hash_to_str(MENU_ENUM_LABEL_CONTENT_INFO_PATH), + MENU_ENUM_LABEL_CONTENT_INFO_PATH, + 0, 0, 0)) + count++; + } /* Core name */ if (!string_is_empty(core_name) && diff --git a/menu/menu_driver.c b/menu/menu_driver.c index 9b3b573606..a06d74ce9e 100644 --- a/menu/menu_driver.c +++ b/menu/menu_driver.c @@ -1309,6 +1309,7 @@ void menu_list_flush_stack( file_list_t *menu_list = MENU_LIST_GET(list, (unsigned)idx); menu_entries_ctl(MENU_ENTRIES_CTL_SET_REFRESH, &refresh); + menu_contentless_cores_flush_runtime(); if (menu_list && menu_list->size) file_list_get_at_offset(menu_list, menu_list->size - 1, &path, &label, &type, &entry_idx); @@ -4162,6 +4163,8 @@ int menu_driver_deferred_push_content_list(file_list_t *list) menu_st->selection_ptr = 0; menu_st->contentless_core_ptr = 0; + menu_contentless_cores_flush_runtime(); + if (!menu_driver_displaylist_push( menu_st, settings, @@ -5269,9 +5272,8 @@ bool menu_driver_init(bool video_is_threaded) const char *menu_driver_ident(void) { struct menu_state *menu_st = &menu_driver_state; - if (menu_st->alive) - if (menu_st->driver_ctx && menu_st->driver_ctx->ident) - return menu_st->driver_ctx->ident; + if (menu_st->driver_ctx && menu_st->driver_ctx->ident) + return menu_st->driver_ctx->ident; return NULL; } @@ -7031,6 +7033,8 @@ bool menu_driver_ctl(enum rarch_menu_ctl_state state, void *data) menu_st->contentless_core_ptr = 0; menu_st->scroll.index_size = 0; + menu_contentless_cores_flush_runtime(); + for (i = 0; i < SCROLL_INDEX_SIZE; i++) menu_st->scroll.index_list[i] = 0; diff --git a/menu/menu_driver.h b/menu/menu_driver.h index bdcef9e4ce..758aba22bf 100644 --- a/menu/menu_driver.h +++ b/menu/menu_driver.h @@ -639,10 +639,37 @@ void menu_explore_free(void); void menu_explore_set_state(explore_state_t *state); #endif +/* Contentless cores START */ +enum contentless_core_runtime_status +{ + CONTENTLESS_CORE_RUNTIME_UNKNOWN = 0, + CONTENTLESS_CORE_RUNTIME_MISSING, + CONTENTLESS_CORE_RUNTIME_VALID +}; + +typedef struct +{ + char *runtime_str; + char *last_played_str; + enum contentless_core_runtime_status status; +} contentless_core_runtime_info_t; + +typedef struct +{ + char *licenses_str; + contentless_core_runtime_info_t runtime; +} contentless_core_info_entry_t; + uintptr_t menu_contentless_cores_get_entry_icon(const char *core_id); void menu_contentless_cores_context_init(void); void menu_contentless_cores_context_deinit(void); void menu_contentless_cores_free(void); +void menu_contentless_cores_set_runtime(const char *core_id, + const contentless_core_runtime_info_t *runtime_info); +void menu_contentless_cores_get_info(const char *core_id, + const contentless_core_info_entry_t **info); +void menu_contentless_cores_flush_runtime(void); +/* Contentless cores END */ /* Returns true if search filter is enabled * for the specified menu list */ diff --git a/runtime_file.c b/runtime_file.c index f88ebe09f7..16125bc7e3 100644 --- a/runtime_file.c +++ b/runtime_file.c @@ -225,7 +225,9 @@ end: /* Initialise runtime log, loading current parameters * if log file exists. Returned object must be free()'d. - * Returns NULL if content_path and/or core_path are invalid */ + * Returns NULL if core_path is invalid, or content_path + * is invalid and core does not support contentless + * operation */ runtime_log_t *runtime_log_init( const char *content_path, const char *core_path, @@ -238,6 +240,7 @@ runtime_log_t *runtime_log_init( char log_file_dir[PATH_MAX_LENGTH]; char log_file_path[PATH_MAX_LENGTH]; char tmp_buf[PATH_MAX_LENGTH]; + bool supports_no_game = false; core_info_t *core_info = NULL; runtime_log_t *runtime_log = NULL; @@ -257,18 +260,23 @@ runtime_log_t *runtime_log_init( if ( string_is_empty(core_path) || string_is_equal(core_path, "builtin") || - string_is_equal(core_path, "DETECT") || - string_is_empty(content_path)) + string_is_equal(core_path, "DETECT")) return NULL; - /* Get core name - * Note: An annoyance - this is required even when - * we are performing aggregate (not per core) logging, - * since content name is sometimes dependent upon core + /* Get core info: + * - Need to know if core supports contentless operation + * - Need core name in order to generate file path when + * per-core logging is enabled + * Note: An annoyance - core name is required even when + * we are performing aggregate logging, since content + * name is sometimes dependent upon core * (e.g. see TyrQuake below) */ - if (core_info_find(core_path, &core_info) && - core_info->core_name) - strlcpy(core_name, core_info->core_name, sizeof(core_name)); + if (core_info_find(core_path, &core_info)) + { + supports_no_game = core_info->supports_no_game; + if (!string_is_empty(core_info->core_name)) + strlcpy(core_name, core_info->core_name, sizeof(core_name)); + } if (string_is_empty(core_name)) return NULL; @@ -313,10 +321,18 @@ runtime_log_t *runtime_log_init( } } - /* Get content name - * NOTE: TyrQuake requires a specific hack, since all + /* Get content name */ + if (string_is_empty(content_path)) + { + /* If core supports contentless operation and + * no content is provided, 'content' is simply + * the name of the core itself */ + if (supports_no_game) + strlcpy(content_name, core_name, sizeof(content_name)); + } + /* NOTE: TyrQuake requires a specific hack, since all * content has the same name... */ - if (string_is_equal(core_name, "TyrQuake")) + else if (string_is_equal(core_name, "TyrQuake")) { const char *last_slash = find_last_slash(content_path); if (last_slash) @@ -1356,3 +1372,98 @@ void runtime_update_playlist( /* Update playlist */ playlist_update_runtime(playlist, idx, &update_entry, false); } + +#if defined(HAVE_MENU) +/* Contentless cores manipulation */ + +/* Updates specified contentless core runtime values with + * contents of associated log file */ +void runtime_update_contentless_core( + const char *core_path, + const char *dir_runtime_log, + const char *dir_playlist, + bool log_per_core, + enum playlist_sublabel_last_played_style_type timedate_style, + enum playlist_sublabel_last_played_date_separator_type date_separator) +{ + char runtime_str[64]; + char last_played_str[64]; + core_info_t *core_info = NULL; + runtime_log_t *runtime_log = NULL; + contentless_core_runtime_info_t runtime_info = {0}; +#if (defined(HAVE_OZONE) || defined(HAVE_MATERIALUI)) + const char *menu_ident = menu_driver_ident(); +#endif + + /* Sanity check */ + if (string_is_empty(core_path) || + !core_info_find(core_path, &core_info) || + !core_info->supports_no_game) + return; + + /* Set fallback runtime status + * (saves 'if' checks later...) */ + runtime_info.status = CONTENTLESS_CORE_RUNTIME_MISSING; + + /* 'Attach' runtime/last played strings */ + runtime_str[0] = '\0'; + last_played_str[0] = '\0'; + runtime_info.runtime_str = runtime_str; + runtime_info.last_played_str = last_played_str; + + /* Attempt to open log file */ + runtime_log = runtime_log_init( + NULL, + core_path, + dir_runtime_log, + dir_playlist, + log_per_core); + + if (runtime_log) + { + /* Check whether a non-zero runtime has been recorded */ + if (runtime_log_has_runtime(runtime_log)) + { + /* Read current runtime */ + runtime_log_get_runtime_str(runtime_log, + runtime_str, sizeof(runtime_str)); + + /* Read last played timestamp */ + runtime_log_get_last_played_str(runtime_log, + last_played_str, sizeof(last_played_str), + timedate_style, date_separator); + + /* Contentless core entry now contains valid runtime data */ + runtime_info.status = CONTENTLESS_CORE_RUNTIME_VALID; + } + + /* Clean up */ + free(runtime_log); + } + +#if (defined(HAVE_OZONE) || defined(HAVE_MATERIALUI)) + /* Ozone and GLUI require runtime/last played strings + * to be populated even when no runtime is recorded */ + if (runtime_info.status != CONTENTLESS_CORE_RUNTIME_VALID) + { + if (string_is_equal(menu_ident, "ozone") || + string_is_equal(menu_ident, "glui")) + { + runtime_log_get_runtime_str(NULL, + runtime_str, sizeof(runtime_str)); + runtime_log_get_last_played_str(NULL, + last_played_str, sizeof(last_played_str), + timedate_style, date_separator); + + /* While runtime data does not exist, the contentless + * core entry does now contain valid information... */ + runtime_info.status = CONTENTLESS_CORE_RUNTIME_VALID; + } + } +#endif + + /* Update contentless core */ + menu_contentless_cores_set_runtime(core_info->core_file_id.str, + &runtime_info); +} +#endif diff --git a/runtime_file.h b/runtime_file.h index dc7012945c..5477994e60 100644 --- a/runtime_file.h +++ b/runtime_file.h @@ -110,7 +110,9 @@ typedef struct /* Initialise runtime log, loading current parameters * if log file exists. Returned object must be free()'d. - * Returns NULL if content_path and/or core_path are invalid */ + * Returns NULL if core_path is invalid, or content_path + * is invalid and core does not support contentless + * operation */ runtime_log_t *runtime_log_init( const char *content_path, const char *core_path, @@ -205,6 +207,20 @@ void runtime_update_playlist( enum playlist_sublabel_last_played_style_type timedate_style, enum playlist_sublabel_last_played_date_separator_type date_separator); +#if defined(HAVE_MENU) +/* Contentless cores manipulation */ + +/* Updates specified contentless core runtime values with + * contents of associated log file */ +void runtime_update_contentless_core( + const char *core_path, + const char *dir_runtime_log, + const char *dir_playlist, + bool log_per_core, + enum playlist_sublabel_last_played_style_type timedate_style, + enum playlist_sublabel_last_played_date_separator_type date_separator); +#endif + RETRO_END_DECLS #endif