diff --git a/core_info.c b/core_info.c index bcdd1f745e..57c15aafa8 100644 --- a/core_info.c +++ b/core_info.c @@ -478,6 +478,7 @@ static void core_info_copy(core_info_t *src, core_info_t *dst) dst->database_match_archive_member = src->database_match_archive_member; dst->is_experimental = src->is_experimental; dst->is_locked = src->is_locked; + dst->is_standalone_exempt = src->is_standalone_exempt; dst->is_installed = src->is_installed; } @@ -574,6 +575,7 @@ static void core_info_transfer(core_info_t *src, core_info_t *dst) dst->database_match_archive_member = src->database_match_archive_member; dst->is_experimental = src->is_experimental; dst->is_locked = src->is_locked; + dst->is_standalone_exempt = src->is_standalone_exempt; dst->is_installed = src->is_installed; } @@ -1282,19 +1284,20 @@ typedef struct { const char *filename; uint32_t hash; -} core_lock_file_path_t; +} core_aux_file_path_t; typedef struct { - core_lock_file_path_t *list; + core_aux_file_path_t *list; size_t size; -} core_lock_file_path_list_t; +} core_aux_file_path_list_t; typedef struct { struct string_list *dir_list; core_file_path_list_t *core_list; - core_lock_file_path_list_t *lock_list; + core_aux_file_path_list_t *lock_list; + core_aux_file_path_list_t *standalone_exempt_list; } core_path_list_t; static uint32_t core_info_hash_string(const char *str) @@ -1325,6 +1328,13 @@ static void core_info_path_list_free(core_path_list_t *path_list) free(path_list->lock_list); } + if (path_list->standalone_exempt_list) + { + if (path_list->standalone_exempt_list->list) + free(path_list->standalone_exempt_list->list); + free(path_list->standalone_exempt_list); + } + if (path_list->dir_list) string_list_free(path_list->dir_list); @@ -1352,22 +1362,30 @@ static core_path_list_t *core_info_path_list_new(const char *core_dir, goto error; /* Allocate list containers */ - path_list->dir_list = string_list_new(); - path_list->core_list = (core_file_path_list_t*)calloc(1, - sizeof(*path_list->core_list)); - path_list->lock_list = (core_lock_file_path_list_t*)calloc(1, - sizeof(*path_list->lock_list)); + path_list->dir_list = string_list_new(); + path_list->core_list = (core_file_path_list_t*) + calloc(1, sizeof(*path_list->core_list)); + path_list->lock_list = (core_aux_file_path_list_t*) + calloc(1, sizeof(*path_list->lock_list)); + path_list->standalone_exempt_list = (core_aux_file_path_list_t*) + calloc(1, sizeof(*path_list->standalone_exempt_list)); if ( !path_list->dir_list || !path_list->core_list - || !path_list->lock_list) + || !path_list->lock_list + || !path_list->standalone_exempt_list) goto error; /* Get list of file extensions to include - * (core + lock file) */ - fill_pathname_join_delim(exts, - core_exts, FILE_PATH_LOCK_EXTENSION_NO_DOT, - '|', sizeof(exts)); + * > core + lock */ + strlcpy(exts, core_exts, sizeof(exts)); + strlcat(exts, "|" FILE_PATH_LOCK_EXTENSION_NO_DOT, + sizeof(exts)); +#if defined(HAVE_DYNAMIC) + /* > 'standalone exempt' */ + strlcat(exts, "|" FILE_PATH_STANDALONE_EXEMPT_EXTENSION_NO_DOT, + sizeof(exts)); +#endif /* Fetch core directory listing */ dir_list_ok = dir_list_append(path_list->dir_list, @@ -1396,15 +1414,19 @@ static core_path_list_t *core_info_path_list_new(const char *core_dir, #endif /* Allocate sub lists */ - path_list->core_list->list = (core_file_path_t*) + path_list->core_list->list = (core_file_path_t*) malloc(path_list->dir_list->size * sizeof(*path_list->core_list->list)); - path_list->lock_list->list = (core_lock_file_path_t*) + path_list->lock_list->list = (core_aux_file_path_t*) malloc(path_list->dir_list->size * sizeof(*path_list->lock_list->list)); + path_list->standalone_exempt_list->list = (core_aux_file_path_t*) + malloc(path_list->dir_list->size * + sizeof(*path_list->standalone_exempt_list->list)); if (!path_list->core_list->list || - !path_list->lock_list->list) + !path_list->lock_list->list || + !path_list->standalone_exempt_list->list) goto error; /* Parse directory listing */ @@ -1419,7 +1441,8 @@ static core_path_list_t *core_info_path_list_new(const char *core_dir, || !(file_ext = path_get_extension(filename))) continue; - /* Check whether this is a core or lock file */ + /* Check whether this is a core, lock or + * 'standalone exempt' file */ if (string_list_find_elem(core_ext_list, file_ext)) { path_list->core_list->list[ @@ -1436,6 +1459,16 @@ static core_path_list_t *core_info_path_list_new(const char *core_dir, path_list->lock_list->size].hash = core_info_hash_string(filename); path_list->lock_list->size++; } +#if defined(HAVE_DYNAMIC) + else if (string_is_equal(file_ext, FILE_PATH_STANDALONE_EXEMPT_EXTENSION_NO_DOT)) + { + path_list->standalone_exempt_list->list[ + path_list->standalone_exempt_list->size].filename = filename; + path_list->standalone_exempt_list->list[ + path_list->standalone_exempt_list->size].hash = core_info_hash_string(filename); + path_list->standalone_exempt_list->size++; + } +#endif } string_list_free(core_ext_list); @@ -1448,13 +1481,15 @@ error: } static bool core_info_path_is_locked( - core_lock_file_path_list_t *lock_list, + core_aux_file_path_list_t *lock_list, const char *core_file_name) { size_t i; uint32_t hash; char lock_filename[256]; + lock_filename[0] = '\0'; + if (lock_list->size < 1) return false; @@ -1465,7 +1500,7 @@ static bool core_info_path_is_locked( for (i = 0; i < lock_list->size; i++) { - core_lock_file_path_t *lock_file = &lock_list->list[i]; + core_aux_file_path_t *lock_file = &lock_list->list[i]; if ((lock_file->hash == hash) && string_is_equal(lock_file->filename, lock_filename)) @@ -1475,6 +1510,37 @@ static bool core_info_path_is_locked( return false; } +static bool core_info_path_is_standalone_exempt( + core_aux_file_path_list_t *exempt_list, + const char *core_file_name) +{ + size_t i; + uint32_t hash; + char exempt_filename[256]; + + exempt_filename[0] = '\0'; + + if (exempt_list->size < 1) + return false; + + snprintf(exempt_filename, sizeof(exempt_filename), + "%s" FILE_PATH_STANDALONE_EXEMPT_EXTENSION, + core_file_name); + + hash = core_info_hash_string(exempt_filename); + + for (i = 0; i < exempt_list->size; i++) + { + core_aux_file_path_t *exempt_file = &exempt_list->list[i]; + + if ((exempt_file->hash == hash) && + string_is_equal(exempt_file->filename, exempt_filename)) + return true; + } + + return false; +} + static bool core_info_get_file_id(const char *core_filename, char *core_file_id, size_t len) { @@ -1987,23 +2053,35 @@ static core_info_list_t *core_info_list_new(const char *path, if (info_cache) { core_info_copy(info_cache, info); + /* Core path is 'dynamic', and cannot * be cached (i.e. core directory may * change between runs) */ if (info->path) free(info->path); - info->path = strdup(base_path); + info->path = strdup(base_path); + /* Core lock status is 'dynamic', and * cannot be cached */ info->is_locked = core_info_path_is_locked( path_list->lock_list, core_filename); - + + /* Core 'standalone exempt' status is 'dynamic', + * and cannot be cached + * > It is also dependent upon whether the core + * supports contentless operation */ + info->is_standalone_exempt = info->supports_no_game && + core_info_path_is_standalone_exempt( + path_list->standalone_exempt_list, + core_filename); + /* 'info_count' is normally incremented inside * core_info_parse_config_file(). If core entry * is cached, must instead increment the value * here */ if (info->has_info) core_info_list->info_count++; + continue; } } @@ -2032,7 +2110,13 @@ static core_info_list_t *core_info_list_new(const char *path, if (!info->display_name) info->display_name = strdup(core_filename); - info->is_installed = true; + /* Get core 'standalone exempt' status */ + info->is_standalone_exempt = info->supports_no_game && + core_info_path_is_standalone_exempt( + path_list->standalone_exempt_list, + core_filename); + + info->is_installed = true; /* If info cache is enabled and we reach this * point, current core is uncached @@ -2205,6 +2289,8 @@ bool core_info_init_current_core(void) current->database_match_archive_member = false; current->is_experimental = false; current->is_locked = false; + current->is_standalone_exempt = false; + current->is_installed = false; current->firmware_count = 0; current->savestate_support_level = CORE_INFO_SAVESTATE_DETERMINISTIC; current->path = NULL; @@ -3087,6 +3173,43 @@ bool core_info_current_supports_runahead(void) CORE_INFO_SAVESTATE_DETERMINISTIC; } +static bool core_info_update_core_aux_file(const char *path, bool create) +{ + bool aux_file_exists = false; + + if (string_is_empty(path)) + return false; + + /* Check whether aux file exists */ + aux_file_exists = path_is_valid(path); + + /* Create or delete aux file, as required */ + if (create && !aux_file_exists) + { + RFILE *aux_file = filestream_open(path, + RETRO_VFS_FILE_ACCESS_WRITE, + RETRO_VFS_FILE_ACCESS_HINT_NONE); + + if (!aux_file) + return false; + + /* We have to write something - just output + * a single character */ + if (filestream_putc(aux_file, 0) != 0) + { + filestream_close(aux_file); + return false; + } + + filestream_close(aux_file); + } + else if (!create && aux_file_exists) + if (filestream_delete(path) != 0) + return false; + + return true; +} + /* Sets 'locked' status of specified core * > Returns true if successful * > Like all functions that access the cached @@ -3094,9 +3217,10 @@ bool core_info_current_supports_runahead(void) bool core_info_set_core_lock(const char *core_path, bool lock) { core_info_t *core_info = NULL; - bool lock_file_exists = false; char lock_file_path[PATH_MAX_LENGTH]; + lock_file_path[0] = '\0'; + #if defined(ANDROID) /* Play Store builds do not support * core locking */ @@ -3104,47 +3228,19 @@ bool core_info_set_core_lock(const char *core_path, bool lock) return false; #endif - if (string_is_empty(core_path)) - return false; - /* Search for specified core */ - if (!core_info_find(core_path, &core_info)) - return false; - - if (string_is_empty(core_info->path)) + if (string_is_empty(core_path) || + !core_info_find(core_path, &core_info) || + string_is_empty(core_info->path)) return false; /* Get lock file path */ snprintf(lock_file_path, sizeof(lock_file_path), "%s" FILE_PATH_LOCK_EXTENSION, core_info->path); - /* Check whether lock file exists */ - lock_file_exists = path_is_valid(lock_file_path); - /* Create or delete lock file, as required */ - if (lock && !lock_file_exists) - { - RFILE *lock_file = filestream_open( - lock_file_path, - RETRO_VFS_FILE_ACCESS_WRITE, - RETRO_VFS_FILE_ACCESS_HINT_NONE); - - if (!lock_file) - return false; - - /* We have to write something - just output - * a single character */ - if (filestream_putc(lock_file, 0) != 0) - { - filestream_close(lock_file); - return false; - } - - filestream_close(lock_file); - } - else if (!lock && lock_file_exists) - if (filestream_delete(lock_file_path) != 0) - return false; + if (!core_info_update_core_aux_file(lock_file_path, lock)) + return false; /* File operations were successful - update * core info entry */ @@ -3169,6 +3265,8 @@ bool core_info_get_core_lock(const char *core_path, bool validate_path) bool is_locked = false; char lock_file_path[PATH_MAX_LENGTH]; + lock_file_path[0] = '\0'; + #if defined(ANDROID) /* Play Store builds do not support * core locking */ @@ -3209,3 +3307,88 @@ bool core_info_get_core_lock(const char *core_path, bool validate_path) return is_locked; } + +/* Sets 'standalone exempt' status of specified core + * > A 'standalone exempt' core will not be shown + * in the contentless cores menu when display type + * is set to 'custom' + * > Returns true if successful + * > Returns false if core does not support + * contentless operation + * > *Not* thread safe */ +bool core_info_set_core_standalone_exempt(const char *core_path, bool exempt) +{ +#if defined(HAVE_DYNAMIC) + core_info_t *core_info = NULL; + char exempt_file_path[PATH_MAX_LENGTH]; + + exempt_file_path[0] = '\0'; + + /* Search for specified core */ + if (string_is_empty(core_path) || + !core_info_find(core_path, &core_info) || + string_is_empty(core_info->path) || + !core_info->supports_no_game) + return false; + + /* Get 'standalone exempt' file path */ + snprintf(exempt_file_path, sizeof(exempt_file_path), + "%s" FILE_PATH_STANDALONE_EXEMPT_EXTENSION, + core_info->path); + + /* Create or delete 'standalone exempt' file, as required */ + if (!core_info_update_core_aux_file(exempt_file_path, exempt)) + return false; + + /* File operations were successful - update + * core info entry */ + core_info->is_standalone_exempt = exempt; + + return true; +#else + /* Static platforms do not support the contentless + * cores menu */ + return false; +#endif +} + +/* Fetches 'standalone exempt' status of specified core + * > Returns true if core should be excluded from + * the contentless cores menu when display type is + * set to 'custom' + * > *Not* thread safe */ +bool core_info_get_core_standalone_exempt(const char *core_path) +{ +#if defined(HAVE_DYNAMIC) + core_info_t *core_info = NULL; + bool is_exempt = false; + char exempt_file_path[PATH_MAX_LENGTH]; + + exempt_file_path[0] = '\0'; + + /* Search for specified core */ + if (string_is_empty(core_path) || + !core_info_find(core_path, &core_info) || + string_is_empty(core_info->path) || + !core_info->supports_no_game) + return false; + + /* Get 'standalone exempt' file path */ + snprintf(exempt_file_path, sizeof(exempt_file_path), + "%s" FILE_PATH_STANDALONE_EXEMPT_EXTENSION, + core_info->path); + + /* Check whether 'standalone exempt' file exists */ + is_exempt = path_is_valid(exempt_file_path); + + /* Ensure that core info 'is_standalone_exempt' + * field is up to date */ + core_info->is_standalone_exempt = is_exempt; + + return is_exempt; +#else + /* Static platforms do not support the contentless + * cores menu */ + return false; +#endif +} diff --git a/core_info.h b/core_info.h index 3096c38e12..708a78bd00 100644 --- a/core_info.h +++ b/core_info.h @@ -108,6 +108,7 @@ typedef struct bool database_match_archive_member; bool is_experimental; bool is_locked; + bool is_standalone_exempt; bool is_installed; } core_info_t; @@ -229,6 +230,22 @@ bool core_info_set_core_lock(const char *core_path, bool lock); * must be checked externally */ bool core_info_get_core_lock(const char *core_path, bool validate_path); +/* Sets 'standalone exempt' status of specified core + * > A 'standalone exempt' core will not be shown + * in the contentless cores menu when display type + * is set to 'custom' + * > Returns true if successful + * > Returns false if core does not support + * contentless operation + * > *Not* thread safe */ +bool core_info_set_core_standalone_exempt(const char *core_path, bool exempt); +/* Fetches 'standalone exempt' status of specified core + * > Returns true if core should be excluded from + * the contentless cores menu when display type is + * set to 'custom' + * > *Not* thread safe */ +bool core_info_get_core_standalone_exempt(const char *core_path); + bool core_info_core_file_id_is_equal(const char *core_path_a, const char *core_path_b); /* When called, generates a temporary file diff --git a/file_path_special.h b/file_path_special.h index cb72bba45c..a303f7ca49 100644 --- a/file_path_special.h +++ b/file_path_special.h @@ -107,6 +107,8 @@ RETRO_BEGIN_DECLS #define FILE_PATH_CORE_BACKUP_EXTENSION_NO_DOT "lcbk" #define FILE_PATH_LOCK_EXTENSION ".lck" #define FILE_PATH_LOCK_EXTENSION_NO_DOT "lck" +#define FILE_PATH_STANDALONE_EXEMPT_EXTENSION ".lsae" +#define FILE_PATH_STANDALONE_EXEMPT_EXTENSION_NO_DOT "lsae" #define FILE_PATH_BACKUP_EXTENSION ".bak" #if defined(RARCH_MOBILE) #define FILE_PATH_DEFAULT_OVERLAY "gamepads/neo-retropad/neo-retropad.cfg" diff --git a/intl/msg_hash_lbl.h b/intl/msg_hash_lbl.h index 3a987c1524..aec58b7642 100644 --- a/intl/msg_hash_lbl.h +++ b/intl/msg_hash_lbl.h @@ -4082,6 +4082,10 @@ MSG_HASH( MENU_ENUM_LABEL_CORE_LOCK, "core_lock" ) +MSG_HASH( + MENU_ENUM_LABEL_CORE_SET_STANDALONE_EXEMPT, + "core_set_standalone_exempt" + ) MSG_HASH( MENU_ENUM_LABEL_CORE_DELETE, "core_delete" diff --git a/intl/msg_hash_us.h b/intl/msg_hash_us.h index bbe8f4d5c1..86fc4fae9b 100644 --- a/intl/msg_hash_us.h +++ b/intl/msg_hash_us.h @@ -531,6 +531,14 @@ MSG_HASH( MENU_ENUM_SUBLABEL_CORE_LOCK, "Prevent modification of the currently installed core. May be used to avoid unwanted updates when content requires a specific core version (e.g. Arcade ROM sets)." ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_CORE_SET_STANDALONE_EXEMPT, + "Exclude From 'Standalone Cores' Menu" + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_CORE_SET_STANDALONE_EXEMPT, + "Prevent this core from being displayed in the 'Standalone Cores' tab/menu. Only applies when display mode is set to 'Custom'." + ) MSG_HASH( MENU_ENUM_LABEL_VALUE_CORE_DELETE, "Delete Core" @@ -4696,7 +4704,7 @@ MSG_HASH( ) MSG_HASH( MENU_ENUM_SUBLABEL_CONTENT_SHOW_CONTENTLESS_CORES, - "Specify the type of core (if any) to show in the 'Standalone Cores' menu. (Restart Required on Ozone/XMB)" + "Specify the type of core (if any) to show in the 'Standalone Cores' menu. When set to 'Custom', individual core visibility may be toggled via the 'Manage Cores' menu. (Restart Required on Ozone/XMB)" ) MSG_HASH( MENU_ENUM_LABEL_VALUE_SHOW_CONTENTLESS_CORES_ALL, @@ -4706,6 +4714,10 @@ MSG_HASH( MENU_ENUM_LABEL_VALUE_SHOW_CONTENTLESS_CORES_SINGLE_PURPOSE, "Single-Use" ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_SHOW_CONTENTLESS_CORES_CUSTOM, + "Custom" + ) MSG_HASH( MENU_ENUM_LABEL_VALUE_TIMEDATE_ENABLE, "Show Date and Time" @@ -12592,6 +12604,14 @@ MSG_HASH( MSG_CORE_UNLOCK_FAILED, "Failed to unlock core: " ) +MSG_HASH( + MSG_CORE_SET_STANDALONE_EXEMPT_FAILED, + "Failed to remove core from 'Standalone Cores' list: " + ) +MSG_HASH( + MSG_CORE_UNSET_STANDALONE_EXEMPT_FAILED, + "Failed to add core to 'Standalone Cores' list: " + ) MSG_HASH( MSG_CORE_DELETE_DISABLED, "Core deletion disabled - core is locked: " diff --git a/menu/cbs/menu_cbs_get_value.c b/menu/cbs/menu_cbs_get_value.c index 7e411330b1..07c20f090c 100644 --- a/menu/cbs/menu_cbs_get_value.c +++ b/menu/cbs/menu_cbs_get_value.c @@ -768,6 +768,40 @@ static void menu_action_setting_disp_set_label_core_lock( *w = (unsigned)strlen(s); } +static void menu_action_setting_disp_set_label_core_set_standalone_exempt( + file_list_t* list, + unsigned *w, unsigned type, unsigned i, + const char *label, + char *s, size_t len, + const char *path, + char *s2, size_t len2) +{ + core_info_t *core_info = NULL; + const char *alt = list->list[i].alt + ? list->list[i].alt + : list->list[i].path; + *s = '\0'; + *w = 0; + + if (alt) + strlcpy(s2, alt, len2); + + /* Check whether core is excluded from the + * contentless cores menu + * > Note: We search core_info here instead of + * calling core_info_get_core_standalone_exempt() + * since we don't want to perform disk access + * every frame */ + if (core_info_find(path, &core_info) && + core_info->supports_no_game && + core_info->is_standalone_exempt) + strlcpy(s, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_ON), len); + else + strlcpy(s, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_OFF), len); + + *w = (unsigned)strlen(s); +} + static void menu_action_setting_disp_set_label_input_desc( file_list_t* list, unsigned *w, unsigned type, unsigned i, @@ -2221,6 +2255,10 @@ static int menu_cbs_init_bind_get_string_representation_compare_type( BIND_ACTION_GET_VALUE(cbs, menu_action_setting_disp_set_label_core_lock); break; + case MENU_SETTING_ACTION_CORE_SET_STANDALONE_EXEMPT: + BIND_ACTION_GET_VALUE(cbs, + menu_action_setting_disp_set_label_core_set_standalone_exempt); + break; case 32: /* Recent history entry */ case 65535: /* System info entry */ BIND_ACTION_GET_VALUE(cbs, menu_action_setting_disp_set_label_entry); diff --git a/menu/cbs/menu_cbs_left.c b/menu/cbs/menu_cbs_left.c index eb346745d3..995ba91050 100644 --- a/menu/cbs/menu_cbs_left.c +++ b/menu/cbs/menu_cbs_left.c @@ -54,6 +54,7 @@ /* Forward declarations */ int action_ok_core_lock(const char *path, const char *label, unsigned type, size_t idx, size_t entry_idx); +int action_ok_core_set_standalone_exempt(const char *path, const char *label, unsigned type, size_t idx, size_t entry_idx); extern struct key_desc key_descriptors[RARCH_MAX_KEYS]; @@ -811,6 +812,12 @@ static int action_left_core_lock(unsigned type, const char *label, return action_ok_core_lock(label, label, type, 0, 0); } +static int action_left_core_set_standalone_exempt(unsigned type, const char *label, + bool wraparound) +{ + return action_ok_core_set_standalone_exempt(label, label, type, 0, 0); +} + static int disk_options_disk_idx_left(unsigned type, const char *label, bool wraparound) { @@ -1241,6 +1248,9 @@ static int menu_cbs_init_bind_left_compare_type(menu_file_list_cbs_t *cbs, case MENU_SETTING_ACTION_CORE_LOCK: BIND_ACTION_LEFT(cbs, action_left_core_lock); break; + case MENU_SETTING_ACTION_CORE_SET_STANDALONE_EXEMPT: + BIND_ACTION_LEFT(cbs, action_left_core_set_standalone_exempt); + break; case MENU_SETTING_DROPDOWN_ITEM_INPUT_DESCRIPTION: case MENU_SETTING_DROPDOWN_ITEM_INPUT_DESCRIPTION_KBD: BIND_ACTION_LEFT(cbs, action_left_scroll); diff --git a/menu/cbs/menu_cbs_ok.c b/menu/cbs/menu_cbs_ok.c index fe34d02c7e..757c63f886 100644 --- a/menu/cbs/menu_cbs_ok.c +++ b/menu/cbs/menu_cbs_ok.c @@ -7406,6 +7406,64 @@ int action_ok_core_lock(const char *path, return ret; } +/* Do not declare this static - it is also used + * in menu_cbs_left.c and menu_cbs_right.c */ +int action_ok_core_set_standalone_exempt(const char *path, + const char *label, unsigned type, size_t idx, size_t entry_idx) +{ + const char *core_path = path; + bool exempt = false; + int ret = 0; + + if (string_is_empty(core_path)) + return -1; + + /* Simply toggle current 'exempt' status */ + exempt = !core_info_get_core_standalone_exempt(core_path); + + if (!core_info_set_core_standalone_exempt(core_path, exempt)) + { + const char *core_name = NULL; + core_info_t *core_info = NULL; + char msg[PATH_MAX_LENGTH]; + + msg[0] = '\0'; + + /* Need to fetch core name for error message */ + + /* If core is found, use display name */ + if (core_info_find(core_path, &core_info) && + core_info->display_name) + core_name = core_info->display_name; + /* If not, use core file name */ + else + core_name = path_basename_nocompression(core_path); + + /* Build error message */ + strlcpy( + msg, + msg_hash_to_str(exempt ? + MSG_CORE_SET_STANDALONE_EXEMPT_FAILED : + MSG_CORE_UNSET_STANDALONE_EXEMPT_FAILED), + sizeof(msg)); + + if (!string_is_empty(core_name)) + strlcat(msg, core_name, sizeof(msg)); + + /* Generate log + notification */ + RARCH_ERR("%s\n", msg); + + runloop_msg_queue_push( + msg, + 1, 100, true, + NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); + + ret = -1; + } + + return ret; +} + static int action_ok_core_delete(const char *path, const char *label, unsigned type, size_t idx, size_t entry_idx) { @@ -8810,6 +8868,9 @@ static int menu_cbs_init_bind_ok_compare_type(menu_file_list_cbs_t *cbs, case MENU_SETTING_ACTION_CORE_LOCK: BIND_ACTION_OK(cbs, action_ok_core_lock); break; + case MENU_SETTING_ACTION_CORE_SET_STANDALONE_EXEMPT: + BIND_ACTION_OK(cbs, action_ok_core_set_standalone_exempt); + break; case MENU_SETTING_ACTION_VIDEO_FILTER_REMOVE: BIND_ACTION_OK(cbs, action_ok_video_filter_remove); break; diff --git a/menu/cbs/menu_cbs_right.c b/menu/cbs/menu_cbs_right.c index 9da4edf83d..dc9c1d258d 100644 --- a/menu/cbs/menu_cbs_right.c +++ b/menu/cbs/menu_cbs_right.c @@ -55,6 +55,7 @@ /* Forward declarations */ int action_ok_core_lock(const char *path, const char *label, unsigned type, size_t idx, size_t entry_idx); +int action_ok_core_set_standalone_exempt(const char *path, const char *label, unsigned type, size_t idx, size_t entry_idx); extern struct key_desc key_descriptors[RARCH_MAX_KEYS]; @@ -928,6 +929,12 @@ static int action_right_core_lock(unsigned type, const char *label, return action_ok_core_lock(label, label, type, 0, 0); } +static int action_right_core_set_standalone_exempt(unsigned type, const char *label, + bool wraparound) +{ + return action_ok_core_set_standalone_exempt(label, label, type, 0, 0); +} + static int disk_options_disk_idx_right(unsigned type, const char *label, bool wraparound) { @@ -1057,6 +1064,9 @@ static int menu_cbs_init_bind_right_compare_type(menu_file_list_cbs_t *cbs, case MENU_SETTING_ACTION_CORE_LOCK: BIND_ACTION_RIGHT(cbs, action_right_core_lock); break; + case MENU_SETTING_ACTION_CORE_SET_STANDALONE_EXEMPT: + BIND_ACTION_RIGHT(cbs, action_right_core_set_standalone_exempt); + break; case MENU_SETTING_DROPDOWN_ITEM_INPUT_DESCRIPTION: case MENU_SETTING_DROPDOWN_ITEM_INPUT_DESCRIPTION_KBD: BIND_ACTION_RIGHT(cbs, action_right_scroll); diff --git a/menu/cbs/menu_cbs_start.c b/menu/cbs/menu_cbs_start.c index 3569f912f0..e6d0bdd7cb 100644 --- a/menu/cbs/menu_cbs_start.c +++ b/menu/cbs/menu_cbs_start.c @@ -637,6 +637,62 @@ static int action_start_core_lock( return ret; } +static int action_start_core_set_standalone_exempt( + const char *path, const char *label, + unsigned type, size_t idx, size_t entry_idx) +{ + const char *core_path = path; + int ret = 0; + + if (string_is_empty(core_path)) + return -1; + + /* Core should not be exempt by default + * > If it is currently 'not exempt', do nothing */ + if (!core_info_get_core_standalone_exempt(core_path)) + return ret; + + /* ...Otherwise, attempt to unset the exempt flag */ + if (!core_info_set_core_standalone_exempt(core_path, false)) + { + const char *core_name = NULL; + core_info_t *core_info = NULL; + char msg[PATH_MAX_LENGTH]; + + msg[0] = '\0'; + + /* Need to fetch core name for error message */ + + /* If core is found, use display name */ + if (core_info_find(core_path, &core_info) && + core_info->display_name) + core_name = core_info->display_name; + /* If not, use core file name */ + else + core_name = path_basename(core_path); + + /* Build error message */ + strlcpy(msg, + msg_hash_to_str(MSG_CORE_UNSET_STANDALONE_EXEMPT_FAILED), + sizeof(msg)); + + if (!string_is_empty(core_name)) + strlcat(msg, core_name, sizeof(msg)); + + /* Generate log + notification */ + RARCH_ERR("%s\n", msg); + + runloop_msg_queue_push( + msg, + 1, 100, true, + NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); + + ret = -1; + } + + return ret; +} + static int action_start_lookup_setting( const char *path, const char *label, unsigned type, size_t idx, size_t entry_idx) @@ -796,6 +852,9 @@ static int menu_cbs_init_bind_start_compare_type(menu_file_list_cbs_t *cbs, case MENU_SETTING_ACTION_CORE_LOCK: BIND_ACTION_START(cbs, action_start_core_lock); break; + case MENU_SETTING_ACTION_CORE_SET_STANDALONE_EXEMPT: + BIND_ACTION_START(cbs, action_start_core_set_standalone_exempt); + break; default: return -1; } diff --git a/menu/cbs/menu_cbs_sublabel.c b/menu/cbs/menu_cbs_sublabel.c index cc3a8b3329..bd380da1fe 100644 --- a/menu/cbs/menu_cbs_sublabel.c +++ b/menu/cbs/menu_cbs_sublabel.c @@ -1042,6 +1042,7 @@ DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_netplay_use_mitm_server, DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_netplay_mitm_server, MENU_ENUM_SUBLABEL_NETPLAY_MITM_SERVER) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_netplay_custom_mitm_server, MENU_ENUM_SUBLABEL_NETPLAY_CUSTOM_MITM_SERVER) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_core_lock, MENU_ENUM_SUBLABEL_CORE_LOCK) +DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_core_set_standalone_exempt, MENU_ENUM_SUBLABEL_CORE_SET_STANDALONE_EXEMPT) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_core_delete, MENU_ENUM_SUBLABEL_CORE_DELETE) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_pause_hardcode_mode, MENU_ENUM_SUBLABEL_ACHIEVEMENT_PAUSE) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_resume_hardcode_mode, MENU_ENUM_SUBLABEL_ACHIEVEMENT_RESUME) @@ -4439,6 +4440,9 @@ int menu_cbs_init_bind_sublabel(menu_file_list_cbs_t *cbs, case MENU_ENUM_LABEL_CORE_LOCK: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_core_lock); break; + case MENU_ENUM_LABEL_CORE_SET_STANDALONE_EXEMPT: + BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_core_set_standalone_exempt); + break; case MENU_ENUM_LABEL_CORE_DELETE: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_core_delete); break; diff --git a/menu/drivers/materialui.c b/menu/drivers/materialui.c index 6ad0fa0818..0712b452ee 100644 --- a/menu/drivers/materialui.c +++ b/menu/drivers/materialui.c @@ -10120,6 +10120,7 @@ static void materialui_list_insert( case FILE_TYPE_CORE: case MENU_SETTING_ACTION_CORE_MANAGER_OPTIONS: case MENU_SETTING_ACTION_CORE_LOCK: + case MENU_SETTING_ACTION_CORE_SET_STANDALONE_EXEMPT: case MENU_EXPLORE_TAB: case MENU_CONTENTLESS_CORES_TAB: node->icon_texture_index = MUI_TEXTURE_CORES; diff --git a/menu/drivers/ozone.c b/menu/drivers/ozone.c index 2fa22c007f..907f69d1ce 100644 --- a/menu/drivers/ozone.c +++ b/menu/drivers/ozone.c @@ -1834,6 +1834,7 @@ static uintptr_t ozone_entries_icon_get_texture(ozone_handle_t *ozone, case MENU_ENUM_LABEL_CORE_OPTIONS_FLUSH: return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_FILE]; case MENU_ENUM_LABEL_CORE_LOCK: + case MENU_ENUM_LABEL_CORE_SET_STANDALONE_EXEMPT: return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_CORE]; case MENU_ENUM_LABEL_ONSCREEN_DISPLAY_SETTINGS: return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_OSD]; diff --git a/menu/drivers/xmb.c b/menu/drivers/xmb.c index 8b9b2f09c7..a1e5093e44 100644 --- a/menu/drivers/xmb.c +++ b/menu/drivers/xmb.c @@ -2806,6 +2806,7 @@ static uintptr_t xmb_icon_get_id(xmb_handle_t *xmb, case MENU_ENUM_LABEL_CORE_OPTIONS_FLUSH: return xmb->textures.list[XMB_TEXTURE_FILE]; case MENU_ENUM_LABEL_CORE_LOCK: + case MENU_ENUM_LABEL_CORE_SET_STANDALONE_EXEMPT: return xmb->textures.list[XMB_TEXTURE_CORE]; case MENU_ENUM_LABEL_ONSCREEN_DISPLAY_SETTINGS: return xmb->textures.list[XMB_TEXTURE_OSD]; diff --git a/menu/menu_contentless_cores.c b/menu/menu_contentless_cores.c index 49634e8d9d..dc3759a39e 100644 --- a/menu/menu_contentless_cores.c +++ b/menu/menu_contentless_cores.c @@ -441,6 +441,10 @@ unsigned menu_displaylist_contentless_cores(file_list_t *list, settings_t *setti core_valid = core_info->supports_no_game && core_info->single_purpose; break; + case MENU_CONTENTLESS_CORES_DISPLAY_CUSTOM: + core_valid = core_info->supports_no_game && + !core_info->is_standalone_exempt; + break; default: break; } diff --git a/menu/menu_defines.h b/menu/menu_defines.h index ab44808b71..96403d595d 100644 --- a/menu/menu_defines.h +++ b/menu/menu_defines.h @@ -137,6 +137,7 @@ enum menu_contentless_cores_display_type MENU_CONTENTLESS_CORES_DISPLAY_NONE = 0, MENU_CONTENTLESS_CORES_DISPLAY_ALL, MENU_CONTENTLESS_CORES_DISPLAY_SINGLE_PURPOSE, + MENU_CONTENTLESS_CORES_DISPLAY_CUSTOM, MENU_CONTENTLESS_CORES_DISPLAY_LAST }; diff --git a/menu/menu_displaylist.c b/menu/menu_displaylist.c index b44a7141bf..7b65056449 100644 --- a/menu/menu_displaylist.c +++ b/menu/menu_displaylist.c @@ -473,6 +473,11 @@ static int menu_displaylist_parse_core_info(menu_displaylist_info_t *info, #if defined(HAVE_NETWORKING) && defined(HAVE_ONLINE_UPDATER) bool menu_show_core_updater = settings->bools.menu_show_core_updater; #endif +#endif +#if defined(HAVE_DYNAMIC) + enum menu_contentless_cores_display_type + contentless_display_type = (enum menu_contentless_cores_display_type) + settings->uints.menu_content_show_contentless_cores; #endif tmp[0] = '\0'; @@ -730,6 +735,34 @@ static int menu_displaylist_parse_core_info(menu_displaylist_info_t *info, end: +#if defined(HAVE_DYNAMIC) + /* Exclude core from contentless cores menu */ + if ((contentless_display_type == + MENU_CONTENTLESS_CORES_DISPLAY_CUSTOM) && + core_info && + core_info->supports_no_game && + !string_is_empty(core_path) && + !kiosk_mode_enable) + { + /* Note: Have to set core_path as both the + * 'path' and 'label' parameters (otherwise + * cannot access it in menu_cbs_get_value.c + * or menu_cbs_left/right.c), which means + * entry name must be set as 'alt' text */ + if (menu_entries_append_enum(info->list, + core_path, + core_path, + MENU_ENUM_LABEL_CORE_SET_STANDALONE_EXEMPT, + MENU_SETTING_ACTION_CORE_SET_STANDALONE_EXEMPT, 0, 0)) + { + file_list_set_alt_at_offset( + info->list, count, + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_CORE_SET_STANDALONE_EXEMPT)); + count++; + } + } +#endif + #if !(defined(__WINRT__) || defined(WINAPI_FAMILY) && WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP) if (!string_is_empty(core_path) && !kiosk_mode_enable) { diff --git a/menu/menu_driver.h b/menu/menu_driver.h index 58e7a72660..06691bd344 100644 --- a/menu/menu_driver.h +++ b/menu/menu_driver.h @@ -144,6 +144,7 @@ enum menu_settings_type MENU_SETTING_ACTION_DELETE_ENTRY, MENU_SETTING_ACTION_RESET, MENU_SETTING_ACTION_CORE_LOCK, + MENU_SETTING_ACTION_CORE_SET_STANDALONE_EXEMPT, MENU_SETTING_ACTION_CORE_DELETE, MENU_SETTING_ACTION_FAVORITES_DIR, /* "Start Directory" */ MENU_SETTING_STRING_OPTIONS, diff --git a/menu/menu_setting.c b/menu/menu_setting.c index 8e81f0d1af..10458eee75 100644 --- a/menu/menu_setting.c +++ b/menu/menu_setting.c @@ -3701,6 +3701,12 @@ static void setting_get_string_representation_uint_menu_contentless_cores_displa MENU_ENUM_LABEL_VALUE_SHOW_CONTENTLESS_CORES_SINGLE_PURPOSE), len); break; + case MENU_CONTENTLESS_CORES_DISPLAY_CUSTOM: + strlcpy(s, + msg_hash_to_str( + MENU_ENUM_LABEL_VALUE_SHOW_CONTENTLESS_CORES_CUSTOM), + len); + break; } } diff --git a/msg_hash.h b/msg_hash.h index 18bdafb36f..19ebfcc27f 100644 --- a/msg_hash.h +++ b/msg_hash.h @@ -1234,6 +1234,7 @@ enum msg_hash_enums MENU_LABEL(CONTENT_SHOW_CONTENTLESS_CORES), MENU_ENUM_LABEL_VALUE_SHOW_CONTENTLESS_CORES_ALL, MENU_ENUM_LABEL_VALUE_SHOW_CONTENTLESS_CORES_SINGLE_PURPOSE, + MENU_ENUM_LABEL_VALUE_SHOW_CONTENTLESS_CORES_CUSTOM, MENU_LABEL(XMB_RIBBON_ENABLE), MENU_LABEL(THUMBNAILS), MENU_LABEL(THUMBNAILS_RGUI), @@ -2319,10 +2320,13 @@ enum msg_hash_enums MENU_LABEL(CORE_INFORMATION), MENU_LABEL(DISC_INFORMATION), MENU_LABEL(CORE_LOCK), + MENU_LABEL(CORE_SET_STANDALONE_EXEMPT), MENU_LABEL(CORE_DELETE), MSG_CORE_LOCK_FAILED, MSG_CORE_UNLOCK_FAILED, + MSG_CORE_SET_STANDALONE_EXEMPT_FAILED, + MSG_CORE_UNSET_STANDALONE_EXEMPT_FAILED, MSG_CORE_DELETE_DISABLED, /* Core updater */