diff --git a/configuration.c b/configuration.c index 58c0437c16c..94db6f2d8b4 100644 --- a/configuration.c +++ b/configuration.c @@ -1686,6 +1686,7 @@ static struct config_path_setting *populate_settings_path( SETTING_PATH("assets_directory", settings->paths.directory_assets, true, NULL, true); SETTING_PATH("dynamic_wallpapers_directory", settings->paths.directory_dynamic_wallpapers, true, NULL, true); SETTING_PATH("thumbnails_directory", settings->paths.directory_thumbnails, true, NULL, true); + SETTING_PATH("patches_directory", settings->paths.directory_patches, true, NULL, true); SETTING_PATH("runtime_log_directory", settings->paths.directory_runtime_log, true, NULL, true); SETTING_PATH("joypad_autoconfig_dir", settings->paths.directory_autoconfig, false, NULL, true); SETTING_PATH("savefile_directory", dir_get_ptr(RARCH_DIR_SAVEFILE), true, NULL, false); @@ -3172,6 +3173,7 @@ void config_set_defaults(void *data) *settings->paths.directory_assets = '\0'; *settings->paths.directory_dynamic_wallpapers = '\0'; *settings->paths.directory_thumbnails = '\0'; + *settings->paths.directory_patches = '\0'; *settings->paths.directory_playlist = '\0'; *settings->paths.directory_content_favorites = '\0'; *settings->paths.directory_content_history = '\0'; @@ -4374,6 +4376,8 @@ static bool config_load_file(global_t *global, *settings->paths.directory_dynamic_wallpapers = '\0'; if (string_is_equal(settings->paths.directory_thumbnails, "default")) *settings->paths.directory_thumbnails = '\0'; + if (string_is_equal(settings->paths.directory_patches, "default")) + *settings->paths.directory_patches = '\0'; if (string_is_equal(settings->paths.directory_playlist, "default")) *settings->paths.directory_playlist = '\0'; if (string_is_equal(settings->paths.directory_content_favorites, "default")) diff --git a/configuration.h b/configuration.h index 3225a2aae2e..d7197253227 100644 --- a/configuration.h +++ b/configuration.h @@ -607,6 +607,7 @@ typedef struct settings char directory_assets[DIR_MAX_LENGTH]; char directory_dynamic_wallpapers[DIR_MAX_LENGTH]; char directory_thumbnails[DIR_MAX_LENGTH]; + char directory_patches[DIR_MAX_LENGTH]; char directory_menu_config[DIR_MAX_LENGTH]; char directory_menu_content[DIR_MAX_LENGTH]; #ifdef _3DS diff --git a/intl/msg_hash_lbl.h b/intl/msg_hash_lbl.h index 383fcb3370f..1cee4248a9d 100644 --- a/intl/msg_hash_lbl.h +++ b/intl/msg_hash_lbl.h @@ -3364,6 +3364,10 @@ MSG_HASH( MENU_ENUM_LABEL_RUN, "collection" ) +MSG_HASH( + MENU_ENUM_LABEL_PATCHES, + "patches" + ) MSG_HASH( MENU_ENUM_LABEL_RUN_MUSIC, "collection_music" @@ -3829,6 +3833,10 @@ MSG_HASH( MENU_ENUM_LABEL_THUMBNAILS_DIRECTORY, "thumbnails_directory" ) +MSG_HASH( + MENU_ENUM_LABEL_PATCHES_DIRECTORY, + "patches_directory" + ) MSG_HASH( MENU_ENUM_LABEL_PL_THUMBNAILS_UPDATER_LIST, "pl_thumbnails_updater_list" diff --git a/intl/msg_hash_us.h b/intl/msg_hash_us.h index 81cde5d6cec..c5b80c6e084 100644 --- a/intl/msg_hash_us.h +++ b/intl/msg_hash_us.h @@ -8235,6 +8235,14 @@ MSG_HASH( MENU_ENUM_SUBLABEL_THUMBNAILS_DIRECTORY, "Box art, screenshot, and title screen thumbnails are stored in this directory." ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_PATCHES_DIRECTORY, + "Patches" + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_PATCHES_DIRECTORY, + "Patch files are searched in this directory." + ) MSG_HASH( /* FIXME Not RGUI specific */ MENU_ENUM_LABEL_VALUE_RGUI_BROWSER_DIRECTORY, "Start Directory" @@ -9075,6 +9083,14 @@ MSG_HASH( MENU_ENUM_SUBLABEL_INFORMATION, "View more information about the content." ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_PATCHES, + "Patches" + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_PATCHES, + "Select and order patches to apply before content is loaded." + ) MSG_HASH( MENU_ENUM_LABEL_VALUE_DOWNLOAD_PL_ENTRY_THUMBNAILS, "Download Thumbnails" diff --git a/menu/cbs/menu_cbs_deferred_push.c b/menu/cbs/menu_cbs_deferred_push.c index 2fc7b49529a..da0f6e627c1 100644 --- a/menu/cbs/menu_cbs_deferred_push.c +++ b/menu/cbs/menu_cbs_deferred_push.c @@ -116,6 +116,10 @@ GENERIC_DEFERRED_PUSH(deferred_archive_action, DISPLAYLIST_ GENERIC_DEFERRED_PUSH(deferred_push_core_counters, DISPLAYLIST_PERFCOUNTERS_CORE) GENERIC_DEFERRED_PUSH(deferred_push_frontend_counters, DISPLAYLIST_PERFCOUNTERS_FRONTEND) GENERIC_DEFERRED_PUSH(deferred_push_core_cheat_options, DISPLAYLIST_OPTIONS_CHEATS) +#ifdef HAVE_PATCH +GENERIC_DEFERRED_PUSH(deferred_push_patches_list, DISPLAYLIST_OPTIONS_PATCHES) +GENERIC_DEFERRED_PUSH(deferred_push_patches_actions_list, DISPLAYLIST_OPTIONS_PATCHES_ACTIONS) +#endif GENERIC_DEFERRED_PUSH(deferred_push_core_input_remapping_options, DISPLAYLIST_OPTIONS_REMAPPINGS) GENERIC_DEFERRED_PUSH(deferred_push_remap_file_manager, DISPLAYLIST_REMAP_FILE_MANAGER) GENERIC_DEFERRED_PUSH(deferred_push_savestate_list, DISPLAYLIST_SAVESTATE_LIST) @@ -738,6 +742,9 @@ static int menu_cbs_init_bind_deferred_push_compare_label( {MENU_ENUM_LABEL_VIDEO_SHADER_PRESET_PARAMETERS, deferred_push_video_shader_preset_parameters}, {MENU_ENUM_LABEL_VIDEO_SHADER_PARAMETERS, deferred_push_video_shader_parameters}, {MENU_ENUM_LABEL_CORE_CHEAT_OPTIONS, deferred_push_core_cheat_options}, +#ifdef HAVE_PATCH + {MENU_ENUM_LABEL_PATCHES, deferred_push_patches_list}, +#endif {MENU_ENUM_LABEL_CORE_INPUT_REMAPPING_OPTIONS, deferred_push_core_input_remapping_options}, {MENU_ENUM_LABEL_DEFERRED_REMAP_FILE_MANAGER_LIST, deferred_push_remap_file_manager}, {MENU_ENUM_LABEL_VIDEO_SHADER_PRESET, deferred_push_video_shader_preset}, @@ -1238,6 +1245,19 @@ static int menu_cbs_init_bind_deferred_push_compare_type( { BIND_ACTION_DEFERRED_PUSH(cbs, deferred_push_cdrom_info_detail_list); } +#ifdef HAVE_PATCH + else if (type == MENU_SETTING_ACTION_PATCHES) + { + BIND_ACTION_DEFERRED_PUSH(cbs, deferred_push_patches_list); + return 0; + } + else if (type >= MENU_SETTINGS_PATCH_BEGIN + && type <= MENU_SETTINGS_PATCH_END) + { + BIND_ACTION_DEFERRED_PUSH(cbs, deferred_push_patches_actions_list); + return 0; + } +#endif else return -1; diff --git a/menu/cbs/menu_cbs_ok.c b/menu/cbs/menu_cbs_ok.c index 9794fe74a4a..e06c76ec8d0 100644 --- a/menu/cbs/menu_cbs_ok.c +++ b/menu/cbs/menu_cbs_ok.c @@ -538,6 +538,9 @@ static enum msg_hash_enums action_ok_dl_to_enum(unsigned lbl) return MENU_ENUM_LABEL_DEFERRED_CORE_OPTION_OVERRIDE_LIST; case ACTION_OK_DL_REMAP_FILE_MANAGER_LIST: return MENU_ENUM_LABEL_DEFERRED_REMAP_FILE_MANAGER_LIST; + case ACTION_OK_DL_PATCHES_LIST: + case ACTION_OK_DL_PATCHES_ACTIONS_LIST: + return MENU_ENUM_LABEL_PATCHES; case ACTION_OK_DL_ADD_TO_PLAYLIST: return MENU_ENUM_LABEL_DEFERRED_ADD_TO_PLAYLIST_LIST; case ACTION_OK_DL_ADD_TO_PLAYLIST_QUICKMENU: @@ -1779,6 +1782,28 @@ int generic_action_ok_displaylist_push( info.type = MENU_SETTINGS_AUDIO_MIXER_STREAM_ACTIONS_BEGIN + player_no; } break; + case ACTION_OK_DL_PATCHES_LIST: + info.list = MENU_LIST_GET_SELECTION(menu_list, 0); + info.directory_ptr = idx; + info.type = type; + info_path = "patches_list"; + info_label = "patches_list"; + info.enum_idx = MENU_ENUM_LABEL_PATCHES; + menu_entries_append(menu_stack, info_path, info_label, + MENU_ENUM_LABEL_PATCHES, 0, idx, 0, NULL); + dl_type = DISPLAYLIST_OPTIONS_PATCHES; + break; + case ACTION_OK_DL_PATCHES_ACTIONS_LIST: + info.list = MENU_LIST_GET_SELECTION(menu_list, 0); + info.directory_ptr = idx; + info.type = type; + info_path = "patches_actions_list"; + info_label = "patches_actions_list"; + info.enum_idx = MENU_ENUM_LABEL_PATCHES; + menu_entries_append(menu_stack, info_path, info_label, + MENU_ENUM_LABEL_PATCHES, 0, idx, 0, NULL); + dl_type = DISPLAYLIST_OPTIONS_PATCHES_ACTIONS; + break; case ACTION_OK_DL_ACCOUNTS_LIST: case ACTION_OK_DL_ACHIEVEMENTS_HARDCORE_PAUSE_LIST: case ACTION_OK_DL_INPUT_SETTINGS_LIST: @@ -4516,6 +4541,115 @@ static int action_ok_stop_streaming(const char *path, return generic_action_ok_command(CMD_EVENT_RESUME); } +#ifdef HAVE_PATCH +#define MENU_LABEL_PATCHES_ADD_LIST "patches_add_list" +#define MENU_LABEL_PATCHES_ACTIONS_LIST "patches_actions_list" + +static bool action_ok_patch_get_selected_idx(size_t *idx) +{ + unsigned menu_type = FILE_TYPE_NONE; + + if (!idx) + return false; + + menu_entries_get_last_stack(NULL, NULL, &menu_type, NULL, NULL); + + if (menu_type < MENU_SETTINGS_PATCH_BEGIN || menu_type > MENU_SETTINGS_PATCH_END) + return false; + + *idx = menu_type - MENU_SETTINGS_PATCH_BEGIN; + return true; +} + +static int action_ok_patch_menu(const char *path, + const char *label, unsigned type, size_t idx, size_t entry_idx) +{ + return generic_action_ok_displaylist_push(path, NULL, + label, type, idx, entry_idx, ACTION_OK_DL_PUSH_DEFAULT); +} + +static int action_ok_patch_add_menu(const char *path, + const char *label, unsigned type, size_t idx, size_t entry_idx) +{ + char start_dir[PATH_MAX_LENGTH]; + + start_dir[0] = '\0'; + if (!patch_stack_resolve_selected_patches_dir(start_dir, sizeof(start_dir))) + return 0; + + filebrowser_set_type(FILEBROWSER_SELECT_FILE); + return generic_action_ok_displaylist_push(start_dir, start_dir, + MENU_LABEL_PATCHES_ADD_LIST, type, idx, entry_idx, ACTION_OK_DL_CONTENT_LIST); +} + +static int action_ok_patch_entry(const char *path, + const char *label, unsigned type, size_t idx, size_t entry_idx) +{ + return generic_action_ok_displaylist_push(path, NULL, + MENU_LABEL_PATCHES_ACTIONS_LIST, type, idx, entry_idx, ACTION_OK_DL_PUSH_DEFAULT); +} + +static int action_ok_patch_add_candidate(const char *path, + const char *label, unsigned type, size_t idx, size_t entry_idx) +{ + struct menu_state *menu_st = menu_state_get_ptr(); + const char *menu_path = NULL; + char full_path[PATH_MAX_LENGTH]; + + menu_entries_get_last_stack(&menu_path, NULL, NULL, NULL, NULL); + + if (!string_is_empty(menu_path) && !path_is_absolute(path)) + fill_pathname_join_special(full_path, menu_path, path, sizeof(full_path)); + else + strlcpy(full_path, path, sizeof(full_path)); + + if (patch_stack_add(full_path)) + menu_st->flags |= MENU_ST_FLAG_ENTRIES_NEED_REFRESH; + + menu_entries_flush_stack(msg_hash_to_str(MENU_ENUM_LABEL_PATCHES), 0); + return 0; +} + +static int action_ok_patch_move_up(const char *path, + const char *label, unsigned type, size_t idx, size_t entry_idx) +{ + struct menu_state *menu_st = menu_state_get_ptr(); + size_t patch_idx = 0; + + if (!action_ok_patch_get_selected_idx(&patch_idx) || !patch_stack_move_up(patch_idx)) + return 0; + + menu_st->flags |= MENU_ST_FLAG_ENTRIES_NEED_REFRESH; + return action_cancel_pop_default(path, label, type, idx); +} + +static int action_ok_patch_move_down(const char *path, + const char *label, unsigned type, size_t idx, size_t entry_idx) +{ + struct menu_state *menu_st = menu_state_get_ptr(); + size_t patch_idx = 0; + + if (!action_ok_patch_get_selected_idx(&patch_idx) || !patch_stack_move_down(patch_idx)) + return 0; + + menu_st->flags |= MENU_ST_FLAG_ENTRIES_NEED_REFRESH; + return action_cancel_pop_default(path, label, type, idx); +} + +static int action_ok_patch_remove(const char *path, + const char *label, unsigned type, size_t idx, size_t entry_idx) +{ + struct menu_state *menu_st = menu_state_get_ptr(); + size_t patch_idx = 0; + + if (!action_ok_patch_get_selected_idx(&patch_idx) || !patch_stack_remove(patch_idx)) + return 0; + + menu_st->flags |= MENU_ST_FLAG_ENTRIES_NEED_REFRESH; + return action_cancel_pop_default(path, label, type, idx); +} +#endif + #ifdef HAVE_CHEATS static int action_ok_cheat_add_top(const char *path, const char *label, unsigned type, size_t idx, size_t entry_idx) @@ -9617,6 +9751,18 @@ static int menu_cbs_init_bind_ok_compare_type(menu_file_list_cbs_t *cbs, { BIND_ACTION_OK(cbs, action_ok_cheat); } +#ifdef HAVE_PATCH + else if (type >= MENU_SETTINGS_PATCH_BEGIN + && type <= MENU_SETTINGS_PATCH_END) + { + BIND_ACTION_OK(cbs, action_ok_patch_entry); + } + else if (type >= MENU_SETTINGS_PATCH_CANDIDATE_BEGIN + && type <= MENU_SETTINGS_PATCH_CANDIDATE_END) + { + BIND_ACTION_OK(cbs, action_ok_patch_add_candidate); + } +#endif else if ( (type >= MENU_SETTINGS_CORE_OPTION_START) && (type < MENU_SETTINGS_CHEEVOS_START)) { @@ -9997,6 +10143,14 @@ static int menu_cbs_init_bind_ok_compare_type(menu_file_list_cbs_t *cbs, break; case FILE_TYPE_IN_CARCHIVE: case FILE_TYPE_PLAIN: +#ifdef HAVE_PATCH + if (string_is_equal(menu_label, MENU_LABEL_PATCHES_ADD_LIST)) + { + BIND_ACTION_OK(cbs, action_ok_patch_add_candidate); + break; + } +#endif + if (filebrowser_get_type() == FILEBROWSER_SCAN_FILE) { #ifdef HAVE_LIBRETRODB @@ -10130,6 +10284,23 @@ static int menu_cbs_init_bind_ok_compare_type(menu_file_list_cbs_t *cbs, case MENU_SETTING_ACTION_CONTENTLESS_CORE_RUN: BIND_ACTION_OK(cbs, action_ok_contentless_core_run); break; +#ifdef HAVE_PATCH + case MENU_SETTING_ACTION_PATCHES: + BIND_ACTION_OK(cbs, action_ok_patch_menu); + break; + case MENU_SETTING_ACTION_PATCH_ADD: + BIND_ACTION_OK(cbs, action_ok_patch_add_menu); + break; + case MENU_SETTING_ACTION_PATCH_MOVE_UP: + BIND_ACTION_OK(cbs, action_ok_patch_move_up); + break; + case MENU_SETTING_ACTION_PATCH_MOVE_DOWN: + BIND_ACTION_OK(cbs, action_ok_patch_move_down); + break; + case MENU_SETTING_ACTION_PATCH_REMOVE: + BIND_ACTION_OK(cbs, action_ok_patch_remove); + break; +#endif default: return -1; } diff --git a/menu/cbs/menu_cbs_sublabel.c b/menu/cbs/menu_cbs_sublabel.c index 375f048d29d..98b67fec732 100644 --- a/menu/cbs/menu_cbs_sublabel.c +++ b/menu/cbs/menu_cbs_sublabel.c @@ -1016,6 +1016,7 @@ DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_netplay_refresh_lan, DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_rename_entry, MENU_ENUM_SUBLABEL_RENAME_ENTRY) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_delete_entry, MENU_ENUM_SUBLABEL_DELETE_ENTRY) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_information, MENU_ENUM_SUBLABEL_INFORMATION) +DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_patches, MENU_ENUM_SUBLABEL_PATCHES) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_run, MENU_ENUM_SUBLABEL_RUN) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_add_to_favorites, MENU_ENUM_SUBLABEL_ADD_TO_FAVORITES) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_add_to_playlist, MENU_ENUM_SUBLABEL_ADD_TO_PLAYLIST) @@ -1100,6 +1101,7 @@ DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_use_builtin_media_player, DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_use_builtin_image_viewer, MENU_ENUM_SUBLABEL_USE_BUILTIN_IMAGE_VIEWER) DEFAULT_SUBLABEL_MACRO(action_bind_dynamic_wallpapers_directory, MENU_ENUM_SUBLABEL_DYNAMIC_WALLPAPERS_DIRECTORY) DEFAULT_SUBLABEL_MACRO(action_bind_thumbnails_directory, MENU_ENUM_SUBLABEL_THUMBNAILS_DIRECTORY) +DEFAULT_SUBLABEL_MACRO(action_bind_patches_directory, MENU_ENUM_SUBLABEL_PATCHES_DIRECTORY) DEFAULT_SUBLABEL_MACRO(action_bind_rgui_config_directory, MENU_ENUM_SUBLABEL_RGUI_CONFIG_DIRECTORY) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_input_latency_frames, MENU_ENUM_SUBLABEL_NETPLAY_INPUT_LATENCY_FRAMES_MIN) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_input_latency_frames_range, MENU_ENUM_SUBLABEL_NETPLAY_INPUT_LATENCY_FRAMES_RANGE) @@ -3347,6 +3349,9 @@ int menu_cbs_init_bind_sublabel(menu_file_list_cbs_t *cbs, case MENU_ENUM_LABEL_THUMBNAILS_DIRECTORY: BIND_ACTION_SUBLABEL(cbs, action_bind_thumbnails_directory); break; + case MENU_ENUM_LABEL_PATCHES_DIRECTORY: + BIND_ACTION_SUBLABEL(cbs, action_bind_patches_directory); + break; case MENU_ENUM_LABEL_DYNAMIC_WALLPAPERS_DIRECTORY: BIND_ACTION_SUBLABEL(cbs, action_bind_dynamic_wallpapers_directory); break; @@ -3591,6 +3596,9 @@ int menu_cbs_init_bind_sublabel(menu_file_list_cbs_t *cbs, case MENU_ENUM_LABEL_INFORMATION: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_information); break; + case MENU_ENUM_LABEL_PATCHES: + BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_patches); + break; case MENU_ENUM_LABEL_RENAME_ENTRY: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_rename_entry); break; diff --git a/menu/drivers/materialui.c b/menu/drivers/materialui.c index 3f50e0c04dc..f36875d8e53 100644 --- a/menu/drivers/materialui.c +++ b/menu/drivers/materialui.c @@ -11685,6 +11685,7 @@ static void materialui_list_insert(void *userdata, || string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_SAVE_CURRENT_CONFIG_OVERRIDE_CONTENT_DIR)) || string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_SAVE_CURRENT_CONFIG_OVERRIDE_GAME)) || string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_QUICK_MENU_OVERRIDE_OPTIONS)) + || string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_PATCHES)) || string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_NETWORK_ON_DEMAND_THUMBNAILS)) ) { diff --git a/menu/drivers/ozone.c b/menu/drivers/ozone.c index 2772275e099..4117797807b 100644 --- a/menu/drivers/ozone.c +++ b/menu/drivers/ozone.c @@ -2301,6 +2301,7 @@ static uintptr_t ozone_entries_icon_get_texture( case MENU_ENUM_LABEL_CONTENT_SHOW_REWIND: return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_REWIND]; case MENU_ENUM_LABEL_QUICK_MENU_OVERRIDE_OPTIONS: + case MENU_ENUM_LABEL_PATCHES: case MENU_ENUM_LABEL_DEFERRED_QUICK_MENU_OVERRIDE_OPTIONS: case MENU_ENUM_LABEL_QUICK_MENU_SHOW_SAVE_CORE_OVERRIDES: case MENU_ENUM_LABEL_QUICK_MENU_SHOW_SAVE_CONTENT_DIR_OVERRIDES: diff --git a/menu/drivers/xmb.c b/menu/drivers/xmb.c index 0e686dae974..b0126ab83f6 100644 --- a/menu/drivers/xmb.c +++ b/menu/drivers/xmb.c @@ -2441,9 +2441,13 @@ static void xmb_set_title(xmb_handle_t *xmb) { if (string_ends_with(label, "_list")) { - label_temp[strlen(label_temp) - STRLEN_CONST("_list")] = '\0'; - label = label_temp; - enum_idx = xmb_search_enum(label); + size_t label_len = strlcpy(label_temp, label, sizeof(label_temp)); + if (label_len > strlen("_list")) + { + label_temp[strlen(label_temp) - STRLEN_CONST("_list")] = '\0'; + label = label_temp; + enum_idx = xmb_search_enum(label); + } } else if (string_starts_with(label, "dropdown_box_list_")) { @@ -3952,6 +3956,7 @@ static uintptr_t xmb_icon_get_id(xmb_handle_t *xmb, case MENU_ENUM_LABEL_INPUT_DEVICE_RESERVATION_TYPE: case MENU_ENUM_LABEL_INPUT_DEVICE_RESERVED_DEVICE_NAME: case MENU_ENUM_LABEL_QUICK_MENU_OVERRIDE_OPTIONS: + case MENU_ENUM_LABEL_PATCHES: case MENU_ENUM_LABEL_QUICK_MENU_SHOW_SAVE_CORE_OVERRIDES: case MENU_ENUM_LABEL_QUICK_MENU_SHOW_SAVE_CONTENT_DIR_OVERRIDES: case MENU_ENUM_LABEL_QUICK_MENU_SHOW_SAVE_GAME_OVERRIDES: diff --git a/menu/menu_cbs.h b/menu/menu_cbs.h index 69d507b1f4c..992c0bb3845 100644 --- a/menu/menu_cbs.h +++ b/menu/menu_cbs.h @@ -252,6 +252,8 @@ enum ACTION_OK_DL_CORE_OPTION_OVERRIDE_LIST, ACTION_OK_DL_CORE_OPTIONS_LIST, ACTION_OK_DL_REMAP_FILE_MANAGER_LIST, + ACTION_OK_DL_PATCHES_LIST, + ACTION_OK_DL_PATCHES_ACTIONS_LIST, ACTION_OK_DL_ADD_TO_PLAYLIST, ACTION_OK_DL_ADD_TO_PLAYLIST_QUICKMENU }; diff --git a/menu/menu_displaylist.c b/menu/menu_displaylist.c index a872c3e7896..08ac898992d 100644 --- a/menu/menu_displaylist.c +++ b/menu/menu_displaylist.c @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -256,6 +257,11 @@ static int filebrowser_parse( if (string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_SCAN_FILE))) filter_ext = false; + else if (string_is_equal(label, "patches_add_list")) + { + exts = "ips|bps|ups|xdelta"; + filter_ext = true; + } if ( string_is_equal(label, "database_manager_list") #ifdef IOS @@ -4034,6 +4040,14 @@ static int menu_displaylist_parse_horizontal_content_actions( #endif } +#ifdef HAVE_PATCH + menu_entries_append(list, + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_PATCHES), + msg_hash_to_str(MENU_ENUM_LABEL_PATCHES), + MENU_ENUM_LABEL_PATCHES, + MENU_SETTING_ACTION_PATCHES, 0, 0, NULL); +#endif + if (settings->bools.quick_menu_show_information) menu_entries_append(list, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_INFORMATION), @@ -8493,6 +8507,106 @@ unsigned menu_displaylist_build_list( FILE_TYPE_NONE, 0, 0, NULL)) count++; } +#endif + break; + case DISPLAYLIST_OPTIONS_PATCHES: +#ifdef HAVE_PATCH + { + size_t i; + size_t patch_count; + char patch_label[NAME_MAX_LENGTH]; + struct string_list *selected = NULL; + + if (!patch_stack_load_selected_patches_list(&selected) || !selected) + break; + + patch_count = selected->size; + if (patch_count > MAX_PATCH_COUNTERS) + patch_count = MAX_PATCH_COUNTERS; + + for (i = 0; i < patch_count; i++) + { + const char *patch_name = selected->elems[i].data; + const char *patch_label_name = path_basename(patch_name); + if (string_is_empty(patch_name)) + continue; + if (string_is_empty(patch_label_name)) + patch_label_name = patch_name; + + snprintf(patch_label, sizeof(patch_label), "%u: %s", + (unsigned)(i + 1), patch_label_name); + + if (menu_entries_append(list, + patch_label, + patch_name, + MSG_UNKNOWN, + (unsigned)(MENU_SETTINGS_PATCH_BEGIN + i), 0, 0, NULL)) + count++; + } + + string_list_free(selected); + + if (menu_entries_append(list, + "Add Patch", + "Add Patch", + MSG_UNKNOWN, + MENU_SETTING_ACTION_PATCH_ADD, 0, 0, NULL)) + count++; + } +#endif + break; + case DISPLAYLIST_OPTIONS_PATCHES_ACTIONS: +#ifdef HAVE_PATCH + { + size_t selected_idx; + size_t selected_size; + unsigned menu_type = FILE_TYPE_NONE; + struct string_list *selected = NULL; + + if (!patch_stack_load_selected_patches_list(&selected) || !selected) + break; + + selected_size = selected->size; + if (selected_size > MAX_PATCH_COUNTERS) + selected_size = MAX_PATCH_COUNTERS; + menu_entries_get_last_stack(NULL, NULL, &menu_type, NULL, NULL); + selected_idx = (menu_type >= MENU_SETTINGS_PATCH_BEGIN + && menu_type <= MENU_SETTINGS_PATCH_END) + ? (size_t)(menu_type - MENU_SETTINGS_PATCH_BEGIN) + : 0; + + if (selected_idx > 0) + { + if (menu_entries_append(list, + "Move Up", + "Move Up", + MSG_UNKNOWN, + MENU_SETTING_ACTION_PATCH_MOVE_UP, 0, 0, NULL)) + count++; + } + + if ((selected_idx + 1) < selected_size) + { + if (menu_entries_append(list, + "Move Down", + "Move Down", + MSG_UNKNOWN, + MENU_SETTING_ACTION_PATCH_MOVE_DOWN, 0, 0, NULL)) + count++; + } + + if (selected_size > 0) + { + if (menu_entries_append(list, + "Remove", + "Remove", + MSG_UNKNOWN, + MENU_SETTING_ACTION_PATCH_REMOVE, 0, 0, NULL)) + count++; + } + + string_list_free(selected); + } #endif break; @@ -11591,6 +11705,7 @@ unsigned menu_displaylist_build_list( {MENU_ENUM_LABEL_CORE_ASSETS_DIRECTORY, PARSE_ONLY_DIR}, {MENU_ENUM_LABEL_ASSETS_DIRECTORY, PARSE_ONLY_DIR}, {MENU_ENUM_LABEL_THUMBNAILS_DIRECTORY, PARSE_ONLY_DIR}, + {MENU_ENUM_LABEL_PATCHES_DIRECTORY, PARSE_ONLY_DIR}, {MENU_ENUM_LABEL_DYNAMIC_WALLPAPERS_DIRECTORY, PARSE_ONLY_DIR}, #ifdef HAVE_CHEATS {MENU_ENUM_LABEL_CHEAT_DATABASE_PATH, PARSE_ONLY_DIR}, @@ -14717,6 +14832,8 @@ bool menu_displaylist_ctl(enum menu_displaylist_ctl_state type, case DISPLAYLIST_NETWORK_SETTINGS_LIST: case DISPLAYLIST_NETPLAY_LOBBY_FILTERS_LIST: case DISPLAYLIST_OPTIONS_CHEATS: + case DISPLAYLIST_OPTIONS_PATCHES: + case DISPLAYLIST_OPTIONS_PATCHES_ACTIONS: case DISPLAYLIST_NETWORK_INFO: case DISPLAYLIST_DROPDOWN_LIST_RESOLUTION: case DISPLAYLIST_DROPDOWN_LIST_PLAYLIST_DEFAULT_CORE: diff --git a/menu/menu_displaylist.h b/menu/menu_displaylist.h index f65ca848cec..0611a8943b9 100644 --- a/menu/menu_displaylist.h +++ b/menu/menu_displaylist.h @@ -272,6 +272,8 @@ enum menu_displaylist_ctl_state DISPLAYLIST_CONTENT_SETTINGS, DISPLAYLIST_OPTIONS, DISPLAYLIST_OPTIONS_CHEATS, + DISPLAYLIST_OPTIONS_PATCHES, + DISPLAYLIST_OPTIONS_PATCHES_ACTIONS, DISPLAYLIST_OPTIONS_REMAPPINGS, DISPLAYLIST_OPTIONS_REMAPPINGS_PORT, DISPLAYLIST_OPTIONS_DISK, diff --git a/menu/menu_driver.h b/menu/menu_driver.h index 1428fc9af38..0b2b6a4de6f 100644 --- a/menu/menu_driver.h +++ b/menu/menu_driver.h @@ -56,6 +56,10 @@ RETRO_BEGIN_DECLS #define MAX_CHEAT_COUNTERS 6000 #endif +#ifndef MAX_PATCH_COUNTERS +#define MAX_PATCH_COUNTERS 8192 +#endif + #define SCROLL_INDEX_SIZE (2 * (26 + 2) + 1) #ifdef EMSCRIPTEN @@ -165,6 +169,11 @@ enum menu_settings_type #endif MENU_SETTING_ACTION_CORE_DISK_OPTIONS, MENU_SETTING_ACTION_CORE_SHADER_OPTIONS, + MENU_SETTING_ACTION_PATCHES, + MENU_SETTING_ACTION_PATCH_ADD, + MENU_SETTING_ACTION_PATCH_MOVE_UP, + MENU_SETTING_ACTION_PATCH_MOVE_DOWN, + MENU_SETTING_ACTION_PATCH_REMOVE, MENU_SETTING_ACTION_SAVESTATE, MENU_SETTING_ACTION_LOADSTATE, MENU_SETTING_ACTION_PLAYREPLAY, @@ -245,6 +254,10 @@ enum menu_settings_type MENU_SETTINGS_PERF_COUNTERS_END = MENU_SETTINGS_PERF_COUNTERS_BEGIN + (MAX_COUNTERS - 1), MENU_SETTINGS_CHEAT_BEGIN, MENU_SETTINGS_CHEAT_END = MENU_SETTINGS_CHEAT_BEGIN + (MAX_CHEAT_COUNTERS - 1), + MENU_SETTINGS_PATCH_BEGIN, + MENU_SETTINGS_PATCH_END = MENU_SETTINGS_PATCH_BEGIN + (MAX_PATCH_COUNTERS - 1), + MENU_SETTINGS_PATCH_CANDIDATE_BEGIN, + MENU_SETTINGS_PATCH_CANDIDATE_END = MENU_SETTINGS_PATCH_CANDIDATE_BEGIN + (MAX_PATCH_COUNTERS - 1), MENU_SETTINGS_INPUT_LIBRETRO_DEVICE, MENU_SETTINGS_INPUT_ANALOG_DPAD_MODE, diff --git a/menu/menu_setting.c b/menu/menu_setting.c index 22efacec5d8..a86ce03bd84 100644 --- a/menu/menu_setting.c +++ b/menu/menu_setting.c @@ -24620,6 +24620,21 @@ static bool setting_append_list( general_read_handler); (*list)[list_info->index - 1].action_start = directory_action_start_generic; + CONFIG_DIR( + list, list_info, + settings->paths.directory_patches, + sizeof(settings->paths.directory_patches), + MENU_ENUM_LABEL_PATCHES_DIRECTORY, + MENU_ENUM_LABEL_VALUE_PATCHES_DIRECTORY, + "", + MENU_ENUM_LABEL_VALUE_DIRECTORY_DEFAULT, + &group_info, + &subgroup_info, + parent_group, + general_write_handler, + general_read_handler); + (*list)[list_info->index - 1].action_start = directory_action_start_generic; + CONFIG_DIR( list, list_info, settings->paths.directory_menu_content, diff --git a/msg_hash.h b/msg_hash.h index e9db968da69..8a7d1ef83ec 100644 --- a/msg_hash.h +++ b/msg_hash.h @@ -1567,6 +1567,7 @@ enum msg_hash_enums MENU_LABEL(MENU_KIOSK_MODE_PASSWORD), MENU_LABEL(RUN_MUSIC), MENU_LABEL(RUN), + MENU_LABEL(PATCHES), MENU_LABEL(MENU_WALLPAPER), MENU_LABEL(DYNAMIC_WALLPAPER), MENU_LABEL(PAUSE_NONACTIVE), @@ -2736,6 +2737,7 @@ enum msg_hash_enums MENU_LABEL(SAVESTATE_DIRECTORY), MENU_LABEL(DYNAMIC_WALLPAPERS_DIRECTORY), MENU_LABEL(THUMBNAILS_DIRECTORY), + MENU_LABEL(PATCHES_DIRECTORY), MENU_LABEL(SLOWMOTION_RATIO), MENU_LABEL(RUN_AHEAD_UNSUPPORTED), diff --git a/tasks/task_patch.c b/tasks/task_patch.c index 9ce81246f96..3fb33e9f68f 100644 --- a/tasks/task_patch.c +++ b/tasks/task_patch.c @@ -28,6 +28,7 @@ #include #include #include +#include #include @@ -35,6 +36,16 @@ #include "../msg_hash.h" #include "../verbosity.h" #include "../configuration.h" +#include "../defaults.h" +#include "../paths.h" +#include "../playlist.h" +#ifdef HAVE_MENU +#include "../menu/menu_driver.h" +#endif + +#ifndef MAX_PATCH_COUNTERS +#define MAX_PATCH_COUNTERS 8192 +#endif #ifdef HAVE_XDELTA #include "../deps/xdelta3/xdelta3.h" @@ -862,6 +873,401 @@ static bool try_xdelta_patch(bool allow_xdelta, return false; } +static bool patch_stack_get_selected_playlist_entry( + const struct playlist_entry **entry_out) +{ +#ifdef HAVE_MENU + playlist_t *playlist = playlist_get_cached(); + struct menu_state *menu_st = menu_state_get_ptr(); + menu_handle_t *menu = menu_st ? menu_st->driver_data : NULL; + + if (!entry_out || !menu || !playlist) + return false; + + playlist_get_index(playlist, menu->rpl_entry_selection_ptr, entry_out); + if (*entry_out && !string_is_empty((*entry_out)->path)) + return true; +#endif + + return false; +} + +static bool patch_stack_resolve_patches_root_dir( + char *patch_root, size_t patch_root_len) +{ + settings_t *settings = config_get_ptr(); + const char *dir_patches = NULL; + const char *dir_port = g_defaults.dirs[DEFAULT_DIR_PORT]; + + if (!patch_root || !patch_root_len || !settings) + return false; + + dir_patches = settings->paths.directory_patches; + if (!string_is_empty(dir_patches)) + strlcpy(patch_root, dir_patches, patch_root_len); + else if (!string_is_empty(dir_port)) + fill_pathname_join_special(patch_root, dir_port, "patches", patch_root_len); + else + fill_pathname_expand_special(patch_root, ":/patches", patch_root_len); + + return !string_is_empty(patch_root); +} + +static bool patch_stack_resolve_selected_patches_file( + char *patch_file, size_t patch_file_len) +{ + const char *content_path = NULL; + const struct playlist_entry *entry = NULL; + char rom_path[PATH_MAX_LENGTH]; + + if (!patch_file || !patch_file_len) + return false; + + if (patch_stack_get_selected_playlist_entry(&entry) + && !string_is_empty(entry->path)) + strlcpy(rom_path, entry->path, sizeof(rom_path)); + else + { + content_path = path_get(RARCH_PATH_CONTENT); + if (string_is_empty(content_path)) + return false; + + strlcpy(rom_path, content_path, sizeof(rom_path)); + } + + fill_pathname(patch_file, rom_path, ".patches", patch_file_len); + + return true; +} + +bool patch_stack_load_selected_patches_list( + struct string_list **selected) +{ + char patch_file[PATH_MAX_LENGTH]; + RFILE *file = NULL; + struct string_list *list; + + if (!selected) + return false; + if (!patch_stack_resolve_selected_patches_file(patch_file, sizeof(patch_file))) + return false; + + list = string_list_new(); + if (!list) + return false; + + if (!path_is_valid(patch_file)) + { + *selected = list; + return true; + } + + file = filestream_open(patch_file, + RETRO_VFS_FILE_ACCESS_READ, + RETRO_VFS_FILE_ACCESS_HINT_NONE); + + if (!file) + { + string_list_free(list); + return false; + } + + while (!filestream_eof(file)) + { + char *line = filestream_getline(file); + union string_list_elem_attr attr; + + attr.i = 0; + + if (!line) + break; + + string_trim_whitespace(line); + + if ( !string_is_empty(line) + && line[0] != '#' + && string_list_find_elem(list, line) == 0) + string_list_append(list, line, attr); + + free(line); + } + + filestream_close(file); + *selected = list; + return true; +} + +static bool patch_stack_save_selected_file(const struct string_list *selected) +{ + size_t i; + RFILE *file = NULL; + char patch_file[PATH_MAX_LENGTH]; + char patch_file_dir[PATH_MAX_LENGTH]; + + if (!selected) + return false; + if (!patch_stack_resolve_selected_patches_file(patch_file, sizeof(patch_file))) + return false; + + fill_pathname_parent_dir(patch_file_dir, patch_file, sizeof(patch_file_dir)); + if ( !string_is_empty(patch_file_dir) + && !path_is_directory(patch_file_dir) + && !path_mkdir(patch_file_dir)) + return false; + + file = filestream_open(patch_file, + RETRO_VFS_FILE_ACCESS_WRITE, + RETRO_VFS_FILE_ACCESS_HINT_NONE); + + if (!file) + return false; + + for (i = 0; i < selected->size; i++) + filestream_printf(file, "%s\n", selected->elems[i].data); + + filestream_close(file); + return true; +} + +bool patch_stack_add(const char *candidate_path) +{ + struct string_list *selected = NULL; + const char *candidate = candidate_path; + size_t root_len; + char patch_root[PATH_MAX_LENGTH]; + char patch_path[PATH_MAX_LENGTH]; + union string_list_elem_attr attr; + + attr.i = 0; + + if (string_is_empty(candidate_path)) + return false; + if (!patch_stack_resolve_patches_root_dir(patch_root, sizeof(patch_root))) + return false; + + if (path_is_absolute(candidate_path)) + { + root_len = strlen(patch_root); + if (!string_starts_with_size(candidate_path, patch_root, root_len)) + return false; + + candidate = candidate_path + root_len; + if (PATH_CHAR_IS_SLASH(*candidate)) + candidate++; + } + + if (string_is_empty(candidate)) + return false; + + if (!patch_stack_load_selected_patches_list(&selected) || !selected) + return false; + + fill_pathname_join_special(patch_path, + patch_root, candidate, sizeof(patch_path)); + if (!path_is_valid(patch_path)) + { + string_list_free(selected); + return false; + } + if (string_list_find_elem(selected, candidate) != 0) + { + string_list_free(selected); + return false; + } + if (!string_list_append(selected, candidate, attr)) + { + string_list_free(selected); + return false; + } + if (!patch_stack_save_selected_file(selected)) + { + string_list_free(selected); + return false; + } + + string_list_free(selected); + return true; +} + +bool patch_stack_resolve_selected_patches_dir(char *dir, size_t len) +{ + const struct playlist_entry *entry = NULL; + char patch_root[PATH_MAX_LENGTH]; + char selected_dir[PATH_MAX_LENGTH]; + char system_name[NAME_MAX_LENGTH]; + char rom_name[NAME_MAX_LENGTH]; + + if (!dir || !len) + return false; + if (!patch_stack_resolve_patches_root_dir(patch_root, sizeof(patch_root))) + return false; + + selected_dir[0] = '\0'; + + if (patch_stack_get_selected_playlist_entry(&entry) + && !string_is_empty(entry->db_name) + && !string_is_empty(entry->path)) + { + strlcpy(rom_name, path_basename(entry->path), sizeof(rom_name)); + path_remove_extension(rom_name); + + strlcpy(system_name, entry->db_name, sizeof(system_name)); + path_remove_extension(system_name); + + fill_pathname_join_special(selected_dir, + patch_root, system_name, sizeof(selected_dir)); + fill_pathname_join_special(selected_dir, + selected_dir, rom_name, sizeof(selected_dir)); + } + + if (!string_is_empty(selected_dir) && path_is_directory(selected_dir)) + strlcpy(dir, selected_dir, len); + else + strlcpy(dir, patch_root, len); + + return !string_is_empty(dir); +} + +bool patch_stack_remove(size_t idx) +{ + size_t i; + struct string_list *selected = NULL; + + if (!patch_stack_load_selected_patches_list(&selected) || !selected) + return false; + if (idx >= selected->size) + { + string_list_free(selected); + return false; + } + + free(selected->elems[idx].data); + for (i = idx; i + 1 < selected->size; i++) + selected->elems[i] = selected->elems[i + 1]; + selected->size--; + + if (!patch_stack_save_selected_file(selected)) + { + string_list_free(selected); + return false; + } + + string_list_free(selected); + return true; +} + +bool patch_stack_move_up(size_t idx) +{ + struct string_list_elem tmp; + struct string_list *selected = NULL; + + if (idx == 0) + return false; + if (!patch_stack_load_selected_patches_list(&selected) || !selected) + return false; + if (idx >= selected->size) + { + string_list_free(selected); + return false; + } + + tmp = selected->elems[idx - 1]; + selected->elems[idx - 1] = selected->elems[idx]; + selected->elems[idx] = tmp; + + if (!patch_stack_save_selected_file(selected)) + { + string_list_free(selected); + return false; + } + + string_list_free(selected); + return true; +} + +bool patch_stack_move_down(size_t idx) +{ + struct string_list_elem tmp; + struct string_list *selected = NULL; + + if (!patch_stack_load_selected_patches_list(&selected) || !selected) + return false; + if (idx + 1 >= selected->size) + { + string_list_free(selected); + return false; + } + + tmp = selected->elems[idx + 1]; + selected->elems[idx + 1] = selected->elems[idx]; + selected->elems[idx] = tmp; + + if (!patch_stack_save_selected_file(selected)) + { + string_list_free(selected); + return false; + } + string_list_free(selected); + return true; +} + +static bool patch_stack_apply_explicit(uint8_t **buf, ssize_t *size) +{ + size_t i; + bool applied_any = false; + char patch_root[PATH_MAX_LENGTH]; + struct string_list *selected = NULL; + + if (!patch_stack_resolve_patches_root_dir(patch_root, sizeof(patch_root))) + return false; + if (!patch_stack_load_selected_patches_list(&selected) + || !selected) + return false; + if (selected->size == 0) + { + string_list_free(selected); + return false; + } + + for (i = 0; i < selected->size; i++) + { + bool patch_ok = false; + const char *patch_name = selected->elems[i].data; + const char *ext = path_get_extension(patch_name); + char patch_fullpath[PATH_MAX_LENGTH]; + + fill_pathname_join_special(patch_fullpath, + patch_root, patch_name, sizeof(patch_fullpath)); + + if (!path_is_valid(patch_fullpath)) + { + RARCH_WARN("[Patch] Missing patch in stack: %s\n", patch_fullpath); + continue; + } + + if (string_is_equal_case_insensitive(ext, "ips")) + patch_ok = try_ips_patch(true, patch_fullpath, buf, size); + else if (string_is_equal_case_insensitive(ext, "bps")) + patch_ok = try_bps_patch(true, patch_fullpath, buf, size); + else if (string_is_equal_case_insensitive(ext, "ups")) + patch_ok = try_ups_patch(true, patch_fullpath, buf, size); + else if (string_is_equal_case_insensitive(ext, "xdelta")) + patch_ok = try_xdelta_patch(true, patch_fullpath, buf, size); + + if (!patch_ok) + { + RARCH_ERR("[Patch] Failed applying patch from stack: %s\n", patch_fullpath); + string_list_free(selected); + return applied_any; + } + + applied_any = true; + } + + string_list_free(selected); + return applied_any; +} + /** * patch_content: * @buf : buffer of the content file. @@ -888,6 +1294,9 @@ bool patch_content( bool allow_bps = !is_ups_pref && !is_ips_pref && !is_xdelta_pref; bool allow_xdelta = !is_bps_pref && !is_ups_pref && !is_ips_pref; + if (patch_stack_apply_explicit(buf, size)) + return true; + if ( (unsigned)is_ips_pref + (unsigned)is_bps_pref + (unsigned)is_ups_pref diff --git a/tasks/tasks_internal.h b/tasks/tasks_internal.h index e5dc47cf195..0e58d2040f9 100644 --- a/tasks/tasks_internal.h +++ b/tasks/tasks_internal.h @@ -215,6 +215,17 @@ bool patch_content( uint8_t **buf, void *data); +struct string_list; + +#ifdef HAVE_PATCH +bool patch_stack_load_selected_patches_list(struct string_list **selected); +bool patch_stack_resolve_selected_patches_dir(char *dir, size_t len); +bool patch_stack_add(const char *candidate_path); +bool patch_stack_remove(size_t idx); +bool patch_stack_move_up(size_t idx); +bool patch_stack_move_down(size_t idx); +#endif + bool task_check_decompress(const char *source_file); void *task_push_decompress(