diff --git a/core_info.c b/core_info.c index 99390d0e7a..63a2fd167d 100644 --- a/core_info.c +++ b/core_info.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -240,6 +241,36 @@ static config_file_t *core_info_list_iterate( return conf; } +/* Returned path must be free()'d */ +static char *core_info_get_core_lock_file_path(const char *core_path) +{ + char *lock_file_path = NULL; + const char *lock_file_ext = file_path_str(FILE_PATH_LOCK_EXTENSION); + size_t len; + + if (string_is_empty(core_path)) + return NULL; + + /* Note: We follow the common 'core_info' trend of + * allocating all strings dynamically... */ + + /* Get path length */ + len = (strlen(core_path) + strlen(lock_file_ext) + 1) * sizeof(char); + + /* Allocate string */ + lock_file_path = (char*)malloc(len); + if (!lock_file_path) + return NULL; + + lock_file_path[0] = '\0'; + + /* Lock file is just core path + 'lock' extension */ + strlcpy(lock_file_path, core_path, len); + strlcat(lock_file_path, lock_file_ext, len); + + return lock_file_path; +} + static core_info_list_t *core_info_list_new(const char *path, const char *libretro_info_dir, const char *exts, @@ -523,6 +554,9 @@ static core_info_list_t *core_info_list_new(const char *path, core_info[i].display_name = strdup(core_filename); } } + + /* Get core lock status */ + core_info[i].is_locked = core_info_get_core_lock(core_info[i].path, false); } if (core_info_list) @@ -1505,3 +1539,144 @@ bool core_info_hw_api_supported(core_info_t *info) return true; #endif } + +/* Sets 'locked' status of specified core + * > Returns true if successful + * > Like all functions that access the cached + * core info list this is *not* thread safe */ +bool core_info_set_core_lock(const char *core_path, bool lock) +{ + char *lock_file_path = NULL; + RFILE *lock_file = NULL; + bool lock_file_exists = false; + core_info_ctx_find_t core_info; + + if (string_is_empty(core_path)) + goto error; + + /* Search for specified core */ + core_info.inf = NULL; + core_info.path = core_path; + + if (!core_info_find(&core_info)) + goto error; + + /* Get associated lock file path */ + lock_file_path = core_info_get_core_lock_file_path(core_info.inf->path); + + if (string_is_empty(lock_file_path)) + goto error; + + /* 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) + { + lock_file = filestream_open( + lock_file_path, + RETRO_VFS_FILE_ACCESS_WRITE, + RETRO_VFS_FILE_ACCESS_HINT_NONE); + + if (!lock_file) + goto error; + + /* We have to write something - just output + * a single character */ + if (filestream_putc(lock_file, 0) != 0) + goto error; + + filestream_close(lock_file); + lock_file = NULL; + } + else if (!lock && lock_file_exists) + if (filestream_delete(lock_file_path) != 0) + goto error; + + /* Clean up */ + free(lock_file_path); + lock_file_path = NULL; + + /* File operations were successful - update + * core info entry */ + core_info.inf->is_locked = lock; + + return true; + +error: + if (lock_file_path) + { + free(lock_file_path); + lock_file_path = NULL; + } + + if (lock_file) + { + filestream_close(lock_file); + lock_file = NULL; + } + + return false; +} + +/* Fetches 'locked' status of specified core + * > If 'validate_path' is 'true', will search + * cached core info list for a corresponding + * 'sanitised' core file path. This is *not* + * thread safe + * > If 'validate_path' is 'false', performs a + * direct filesystem check. This *is* thread + * safe, but validity of specified core path + * must be checked externally */ +bool core_info_get_core_lock(const char *core_path, bool validate_path) +{ + const char *core_file_path = NULL; + char *lock_file_path = NULL; + bool is_locked = false; + core_info_ctx_find_t core_info; + + if (string_is_empty(core_path)) + goto end; + + /* Check whether core path is to be validated */ + if (validate_path) + { + core_info.inf = NULL; + core_info.path = core_path; + + if (core_info_find(&core_info)) + core_file_path = core_info.inf->path; + } + else + core_file_path = core_path; + + /* A core cannot be locked if it does not exist... */ + if (string_is_empty(core_file_path) || + !path_is_valid(core_file_path)) + goto end; + + /* Get lock file path */ + lock_file_path = core_info_get_core_lock_file_path(core_file_path); + + if (string_is_empty(lock_file_path)) + goto end; + + /* Check whether lock file exists */ + is_locked = path_is_valid(lock_file_path); + + /* If core path has been validated (and a + * core info object is available), ensure + * that core info 'is_locked' field is + * up to date */ + if (validate_path && core_info.inf) + core_info.inf->is_locked = is_locked; + +end: + if (lock_file_path) + { + free(lock_file_path); + lock_file_path = NULL; + } + + return is_locked; +} diff --git a/core_info.h b/core_info.h index e4b81b0e13..de6e927dd2 100644 --- a/core_info.h +++ b/core_info.h @@ -57,6 +57,7 @@ typedef struct bool supports_no_game; bool database_match_archive_member; bool is_experimental; + bool is_locked; size_t firmware_count; char *path; void *config_data; @@ -201,6 +202,22 @@ bool core_info_list_get_info(core_info_list_t *core_info_list, bool core_info_hw_api_supported(core_info_t *info); +/* Sets 'locked' status of specified core + * > Returns true if successful + * > Like all functions that access the cached + * core info list this is *not* thread safe */ +bool core_info_set_core_lock(const char *core_path, bool lock); +/* Fetches 'locked' status of specified core + * > If 'validate_path' is 'true', will search + * cached core info list for a corresponding + * 'sanitised' core file path. This is *not* + * thread safe + * > If 'validate_path' is 'false', performs a + * direct filesystem check. This *is* thread + * safe, but validity of specified core path + * must be checked externally */ +bool core_info_get_core_lock(const char *core_path, bool validate_path); + core_info_state_t *coreinfo_get_ptr(void); RETRO_END_DECLS diff --git a/file_path_special.h b/file_path_special.h index 72e8fda1aa..c75a889078 100644 --- a/file_path_special.h +++ b/file_path_special.h @@ -98,7 +98,8 @@ enum file_path_enum FILE_PATH_EVENT_LOG_EXTENSION, FILE_PATH_DISK_CONTROL_INDEX_EXTENSION, FILE_PATH_CORE_BACKUP_EXTENSION, - FILE_PATH_CORE_BACKUP_EXTENSION_NO_DOT + FILE_PATH_CORE_BACKUP_EXTENSION_NO_DOT, + FILE_PATH_LOCK_EXTENSION }; enum application_special_type diff --git a/file_path_str.c b/file_path_str.c index 7e99ba7218..b594fd3dd0 100644 --- a/file_path_str.c +++ b/file_path_str.c @@ -236,6 +236,9 @@ const char *file_path_str(enum file_path_enum enum_idx) case FILE_PATH_CORE_BACKUP_EXTENSION_NO_DOT: str = "lcbk"; break; + case FILE_PATH_LOCK_EXTENSION: + str = ".lck"; + break; case FILE_PATH_UNKNOWN: default: break; diff --git a/intl/msg_hash_lbl.h b/intl/msg_hash_lbl.h index 5e01add87f..b2e48257e1 100644 --- a/intl/msg_hash_lbl.h +++ b/intl/msg_hash_lbl.h @@ -3540,6 +3540,10 @@ MSG_HASH( MENU_ENUM_LABEL_MENU_SCROLL_FAST, "menu_scroll_fast" ) +MSG_HASH( + MENU_ENUM_LABEL_CORE_LOCK, + "core_lock" + ) MSG_HASH( MENU_ENUM_LABEL_CORE_DELETE, "core_delete" diff --git a/intl/msg_hash_us.h b/intl/msg_hash_us.h index cf5d638777..6da74bfc35 100644 --- a/intl/msg_hash_us.h +++ b/intl/msg_hash_us.h @@ -449,6 +449,14 @@ MSG_HASH( MENU_ENUM_LABEL_VALUE_REQUIRED, "Required" ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_CORE_LOCK, + "Lock Installed Core" + ) +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_DELETE, "Delete Core" @@ -9726,6 +9734,14 @@ MSG_HASH( MSG_NUM_CORES_UPDATED, "cores updated: " ) +MSG_HASH( + MSG_NUM_CORES_LOCKED, + "cores skipped: " + ) +MSG_HASH( + MSG_CORE_UPDATE_DISABLED, + "Core update disabled - core is locked: " + ) MSG_HASH( MSG_PLAYLIST_MANAGER_RESETTING_CORES, "Resetting cores: " @@ -10934,6 +10950,26 @@ MSG_HASH( MSG_CORE_INSTALLATION_FAILED, "Core installation failed: " ) +MSG_HASH( + MSG_CORE_RESTORATION_DISABLED, + "Core restoration disabled - core is locked: " + ) +MSG_HASH( + MSG_CORE_INSTALLATION_DISABLED, + "Core installation disabled - core is locked: " + ) +MSG_HASH( + MSG_CORE_LOCK_FAILED, + "Failed to lock core: " + ) +MSG_HASH( + MSG_CORE_UNLOCK_FAILED, + "Failed to unlock core: " + ) +MSG_HASH( + MSG_CORE_DELETE_DISABLED, + "Core deletion disabled - core is locked: " + ) /* Lakka */ diff --git a/menu/cbs/menu_cbs_get_value.c b/menu/cbs/menu_cbs_get_value.c index 7a697bf3ea..4be9f69302 100644 --- a/menu/cbs/menu_cbs_get_value.c +++ b/menu/cbs/menu_cbs_get_value.c @@ -464,8 +464,17 @@ static void menu_action_setting_disp_set_label_core_updater_entry( if (core_info_find(&core_info)) { - strlcpy(s, "[#]", len); - *w = (unsigned)STRLEN_CONST("[#]"); + /* Highlight locked cores */ + if (core_info.inf->is_locked) + { + strlcpy(s, "[#!]", len); + *w = (unsigned)STRLEN_CONST("[#!]"); + } + else + { + strlcpy(s, "[#]", len); + *w = (unsigned)STRLEN_CONST("[#]"); + } } } } @@ -480,6 +489,7 @@ static void menu_action_setting_disp_set_label_core_manager_entry( char *s2, size_t len2) { const char *alt = NULL; + core_info_ctx_find_t core_info; *s = '\0'; *w = 0; @@ -490,10 +500,55 @@ static void menu_action_setting_disp_set_label_core_manager_entry( if (alt) strlcpy(s2, alt, len2); - /* TODO: Once core-specific 'block online updates' - * settings are implemented, the 'value' string will - * be used to indicate whether updates are enabled - * or disabled */ + /* Check whether core is locked + * > Note: We search core_info here instead of + * calling core_info_get_core_lock() since we + * don't want to perform disk access every frame */ + core_info.inf = NULL; + core_info.path = path; + + if (core_info_find(&core_info) && + core_info.inf->is_locked) + { + strlcpy(s, "[!]", len); + *w = (unsigned)STRLEN_CONST("[!]"); + } +} + +static void menu_action_setting_disp_set_label_core_lock( + 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) +{ + const char *alt = NULL; + core_info_ctx_find_t core_info; + + *s = '\0'; + *w = 0; + + menu_entries_get_at_offset(list, i, NULL, + NULL, NULL, NULL, &alt); + + if (alt) + strlcpy(s2, alt, len2); + + /* Check whether core is locked + * > Note: We search core_info here instead of + * calling core_info_get_core_lock() since we + * don't want to perform disk access every frame */ + core_info.inf = NULL; + core_info.path = path; + + if (core_info_find(&core_info) && + core_info.inf->is_locked) + 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( @@ -1622,7 +1677,7 @@ static int menu_cbs_init_bind_get_string_representation_compare_label( menu_action_setting_disp_set_label_core_manager_entry); break; default: - return - 1; + return -1; } } else @@ -1821,6 +1876,10 @@ static int menu_cbs_init_bind_get_string_representation_compare_type( case MENU_SETTING_NO_ITEM: BIND_ACTION_GET_VALUE(cbs, menu_action_setting_disp_set_label_no_items); break; + case MENU_SETTING_ACTION_CORE_LOCK: + BIND_ACTION_GET_VALUE(cbs, + menu_action_setting_disp_set_label_core_lock); + 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 33aa226d68..7da4879fad 100644 --- a/menu/cbs/menu_cbs_left.c +++ b/menu/cbs/menu_cbs_left.c @@ -46,6 +46,9 @@ #define BIND_ACTION_LEFT(cbs, name) (cbs)->action_left = (name) #endif +/* Forward declarations */ +int action_ok_core_lock(const char *path, const char *label, unsigned type, size_t idx, size_t entry_idx); + extern struct key_desc key_descriptors[RARCH_MAX_KEYS]; #if defined(HAVE_CG) || defined(HAVE_GLSL) || defined(HAVE_SLANG) || defined(HAVE_HLSL) @@ -680,6 +683,12 @@ static int core_setting_left(unsigned type, const char *label, return 0; } +static int action_left_core_lock(unsigned type, const char *label, + bool wraparound) +{ + return action_ok_core_lock(label, label, type, 0, 0); +} + static int disk_options_disk_idx_left(unsigned type, const char *label, bool wraparound) { @@ -1066,6 +1075,9 @@ static int menu_cbs_init_bind_left_compare_type(menu_file_list_cbs_t *cbs, case FILE_TYPE_CONTENTLIST_ENTRY: BIND_ACTION_LEFT(cbs, action_left_mainmenu); break; + case MENU_SETTING_ACTION_CORE_LOCK: + BIND_ACTION_LEFT(cbs, action_left_core_lock); + break; default: return -1; } diff --git a/menu/cbs/menu_cbs_ok.c b/menu/cbs/menu_cbs_ok.c index 3156114086..6f88b915ad 100644 --- a/menu/cbs/menu_cbs_ok.c +++ b/menu/cbs/menu_cbs_ok.c @@ -6360,17 +6360,122 @@ static int action_ok_core_delete_backup(const char *path, return 0; } +/* Do not declare this static - it is also used + * in menu_cbs_left.c and menu_cbs_right.c */ +int action_ok_core_lock(const char *path, + const char *label, unsigned type, size_t idx, size_t entry_idx) +{ + const char *core_path = path; + bool lock = false; + bool refresh = false; + int ret = 0; + + if (string_is_empty(core_path)) + return -1; + + /* Simply toggle current lock status */ + lock = !core_info_get_core_lock(core_path, true); + + if (!core_info_set_core_lock(core_path, lock)) + { + const char *core_name = NULL; + core_info_ctx_find_t core_info; + char msg[PATH_MAX_LENGTH]; + + msg[0] = '\0'; + + /* Need to fetch core name for error message */ + core_info.inf = NULL; + core_info.path = core_path; + + /* If core is found, use display name */ + if (core_info_find(&core_info) && + core_info.inf->display_name) + core_name = core_info.inf->display_name; + /* If not, use core file name */ + else + core_name = path_basename(core_path); + + /* Build error message */ + strlcpy( + msg, + msg_hash_to_str(lock ? + MSG_CORE_LOCK_FAILED : MSG_CORE_UNLOCK_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; + } + + /* Whenever lock status is changed, menu must be + * refreshed - do this even in the event of an error, + * since we don't want to leave the menu in an + * undefined state */ + menu_entries_ctl(MENU_ENTRIES_CTL_SET_REFRESH, &refresh); + menu_driver_ctl(RARCH_MENU_CTL_SET_PREVENT_POPULATE, NULL); + + return ret; +} + static int action_ok_core_delete(const char *path, const char *label, unsigned type, size_t idx, size_t entry_idx) { - const char *core_path = label; - const char *core = NULL; + const char *core_path = label; + const char *core = NULL; const char *loaded_core_path = NULL; const char *loaded_core = NULL; if (string_is_empty(core_path)) return -1; + /* Check whether core is locked */ + if (core_info_get_core_lock(core_path, true)) + { + const char *core_name = NULL; + core_info_ctx_find_t core_info; + char msg[PATH_MAX_LENGTH]; + + msg[0] = '\0'; + + /* Need to fetch core name for notification */ + core_info.inf = NULL; + core_info.path = core_path; + + /* If core is found, use display name */ + if (core_info_find(&core_info) && + core_info.inf->display_name) + core_name = core_info.inf->display_name; + /* If not, use core file name */ + else + core_name = path_basename(core_path); + + /* Build notification message */ + strlcpy(msg, msg_hash_to_str(MSG_CORE_DELETE_DISABLED), sizeof(msg)); + + if (!string_is_empty(core_name)) + strlcat(msg, core_name, sizeof(msg)); + + runloop_msg_queue_push( + msg, + 1, 100, true, + NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); + + /* We do not consider this an 'error' - we are + * merely telling the user that this operation + * is not currently supported */ + return 0; + } + /* Get core file name */ core = path_basename(core_path); if (string_is_empty(core)) @@ -7409,6 +7514,9 @@ static int menu_cbs_init_bind_ok_compare_type(menu_file_list_cbs_t *cbs, case MENU_SETTING_ITEM_CORE_DELETE_BACKUP: BIND_ACTION_OK(cbs, action_ok_core_delete_backup); break; + case MENU_SETTING_ACTION_CORE_LOCK: + BIND_ACTION_OK(cbs, action_ok_core_lock); + break; default: return -1; } diff --git a/menu/cbs/menu_cbs_right.c b/menu/cbs/menu_cbs_right.c index 7b5c8f0cbc..53478743b8 100644 --- a/menu/cbs/menu_cbs_right.c +++ b/menu/cbs/menu_cbs_right.c @@ -47,6 +47,9 @@ #define BIND_ACTION_RIGHT(cbs, name) (cbs)->action_right = (name) #endif +/* Forward declarations */ +int action_ok_core_lock(const char *path, const char *label, unsigned type, size_t idx, size_t entry_idx); + extern struct key_desc key_descriptors[RARCH_MAX_KEYS]; #if defined(HAVE_CG) || defined(HAVE_GLSL) || defined(HAVE_SLANG) || defined(HAVE_HLSL) @@ -804,6 +807,12 @@ int core_setting_right(unsigned type, const char *label, return 0; } +static int action_right_core_lock(unsigned type, const char *label, + bool wraparound) +{ + return action_ok_core_lock(label, label, type, 0, 0); +} + static int disk_options_disk_idx_right(unsigned type, const char *label, bool wraparound) { @@ -923,6 +932,9 @@ static int menu_cbs_init_bind_right_compare_type(menu_file_list_cbs_t *cbs, case FILE_TYPE_CONTENTLIST_ENTRY: BIND_ACTION_RIGHT(cbs, action_right_mainmenu); break; + case MENU_SETTING_ACTION_CORE_LOCK: + BIND_ACTION_RIGHT(cbs, action_right_core_lock); + break; default: return -1; } diff --git a/menu/cbs/menu_cbs_start.c b/menu/cbs/menu_cbs_start.c index cc0f1a0548..465a0639d0 100644 --- a/menu/cbs/menu_cbs_start.c +++ b/menu/cbs/menu_cbs_start.c @@ -36,6 +36,7 @@ #include "../../managers/core_option_manager.h" #include "../../managers/cheat_manager.h" #include "../../retroarch.h" +#include "../../verbosity.h" #include "../../performance_counters.h" #include "../../playlist.h" #include "../../manual_content_scan.h" @@ -495,6 +496,70 @@ static int action_start_core_updater_entry( } #endif +static int action_start_core_lock( + const char *path, const char *label, + unsigned type, size_t idx, size_t entry_idx) +{ + const char *core_path = path; + bool refresh = false; + int ret = 0; + + if (string_is_empty(core_path)) + return -1; + + /* Core should be unlocked by default + * > If it is currently unlocked, do nothing */ + if (!core_info_get_core_lock(core_path, true)) + return ret; + + /* ...Otherwise, attempt to unlock it */ + if (!core_info_set_core_lock(core_path, false)) + { + const char *core_name = NULL; + core_info_ctx_find_t core_info; + char msg[PATH_MAX_LENGTH]; + + msg[0] = '\0'; + + /* Need to fetch core name for error message */ + core_info.inf = NULL; + core_info.path = core_path; + + /* If core is found, use display name */ + if (core_info_find(&core_info) && + core_info.inf->display_name) + core_name = core_info.inf->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_UNLOCK_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; + } + + /* Whenever lock status is changed, menu must be + * refreshed - do this even in the event of an error, + * since we don't want to leave the menu in an + * undefined state */ + menu_entries_ctl(MENU_ENTRIES_CTL_SET_REFRESH, &refresh); + menu_driver_ctl(RARCH_MENU_CTL_SET_PREVENT_POPULATE, NULL); + + return ret; +} + static int action_start_lookup_setting( const char *path, const char *label, unsigned type, size_t idx, size_t entry_idx) @@ -636,6 +701,9 @@ static int menu_cbs_init_bind_start_compare_type(menu_file_list_cbs_t *cbs, BIND_ACTION_START(cbs, action_start_core_updater_entry); break; #endif + case MENU_SETTING_ACTION_CORE_LOCK: + BIND_ACTION_START(cbs, action_start_core_lock); + break; default: return -1; } diff --git a/menu/cbs/menu_cbs_sublabel.c b/menu/cbs/menu_cbs_sublabel.c index 273be76327..44996c0229 100644 --- a/menu/cbs/menu_cbs_sublabel.c +++ b/menu/cbs/menu_cbs_sublabel.c @@ -753,6 +753,7 @@ DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_video_viewport_custom_x, DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_video_viewport_custom_y, MENU_ENUM_SUBLABEL_VIDEO_VIEWPORT_CUSTOM_Y) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_netplay_use_mitm_server, MENU_ENUM_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_core_lock, MENU_ENUM_SUBLABEL_CORE_LOCK) 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) @@ -3468,6 +3469,9 @@ int menu_cbs_init_bind_sublabel(menu_file_list_cbs_t *cbs, case MENU_ENUM_LABEL_NETPLAY_MITM_SERVER: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_netplay_mitm_server); break; + case MENU_ENUM_LABEL_CORE_LOCK: + BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_core_lock); + 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 af74d3bc73..d0520eb204 100644 --- a/menu/drivers/materialui.c +++ b/menu/drivers/materialui.c @@ -9119,6 +9119,7 @@ static void materialui_list_insert( case FILE_TYPE_DOWNLOAD_CORE: case FILE_TYPE_CORE: case MENU_SETTING_ACTION_CORE_MANAGER_OPTIONS: + case MENU_SETTING_ACTION_CORE_LOCK: node->icon_texture_index = MUI_TEXTURE_CORES; node->has_icon = true; break; diff --git a/menu/drivers/ozone/ozone_texture.c b/menu/drivers/ozone/ozone_texture.c index 22835504b6..e9a5a6d5cd 100644 --- a/menu/drivers/ozone/ozone_texture.c +++ b/menu/drivers/ozone/ozone_texture.c @@ -239,6 +239,8 @@ uintptr_t ozone_entries_icon_get_texture(ozone_handle_t *ozone, case MENU_ENUM_LABEL_DELETE_PLAYLIST: case MENU_ENUM_LABEL_CORE_DELETE_BACKUP_LIST: return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_CLOSE]; + case MENU_ENUM_LABEL_CORE_LOCK: + 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]; case MENU_ENUM_LABEL_SHOW_WIMP: diff --git a/menu/drivers/xmb.c b/menu/drivers/xmb.c index a7f20acdab..ea42e71fa3 100644 --- a/menu/drivers/xmb.c +++ b/menu/drivers/xmb.c @@ -2644,6 +2644,8 @@ static uintptr_t xmb_icon_get_id(xmb_handle_t *xmb, case MENU_ENUM_LABEL_DELETE_PLAYLIST: case MENU_ENUM_LABEL_CORE_DELETE_BACKUP_LIST: return xmb->textures.list[XMB_TEXTURE_CLOSE]; + case MENU_ENUM_LABEL_CORE_LOCK: + return xmb->textures.list[XMB_TEXTURE_CORE]; case MENU_ENUM_LABEL_ONSCREEN_DISPLAY_SETTINGS: return xmb->textures.list[XMB_TEXTURE_OSD]; case MENU_ENUM_LABEL_SHOW_WIMP: diff --git a/menu/menu_displaylist.c b/menu/menu_displaylist.c index 7c67765977..2046c74818 100644 --- a/menu/menu_displaylist.c +++ b/menu/menu_displaylist.c @@ -367,6 +367,26 @@ end: #if !(defined(__WINRT__) || defined(WINAPI_FAMILY) && WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP) if (!string_is_empty(core_path) && !kiosk_mode_enable) { + /* Check whether core is currently locked */ + bool core_locked = core_info_get_core_lock(core_path, true); + + /* Lock core + * > 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_LOCK, + MENU_SETTING_ACTION_CORE_LOCK, 0, 0)) + { + file_list_set_alt_at_offset( + info->list, count, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_CORE_LOCK)); + count++; + } + /* Backup core */ if (menu_entries_append_enum(info->list, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_CORE_CREATE_BACKUP), @@ -376,12 +396,13 @@ end: count++; /* Restore core from backup */ - if (menu_entries_append_enum(info->list, - msg_hash_to_str(MENU_ENUM_LABEL_VALUE_CORE_RESTORE_BACKUP_LIST), - core_path, - MENU_ENUM_LABEL_CORE_RESTORE_BACKUP_LIST, - MENU_SETTING_ACTION_CORE_RESTORE_BACKUP, 0, 0)) - count++; + if (!core_locked) + if (menu_entries_append_enum(info->list, + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_CORE_RESTORE_BACKUP_LIST), + core_path, + MENU_ENUM_LABEL_CORE_RESTORE_BACKUP_LIST, + MENU_SETTING_ACTION_CORE_RESTORE_BACKUP, 0, 0)) + count++; /* Delete core backup */ if (menu_entries_append_enum(info->list, @@ -397,7 +418,7 @@ end: * up in a situation where a core cannot be * restored */ #if defined(HAVE_NETWORKING) && defined(HAVE_ONLINE_UPDATER) - if (menu_show_core_updater) + if (menu_show_core_updater && !core_locked) if (menu_entries_append_enum(info->list, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_CORE_DELETE), core_path, @@ -9641,26 +9662,30 @@ bool menu_displaylist_ctl(enum menu_displaylist_ctl_state type, break; case DISPLAYLIST_CORE_INFO: { - /* There is a (infinitesimally small) chance that - * the number of items in the core info menu will - * change after performing a core restore operation - * (i.e. the core info files are reloaded, and if - * an unknown error occurs then info entries may - * not be available upon popping the stack). We - * therefore have to cache the last set menu size, - * and reset the navigation pointer if the current - * size is different */ - static size_t prev_count = 0; - menu_entries_ctl(MENU_ENTRIES_CTL_CLEAR, info->list); - count = menu_displaylist_parse_core_info(info); + /* The number of items in the core info menu: + * - *May* (possibly) change after performing a + * core restore operation (i.e. the core info + * files are reloaded, and if an unknown error + * occurs then info entries may not be available + * upon popping the stack) + * - *Will* change when toggling the core lock + * status + * To prevent the menu selection from going out + * of bounds, we therefore have to check that the + * current selection index is less than the current + * number of menu entries - if not, we reset the + * navigation pointer */ + size_t selection = menu_navigation_get_selection(); - if (count != prev_count) + menu_entries_ctl(MENU_ENTRIES_CTL_CLEAR, info->list); + count = menu_displaylist_parse_core_info(info); + + if (selection >= count) { info->need_refresh = true; info->need_navigation_clear = true; - prev_count = count; } - info->need_push = true; + info->need_push = true; } break; case DISPLAYLIST_CORE_RESTORE_BACKUP_LIST: diff --git a/menu/menu_driver.h b/menu/menu_driver.h index dd05d21853..28e2d61825 100644 --- a/menu/menu_driver.h +++ b/menu/menu_driver.h @@ -108,6 +108,7 @@ enum menu_settings_type MENU_SETTING_ACTION_SCREENSHOT, MENU_SETTING_ACTION_DELETE_ENTRY, MENU_SETTING_ACTION_RESET, + MENU_SETTING_ACTION_CORE_LOCK, MENU_SETTING_ACTION_CORE_DELETE, MENU_SETTING_STRING_OPTIONS, MENU_SETTING_GROUP, diff --git a/msg_hash.h b/msg_hash.h index 8e8d5c2313..1a59ca6978 100644 --- a/msg_hash.h +++ b/msg_hash.h @@ -1995,8 +1995,13 @@ enum msg_hash_enums MENU_LABEL(ACHIEVEMENT_RESUME), MENU_LABEL(CORE_INFORMATION), MENU_LABEL(DISC_INFORMATION), + MENU_LABEL(CORE_LOCK), MENU_LABEL(CORE_DELETE), + MSG_CORE_LOCK_FAILED, + MSG_CORE_UNLOCK_FAILED, + MSG_CORE_DELETE_DISABLED, + /* Core updater */ MENU_LABEL(UPDATE_INSTALLED_CORES), @@ -2011,6 +2016,8 @@ enum msg_hash_enums MSG_CHECKING_CORE, MSG_ALL_CORES_UPDATED, MSG_NUM_CORES_UPDATED, + MSG_NUM_CORES_LOCKED, + MSG_CORE_UPDATE_DISABLED, /* Core backup/restore */ MENU_LABEL(CORE_CREATE_BACKUP), @@ -2037,6 +2044,8 @@ enum msg_hash_enums MSG_CORE_BACKUP_FAILED, MSG_CORE_RESTORATION_FAILED, MSG_CORE_INSTALLATION_FAILED, + MSG_CORE_RESTORATION_DISABLED, + MSG_CORE_INSTALLATION_DISABLED, MENU_LABEL(VIDEO_SHADER_PARAMETERS), MENU_LABEL(VIDEO_SHADER_PRESET_PARAMETERS), diff --git a/tasks/task_core_backup.c b/tasks/task_core_backup.c index 15bdbb772d..f1c4db26ae 100644 --- a/tasks/task_core_backup.c +++ b/tasks/task_core_backup.c @@ -992,14 +992,6 @@ bool task_push_core_restore(const char *backup_path, const char *dir_libretro, goto error; } - /* Concurrent backup/restore tasks for the same core - * are not allowed */ - find_data.func = task_core_backup_finder; - find_data.userdata = (void*)core_path; - - if (task_queue_find(&find_data)) - goto error; - /* Get core name */ core_info.inf = NULL; core_info.path = core_path; @@ -1017,6 +1009,34 @@ bool task_push_core_restore(const char *backup_path, const char *dir_libretro, goto error; } + /* Check whether core is locked */ + if (core_info_get_core_lock(core_path, true)) + { + char msg[PATH_MAX_LENGTH]; + + msg[0] = '\0'; + + strlcpy(msg, + (backup_type == CORE_BACKUP_TYPE_ARCHIVE) ? + msg_hash_to_str(MSG_CORE_RESTORATION_DISABLED) : + msg_hash_to_str(MSG_CORE_INSTALLATION_DISABLED), + sizeof(msg)); + strlcat(msg, core_name, sizeof(msg)); + + RARCH_ERR("[core restore] Restoration disabled - core is locked: %s\n", core_path); + runloop_msg_queue_push(msg, 1, 100, true, + NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); + goto error; + } + + /* Concurrent backup/restore tasks for the same core + * are not allowed */ + find_data.func = task_core_backup_finder; + find_data.userdata = (void*)core_path; + + if (task_queue_find(&find_data)) + goto error; + /* Configure handle */ backup_handle = (core_backup_handle_t*)calloc(1, sizeof(core_backup_handle_t)); diff --git a/tasks/task_core_updater.c b/tasks/task_core_updater.c index 8cfaa8ff47..2a3b275f45 100644 --- a/tasks/task_core_updater.c +++ b/tasks/task_core_updater.c @@ -123,6 +123,7 @@ typedef struct update_installed_cores_handle size_t list_index; size_t installed_index; unsigned num_updated; + unsigned num_locked; enum update_installed_cores_status status; } update_installed_cores_handle_t; @@ -962,14 +963,37 @@ void *task_push_core_updater_download( core_list, filename, &list_entry)) goto error; - if (string_is_empty(list_entry->remote_core_path)) + if (string_is_empty(list_entry->remote_core_path) || + string_is_empty(list_entry->local_core_path) || + string_is_empty(list_entry->display_name)) goto error; - if (string_is_empty(list_entry->local_core_path)) - goto error; + /* Check whether core is locked + * > Have to set validate_path to 'false' here, + * since this may not run on the main thread + * > Validation is not required anyway, since core + * updater list provides 'sane' core paths */ + if (core_info_get_core_lock(list_entry->local_core_path, false)) + { + RARCH_ERR("[core updater] Update disabled - core is locked: %s\n", + list_entry->local_core_path); + + /* If task is not muted, generate notification */ + if (!mute) + { + char msg[PATH_MAX_LENGTH]; + + msg[0] = '\0'; + + strlcpy(msg, msg_hash_to_str(MSG_CORE_UPDATE_DISABLED), sizeof(msg)); + strlcat(msg, list_entry->display_name, sizeof(msg)); + + runloop_msg_queue_push(msg, 1, 100, true, + NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); + } - if (string_is_empty(list_entry->display_name)) goto error; + } /* Get local file download path */ if (string_is_empty(path_dir_libretro)) @@ -1206,6 +1230,24 @@ static void task_update_installed_cores_handler(retro_task_t *task) break; } + /* Check whether core is locked + * > Have to set validate_path to 'false' here, + * since this does not run on the main thread + * > Validation is not required anyway, since core + * updater list provides 'sane' core paths */ + if (core_info_get_core_lock(list_entry->local_core_path, false)) + { + RARCH_LOG("[core updater] Skipping locked core: %s\n", + list_entry->display_name); + + /* Core update is disabled + * > Just increment 'locked cores' counter and + * return to UPDATE_INSTALLED_CORES_ITERATE state */ + update_installed_handle->num_locked++; + update_installed_handle->status = UPDATE_INSTALLED_CORES_ITERATE; + break; + } + /* Get CRC of existing core */ local_crc = task_core_updater_get_core_crc( list_entry->local_core_path); @@ -1292,24 +1334,40 @@ static void task_update_installed_cores_handler(retro_task_t *task) * successfully */ if (update_installed_handle->list_size > 0) { - /* > Check whether a non-zero number of cores - * were updated */ + char task_title[PATH_MAX_LENGTH]; + + task_title[0] = '\0'; + + /* > Generate final status message based on number + * of cores that were updated/locked */ if (update_installed_handle->num_updated > 0) { - char task_title[PATH_MAX_LENGTH]; - - task_title[0] = '\0'; - + if (update_installed_handle->num_locked > 0) + snprintf( + task_title, sizeof(task_title), "%s [%s%u, %s%u]", + msg_hash_to_str(MSG_ALL_CORES_UPDATED), + msg_hash_to_str(MSG_NUM_CORES_UPDATED), + update_installed_handle->num_updated, + msg_hash_to_str(MSG_NUM_CORES_LOCKED), + update_installed_handle->num_locked); + else + snprintf( + task_title, sizeof(task_title), "%s [%s%u]", + msg_hash_to_str(MSG_ALL_CORES_UPDATED), + msg_hash_to_str(MSG_NUM_CORES_UPDATED), + update_installed_handle->num_updated); + } + else if (update_installed_handle->num_locked > 0) snprintf( task_title, sizeof(task_title), "%s [%s%u]", msg_hash_to_str(MSG_ALL_CORES_UPDATED), - msg_hash_to_str(MSG_NUM_CORES_UPDATED), - update_installed_handle->num_updated); - - task_set_title(task, strdup(task_title)); - } + msg_hash_to_str(MSG_NUM_CORES_LOCKED), + update_installed_handle->num_locked); else - task_set_title(task, strdup(msg_hash_to_str(MSG_ALL_CORES_UPDATED))); + strlcpy(task_title, msg_hash_to_str(MSG_ALL_CORES_UPDATED), + sizeof(task_title)); + + task_set_title(task, strdup(task_title)); } else task_set_title(task, strdup(msg_hash_to_str(MSG_CORE_LIST_FAILED))); @@ -1370,6 +1428,7 @@ void task_push_update_installed_cores( update_installed_handle->list_index = 0; update_installed_handle->installed_index = 0; update_installed_handle->num_updated = 0; + update_installed_handle->num_locked = 0; update_installed_handle->status = UPDATE_INSTALLED_CORES_BEGIN; if (!update_installed_handle->core_list)