From fba4a8e837400d2969f8169db635a6b62889391c Mon Sep 17 00:00:00 2001 From: jdgleaver Date: Mon, 27 Jul 2020 14:43:54 +0100 Subject: [PATCH 01/17] (RGUI) Add optional 'toggle switch' icons --- config.def.h | 1 + configuration.c | 1 + configuration.h | 1 + intl/msg_hash_lbl.h | 4 + intl/msg_hash_us.h | 8 + menu/cbs/menu_cbs_sublabel.c | 4 + menu/drivers/rgui.c | 304 +++++++++++++++++++++++++++++------ menu/menu_displaylist.c | 1 + menu/menu_setting.c | 15 ++ msg_hash.h | 1 + 10 files changed, 287 insertions(+), 53 deletions(-) diff --git a/config.def.h b/config.def.h index 70564d37e7..7ae77928c6 100644 --- a/config.def.h +++ b/config.def.h @@ -571,6 +571,7 @@ static const bool rgui_shadows = false; static const unsigned rgui_particle_effect = RGUI_PARTICLE_EFFECT_NONE; #define DEFAULT_RGUI_PARTICLE_EFFECT_SPEED 1.0f static const bool rgui_extended_ascii = false; +#define DEFAULT_RGUI_SWITCH_ICONS true #endif #ifdef HAVE_MENU diff --git a/configuration.c b/configuration.c index 042378cfbb..d721e7006f 100644 --- a/configuration.c +++ b/configuration.c @@ -1635,6 +1635,7 @@ static struct config_bool_setting *populate_settings_bool( SETTING_BOOL("rgui_inline_thumbnails", &settings->bools.menu_rgui_inline_thumbnails, true, rgui_inline_thumbnails, false); SETTING_BOOL("rgui_swap_thumbnails", &settings->bools.menu_rgui_swap_thumbnails, true, rgui_swap_thumbnails, false); SETTING_BOOL("rgui_extended_ascii", &settings->bools.menu_rgui_extended_ascii, true, rgui_extended_ascii, false); + SETTING_BOOL("rgui_switch_icons", &settings->bools.menu_rgui_switch_icons, true, DEFAULT_RGUI_SWITCH_ICONS, false); #endif #ifdef HAVE_XMB SETTING_BOOL("xmb_shadows_enable", &settings->bools.menu_xmb_shadows_enable, true, DEFAULT_XMB_SHADOWS_ENABLE, false); diff --git a/configuration.h b/configuration.h index 5836115473..fc4617cb71 100644 --- a/configuration.h +++ b/configuration.h @@ -219,6 +219,7 @@ typedef struct settings bool menu_rgui_inline_thumbnails; bool menu_rgui_swap_thumbnails; bool menu_rgui_extended_ascii; + bool menu_rgui_switch_icons; bool menu_xmb_shadows_enable; bool menu_xmb_vertical_thumbnails; bool menu_content_show_settings; diff --git a/intl/msg_hash_lbl.h b/intl/msg_hash_lbl.h index 91107f61f9..87b2cc95e7 100644 --- a/intl/msg_hash_lbl.h +++ b/intl/msg_hash_lbl.h @@ -3894,6 +3894,10 @@ MSG_HASH( MENU_ENUM_LABEL_MENU_RGUI_EXTENDED_ASCII, "rgui_extended_ascii" ) +MSG_HASH( + MENU_ENUM_LABEL_MENU_RGUI_SWITCH_ICONS, + "rgui_switch_icons" + ) MSG_HASH( MENU_ENUM_LABEL_CONTENT_SHOW_REWIND, "menu_show_rewind_settings" diff --git a/intl/msg_hash_us.h b/intl/msg_hash_us.h index 2164a93f3a..1cdbfbfc17 100644 --- a/intl/msg_hash_us.h +++ b/intl/msg_hash_us.h @@ -7624,6 +7624,14 @@ MSG_HASH( MENU_ENUM_SUBLABEL_MENU_RGUI_EXTENDED_ASCII, "Enable display of non-standard ASCII characters. Required for compatibility with certain non-English Western languages. Has a moderate performance impact." ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_MENU_RGUI_SWITCH_ICONS, + "Show Switch Icons" + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_MENU_RGUI_SWITCH_ICONS, + "Use icons instead of ON/OFF text to represent 'toggle switch' menu settings entries." + ) /* RGUI: Settings Options */ diff --git a/menu/cbs/menu_cbs_sublabel.c b/menu/cbs/menu_cbs_sublabel.c index b527d241f1..002c77f9fd 100644 --- a/menu/cbs/menu_cbs_sublabel.c +++ b/menu/cbs/menu_cbs_sublabel.c @@ -852,6 +852,7 @@ DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_playlist_use_old_format, DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_playlist_compression, MENU_ENUM_SUBLABEL_PLAYLIST_COMPRESSION) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_menu_rgui_full_width_layout, MENU_ENUM_SUBLABEL_MENU_RGUI_FULL_WIDTH_LAYOUT) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_menu_rgui_extended_ascii, MENU_ENUM_SUBLABEL_MENU_RGUI_EXTENDED_ASCII) +DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_menu_rgui_switch_icons, MENU_ENUM_SUBLABEL_MENU_RGUI_SWITCH_ICONS) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_thumbnails_updater_list, MENU_ENUM_SUBLABEL_THUMBNAILS_UPDATER_LIST) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_pl_thumbnails_updater_list, MENU_ENUM_SUBLABEL_PL_THUMBNAILS_UPDATER_LIST) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_help_send_debug_info, MENU_ENUM_SUBLABEL_HELP_SEND_DEBUG_INFO) @@ -3822,6 +3823,9 @@ int menu_cbs_init_bind_sublabel(menu_file_list_cbs_t *cbs, case MENU_ENUM_LABEL_MENU_RGUI_EXTENDED_ASCII: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_menu_rgui_extended_ascii); break; + case MENU_ENUM_LABEL_MENU_RGUI_SWITCH_ICONS: + BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_menu_rgui_switch_icons); + break; case MENU_ENUM_LABEL_THUMBNAILS_UPDATER_LIST: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_thumbnails_updater_list); break; diff --git a/menu/drivers/rgui.c b/menu/drivers/rgui.c index 6865fb285e..9bc466f6fc 100644 --- a/menu/drivers/rgui.c +++ b/menu/drivers/rgui.c @@ -550,6 +550,20 @@ typedef struct float d; } rgui_particle_t; +/* Defines all possible entry value types + * > Note: These are not necessarily 'values', + * but they correspond to the object drawn in + * the 'value' location when rendering + * menu lists */ +enum rgui_entry_value_type +{ + RGUI_ENTRY_VALUE_NONE = 0, + RGUI_ENTRY_VALUE_TEXT, + RGUI_ENTRY_VALUE_SWITCH_ON, + RGUI_ENTRY_VALUE_SWITCH_OFF, + RGUI_ENTRY_VALUE_CHECKMARK +}; + typedef struct { bool bg_modified; @@ -625,7 +639,13 @@ enum rgui_symbol_type RGUI_SYMBOL_BATTERY_60, RGUI_SYMBOL_BATTERY_40, RGUI_SYMBOL_BATTERY_20, - RGUI_SYMBOL_CHECKMARK + RGUI_SYMBOL_CHECKMARK, + RGUI_SYMBOL_SWITCH_ON_LEFT, + RGUI_SYMBOL_SWITCH_ON_CENTRE, + RGUI_SYMBOL_SWITCH_ON_RIGHT, + RGUI_SYMBOL_SWITCH_OFF_LEFT, + RGUI_SYMBOL_SWITCH_OFF_CENTRE, + RGUI_SYMBOL_SWITCH_OFF_RIGHT }; /* All custom symbols must have dimensions @@ -789,6 +809,78 @@ static const uint8_t rgui_symbol_data_checkmark[FONT_WIDTH * FONT_HEIGHT] = { 0, 1, 1, 0, 0, 0, 0, 0, 0, 0}; +static const uint8_t rgui_symbol_data_switch_on_left[FONT_WIDTH * FONT_HEIGHT] = { + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 1, 1, 1, 1, + 1, 1, 1, 1, 1, + 0, 1, 1, 1, 1, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, /* Baseline */ + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0}; + +static const uint8_t rgui_symbol_data_switch_on_centre[FONT_WIDTH * FONT_HEIGHT] = { + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 1, 1, 1, 1, 0, + 1, 1, 1, 1, 0, + 1, 1, 1, 1, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, /* Baseline */ + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0}; + +static const uint8_t rgui_symbol_data_switch_on_right[FONT_WIDTH * FONT_HEIGHT] = { + 0, 0, 0, 0, 0, + 0, 1, 1, 1, 0, + 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, + 0, 1, 1, 1, 0, /* Baseline */ + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0}; + +static const uint8_t rgui_symbol_data_switch_off_left[FONT_WIDTH * FONT_HEIGHT] = { + 0, 0, 0, 0, 0, + 0, 1, 1, 1, 0, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 0, 1, 1, 1, 0, /* Baseline */ + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0}; + +static const uint8_t rgui_symbol_data_switch_off_centre[FONT_WIDTH * FONT_HEIGHT] = { + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 1, 1, 1, 1, + 0, 1, 0, 0, 0, + 0, 1, 1, 1, 1, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, /* Baseline */ + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0}; + +static const uint8_t rgui_symbol_data_switch_off_right[FONT_WIDTH * FONT_HEIGHT] = { + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 1, 1, 1, 1, 0, + 0, 0, 0, 0, 1, + 1, 1, 1, 1, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, /* Baseline */ + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0}; + /* ============================== * Custom Symbols (glyphs) END * ============================== */ @@ -2638,6 +2730,18 @@ static const uint8_t *rgui_get_symbol_data(enum rgui_symbol_type symbol) return rgui_symbol_data_battery_20; case RGUI_SYMBOL_CHECKMARK: return rgui_symbol_data_checkmark; + case RGUI_SYMBOL_SWITCH_ON_LEFT: + return rgui_symbol_data_switch_on_left; + case RGUI_SYMBOL_SWITCH_ON_CENTRE: + return rgui_symbol_data_switch_on_centre; + case RGUI_SYMBOL_SWITCH_ON_RIGHT: + return rgui_symbol_data_switch_on_right; + case RGUI_SYMBOL_SWITCH_OFF_LEFT: + return rgui_symbol_data_switch_off_left; + case RGUI_SYMBOL_SWITCH_OFF_CENTRE: + return rgui_symbol_data_switch_off_centre; + case RGUI_SYMBOL_SWITCH_OFF_RIGHT: + return rgui_symbol_data_switch_off_right; default: break; } @@ -3228,6 +3332,58 @@ static void rgui_render_osk( } } +static void rgui_render_toggle_switch(unsigned fb_width, int x, int y, + bool on, uint16_t color, uint16_t shadow_color) +{ + int x_current = x; + + /* Toggle switch is just 3 adjacent symbols + * > Note that we indent the left/right symbols + * by 1 pixel, to avoid the gap that is normally + * present between symbols/characters */ + blit_symbol(fb_width, x_current + 1, y, + on ? RGUI_SYMBOL_SWITCH_ON_LEFT : RGUI_SYMBOL_SWITCH_OFF_LEFT, + color, shadow_color); + x_current += FONT_WIDTH_STRIDE; + + blit_symbol(fb_width, x_current, y, + on ? RGUI_SYMBOL_SWITCH_ON_CENTRE : RGUI_SYMBOL_SWITCH_OFF_CENTRE, + color, shadow_color); + x_current += FONT_WIDTH_STRIDE; + + blit_symbol(fb_width, x_current - 1, y, + on ? RGUI_SYMBOL_SWITCH_ON_RIGHT : RGUI_SYMBOL_SWITCH_OFF_RIGHT, + color, shadow_color); +} + +static enum rgui_entry_value_type rgui_get_entry_value_type( + const char *entry_value, bool entry_checked, + bool switch_icons_enabled) +{ + enum rgui_entry_value_type value_type = RGUI_ENTRY_VALUE_NONE; + + if (!string_is_empty(entry_value)) + { + value_type = RGUI_ENTRY_VALUE_TEXT; + + if (switch_icons_enabled) + { + /* Toggle switch off */ + if (string_is_equal(entry_value, msg_hash_to_str(MENU_ENUM_LABEL_DISABLED)) || + string_is_equal(entry_value, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_OFF))) + value_type = RGUI_ENTRY_VALUE_SWITCH_OFF; + /* Toggle switch on */ + else if (string_is_equal(entry_value, msg_hash_to_str(MENU_ENUM_LABEL_ENABLED)) || + string_is_equal(entry_value, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_ON))) + value_type = RGUI_ENTRY_VALUE_SWITCH_ON; + } + } + else if (entry_checked) + value_type = RGUI_ENTRY_VALUE_CHECKMARK; + + return value_type; +} + #if defined(GEKKO) /* Need to forward declare this for the Wii build * (I'm not going to reorder the functions and mess @@ -3262,6 +3418,7 @@ static void rgui_render(void *data, bool use_smooth_ticker = settings->bools.menu_ticker_smooth; bool rgui_swap_thumbnails = settings->bools.menu_rgui_swap_thumbnails; bool rgui_full_width_layout = settings->bools.menu_rgui_full_width_layout; + bool rgui_switch_icons = settings->bools.menu_rgui_switch_icons; bool menu_show_sublabels = settings->bools.menu_show_sublabels; bool video_fullscreen = settings->bools.video_fullscreen; bool menu_mouse_enable = settings->bools.menu_mouse_enable; @@ -3656,12 +3813,13 @@ static void rgui_render(void *data, char entry_title_buf[255]; char type_str_buf[255]; menu_entry_t entry; - const char *entry_label = NULL; - const char *entry_value = NULL; - size_t entry_title_max_len = 0; - unsigned entry_value_len = 0; - bool entry_selected = (i == selection); - uint16_t entry_color = entry_selected ? + const char *entry_label = NULL; + const char *entry_value = NULL; + size_t entry_title_max_len = 0; + unsigned entry_value_len = 0; + enum rgui_entry_value_type entry_value_type = RGUI_ENTRY_VALUE_NONE; + bool entry_selected = (i == selection); + uint16_t entry_color = entry_selected ? rgui->colors.hover_color : rgui->colors.normal_color; if (i > (selection + 100)) @@ -3721,23 +3879,45 @@ static void rgui_render(void *data, entry_title_max_len -= (thumbnail_width / FONT_WIDTH_STRIDE) + 1; } - /* Determine whether entry has a value component */ - if (!string_is_empty(entry_value)) - { - if (rgui_full_width_layout) - { - /* Resize fields according to actual length of value string */ - entry_value_len = (unsigned)strlen(entry_value); - entry_value_len = (entry_value_len - > rgui_term_layout.value_maxlen) - ? rgui_term_layout.value_maxlen - : entry_value_len; - } - else /* Use classic fixed width layout */ - entry_value_len = entry.spacing; + /* Get 'type' of entry value component */ + entry_value_type = rgui_get_entry_value_type( + entry_value, entry.checked, rgui_switch_icons); - /* Update width of entry title field */ - entry_title_max_len -= entry_value_len + 2; + switch (entry_value_type) + { + case RGUI_ENTRY_VALUE_TEXT: + /* Resize fields according to actual length + * of value string */ + if (rgui_full_width_layout) + { + entry_value_len = (unsigned)strlen(entry_value); + entry_value_len = (entry_value_len + > rgui_term_layout.value_maxlen) ? + rgui_term_layout.value_maxlen : + entry_value_len; + } + /* Use classic fixed width layout */ + else + entry_value_len = entry.spacing; + + /* Update width of entry title field */ + entry_title_max_len -= entry_value_len + 2; + break; + case RGUI_ENTRY_VALUE_SWITCH_ON: + case RGUI_ENTRY_VALUE_SWITCH_OFF: + /* Switch icon is 3 characters wide + * (if using classic fixed width layout, + * set maximum width to ensure icon is + * aligned with left hand edge of values + * column) */ + entry_value_len = rgui_full_width_layout ? + 3 : RGUI_ENTRY_VALUE_MAXLEN; + + /* Update width of entry title field */ + entry_title_max_len -= entry_value_len + 2; + break; + default: + break; } /* Format entry title string */ @@ -3769,41 +3949,59 @@ static void rgui_render(void *data, entry_color, rgui->colors.shadow_color); /* Print entry value, if required */ - if (entry_value_len > 0) + switch (entry_value_type) { - /* Format entry value string */ - if (use_smooth_ticker) - { - ticker_smooth.field_width = entry_value_len * FONT_WIDTH_STRIDE; - ticker_smooth.src_str = entry_value; - ticker_smooth.dst_str = type_str_buf; - ticker_smooth.dst_str_len = sizeof(type_str_buf); - ticker_smooth.x_offset = &ticker_x_offset; + case RGUI_ENTRY_VALUE_TEXT: + /* Format entry value string */ + if (use_smooth_ticker) + { + ticker_smooth.field_width = entry_value_len * FONT_WIDTH_STRIDE; + ticker_smooth.src_str = entry_value; + ticker_smooth.dst_str = type_str_buf; + ticker_smooth.dst_str_len = sizeof(type_str_buf); + ticker_smooth.x_offset = &ticker_x_offset; - gfx_animation_ticker_smooth(&ticker_smooth); - } - else - { - ticker.s = type_str_buf; - ticker.len = entry_value_len; - ticker.str = entry_value; + gfx_animation_ticker_smooth(&ticker_smooth); + } + else + { + ticker.s = type_str_buf; + ticker.len = entry_value_len; + ticker.str = entry_value; - gfx_animation_ticker(&ticker); - } + gfx_animation_ticker(&ticker); + } - /* Print entry value */ - blit_line(rgui, - fb_width, - ticker_x_offset + term_end_x - ((entry_value_len + 1) * FONT_WIDTH_STRIDE), - y, - type_str_buf, - entry_color, rgui->colors.shadow_color); + /* Print entry value */ + blit_line(rgui, + fb_width, + ticker_x_offset + term_end_x - ((entry_value_len + 1) * FONT_WIDTH_STRIDE), + y, + type_str_buf, + entry_color, rgui->colors.shadow_color); + break; + case RGUI_ENTRY_VALUE_SWITCH_ON: + rgui_render_toggle_switch(fb_width, + term_end_x - ((entry_value_len + 1) * FONT_WIDTH_STRIDE), y, + true, + entry_color, rgui->colors.shadow_color); + break; + case RGUI_ENTRY_VALUE_SWITCH_OFF: + rgui_render_toggle_switch(fb_width, + term_end_x - ((entry_value_len + 1) * FONT_WIDTH_STRIDE), y, + false, + entry_color, rgui->colors.shadow_color); + break; + case RGUI_ENTRY_VALUE_CHECKMARK: + /* Print marker for currently selected + * item in drop-down lists */ + blit_symbol(fb_width, x + FONT_WIDTH_STRIDE, y, + RGUI_SYMBOL_CHECKMARK, + entry_color, rgui->colors.shadow_color); + break; + default: + break; } - /* Print marker for currently selected item in - * drop down lists, if required */ - else if (entry.checked) - blit_symbol(fb_width, x + FONT_WIDTH_STRIDE, y, RGUI_SYMBOL_CHECKMARK, - entry_color, rgui->colors.shadow_color); /* Print selection marker, if required */ if (entry_selected) diff --git a/menu/menu_displaylist.c b/menu/menu_displaylist.c index 6040266448..9133e32d3f 100644 --- a/menu/menu_displaylist.c +++ b/menu/menu_displaylist.c @@ -8572,6 +8572,7 @@ unsigned menu_displaylist_build_list( {MENU_ENUM_LABEL_MENU_TICKER_SMOOTH, PARSE_ONLY_BOOL, true}, {MENU_ENUM_LABEL_OZONE_SCROLL_CONTENT_METADATA, PARSE_ONLY_BOOL, true}, {MENU_ENUM_LABEL_MENU_RGUI_EXTENDED_ASCII, PARSE_ONLY_BOOL, true}, + {MENU_ENUM_LABEL_MENU_RGUI_SWITCH_ICONS, PARSE_ONLY_BOOL, true}, }; for (i = 0; i < ARRAY_SIZE(build_list); i++) diff --git a/menu/menu_setting.c b/menu/menu_setting.c index 0cfec76b40..31a06b5899 100644 --- a/menu/menu_setting.c +++ b/menu/menu_setting.c @@ -13471,6 +13471,21 @@ static bool setting_append_list( general_write_handler, general_read_handler, SD_FLAG_NONE); + + CONFIG_BOOL( + list, list_info, + &settings->bools.menu_rgui_switch_icons, + MENU_ENUM_LABEL_MENU_RGUI_SWITCH_ICONS, + MENU_ENUM_LABEL_VALUE_MENU_RGUI_SWITCH_ICONS, + DEFAULT_RGUI_SWITCH_ICONS, + MENU_ENUM_LABEL_VALUE_OFF, + MENU_ENUM_LABEL_VALUE_ON, + &group_info, + &subgroup_info, + parent_group, + general_write_handler, + general_read_handler, + SD_FLAG_NONE); } if (string_is_equal(settings->arrays.menu_driver, "xmb")) diff --git a/msg_hash.h b/msg_hash.h index 4ef2ee8de1..c8fdf33222 100644 --- a/msg_hash.h +++ b/msg_hash.h @@ -1050,6 +1050,7 @@ enum msg_hash_enums MENU_LABEL(MENU_RGUI_PARTICLE_EFFECT), MENU_LABEL(MENU_RGUI_PARTICLE_EFFECT_SPEED), MENU_LABEL(MENU_RGUI_EXTENDED_ASCII), + MENU_LABEL(MENU_RGUI_SWITCH_ICONS), MENU_LABEL(MENU_LINEAR_FILTER), MENU_LABEL(MENU_HORIZONTAL_ANIMATION), MENU_LABEL(NAVIGATION_WRAPAROUND), From 9744fcb76a20129371bece1c9a46622401383251 Mon Sep 17 00:00:00 2001 From: francescotintori Date: Thu, 23 Jul 2020 20:08:43 +0200 Subject: [PATCH 02/17] Adds base content directory support in playlists. If playlist base content directory does not match configuration parameter 'rgui_browser_directory', all entries paths are automatically fixed to match parameter 'rgui_browser_directory'. Functionality is enabled if new parameter 'playlist_autofix_paths' is enabled. --- config.def.h | 2 + configuration.c | 1 + configuration.h | 1 + core_info.c | 47 +++++++ core_info.h | 2 + intl/msg_hash_lbl.h | 4 + intl/msg_hash_us.h | 8 ++ menu/cbs/menu_cbs_ok.c | 2 + menu/cbs/menu_cbs_sublabel.c | 4 + menu/menu_displaylist.c | 3 + menu/menu_setting.c | 16 +++ msg_hash.h | 1 + playlist.c | 219 ++++++++++++++++++++++++++---- playlist.h | 6 + retroarch.c | 3 + tasks/task_database.c | 2 + tasks/task_netplay_find_content.c | 1 + ui/drivers/qt/qt_playlist.cpp | 6 + 18 files changed, 301 insertions(+), 27 deletions(-) diff --git a/config.def.h b/config.def.h index 70564d37e7..4cd7037eff 100644 --- a/config.def.h +++ b/config.def.h @@ -1055,6 +1055,8 @@ static const int default_content_favorites_size = 200; #define DEFAULT_PLAYLIST_FUZZY_ARCHIVE_MATCH false +#define DEFAULT_PLAYLIST_PORTABLE_PATHS false + /* Show Menu start-up screen on boot. */ #define DEFAULT_MENU_SHOW_START_SCREEN true diff --git a/configuration.c b/configuration.c index 042378cfbb..7f544c9540 100644 --- a/configuration.c +++ b/configuration.c @@ -1722,6 +1722,7 @@ static struct config_bool_setting *populate_settings_bool( SETTING_BOOL("playlist_show_sublabels", &settings->bools.playlist_show_sublabels, true, DEFAULT_PLAYLIST_SHOW_SUBLABELS, false); SETTING_BOOL("playlist_sort_alphabetical", &settings->bools.playlist_sort_alphabetical, true, DEFAULT_PLAYLIST_SORT_ALPHABETICAL, false); SETTING_BOOL("playlist_fuzzy_archive_match", &settings->bools.playlist_fuzzy_archive_match, true, DEFAULT_PLAYLIST_FUZZY_ARCHIVE_MATCH, false); + SETTING_BOOL("playlist_portable_paths", &settings->bools.playlist_portable_paths, true, DEFAULT_PLAYLIST_PORTABLE_PATHS, false); SETTING_BOOL("quit_press_twice", &settings->bools.quit_press_twice, true, DEFAULT_QUIT_PRESS_TWICE, false); SETTING_BOOL("vibrate_on_keypress", &settings->bools.vibrate_on_keypress, true, vibrate_on_keypress, false); diff --git a/configuration.h b/configuration.h index 5836115473..68ae84c71c 100644 --- a/configuration.h +++ b/configuration.h @@ -404,6 +404,7 @@ typedef struct settings bool playlist_sort_alphabetical; bool playlist_show_sublabels; bool playlist_fuzzy_archive_match; + bool playlist_portable_paths; bool quit_press_twice; bool vibrate_on_keypress; diff --git a/core_info.c b/core_info.c index a016d5f0ea..aa92d0156a 100644 --- a/core_info.c +++ b/core_info.c @@ -873,6 +873,53 @@ void core_info_list_get_supported_cores(core_info_list_t *core_info_list, *num_infos = supported; } +/* + * Matches core path A and B "base" filename (ignoring everything after _libretro) + * + * Ex: + * snes9x_libretro.dll and snes9x_libretro_android.so are matched + * snes9x__2005_libretro.dll and snes9x_libretro_android.so are NOT matched + */ +bool core_info_core_file_id_is_equal(const char* core_path_a, const char* core_path_b) +{ + const char *core_path_basename_a = NULL; + const char *extension_pos = NULL; + const char *underscore_pos = NULL; + + if (!core_path_a || !core_path_b) + return false; + + core_path_basename_a = path_basename(core_path_a); + + if (core_path_basename_a) + { + extension_pos = strrchr(core_path_basename_a, '.'); + + if (extension_pos) + { + /* Remove extension */ + *((char*)extension_pos) = '\0'; + + underscore_pos = strrchr(core_path_basename_a, '_'); + + /* Restore extension */ + *((char*)extension_pos) = '.'; + + if (underscore_pos) + { + size_t core_base_file_id_length = underscore_pos - core_path_basename_a; + const char* core_path_basename_b = path_basename(core_path_b); + + if (string_starts_with_size(core_path_basename_a, core_path_basename_b, + core_base_file_id_length)) + return true; + } + } + } + + return false; +} + void core_info_get_name(const char *path, char *s, size_t len, const char *path_info, const char *dir_cores, const char *exts, bool dir_show_hidden_files, diff --git a/core_info.h b/core_info.h index de6e927dd2..3fc042100c 100644 --- a/core_info.h +++ b/core_info.h @@ -220,6 +220,8 @@ bool core_info_get_core_lock(const char *core_path, bool validate_path); core_info_state_t *coreinfo_get_ptr(void); +bool core_info_core_file_id_is_equal(const char* core_path_a, const char* core_path_b); + RETRO_END_DECLS #endif /* CORE_INFO_H_ */ diff --git a/intl/msg_hash_lbl.h b/intl/msg_hash_lbl.h index a79f925857..c249fda41a 100644 --- a/intl/msg_hash_lbl.h +++ b/intl/msg_hash_lbl.h @@ -4292,6 +4292,10 @@ MSG_HASH( MENU_ENUM_LABEL_PLAYLIST_SUBLABEL_LAST_PLAYED_STYLE, "playlist_sublabel_last_played_style" ) +MSG_HASH( + MENU_ENUM_LABEL_PLAYLIST_PORTABLE_PATHS, + "playlist_portable_paths" + ) MSG_HASH( MENU_ENUM_LABEL_HELP_SEND_DEBUG_INFO, "help_send_debug_info" diff --git a/intl/msg_hash_us.h b/intl/msg_hash_us.h index 2164a93f3a..d6da039cba 100644 --- a/intl/msg_hash_us.h +++ b/intl/msg_hash_us.h @@ -4907,6 +4907,14 @@ MSG_HASH( MENU_ENUM_SUBLABEL_PLAYLIST_MANAGER_LIST, "Perform maintenance tasks on playlists." ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_PLAYLIST_PORTABLE_PATHS, + "Portable Playlists" + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_PLAYLIST_PORTABLE_PATHS, + "When enabled, and 'File Browser' directory is also selected, the current value of parameter 'File Browser' is saved in the playlist. When the playlist is loaded on another system where the same option is enabled, the value of parameter 'File Browser' is compared with the playlist value; if different, the playlist entries' paths are automatically fixed." + ) /* Settings > Playlists > Playlist Management */ diff --git a/menu/cbs/menu_cbs_ok.c b/menu/cbs/menu_cbs_ok.c index e93ebd0467..4ed5245e86 100644 --- a/menu/cbs/menu_cbs_ok.c +++ b/menu/cbs/menu_cbs_ok.c @@ -2054,6 +2054,7 @@ static int action_ok_playlist_entry_collection(const char *path, playlist_config.old_format = settings->bools.playlist_use_old_format; playlist_config.compress = settings->bools.playlist_compression; playlist_config.fuzzy_archive_match = settings->bools.playlist_fuzzy_archive_match; + playlist_config_set_base_content_directory(&playlist_config, settings->bools.playlist_portable_paths ? settings->paths.directory_menu_content : NULL); content_path[0] = '\0'; content_label[0] = '\0'; @@ -6207,6 +6208,7 @@ static int action_ok_manual_content_scan_start(const char *path, playlist_config.old_format = settings->bools.playlist_use_old_format; playlist_config.compress = settings->bools.playlist_compression; playlist_config.fuzzy_archive_match = settings->bools.playlist_fuzzy_archive_match; + playlist_config_set_base_content_directory(&playlist_config, settings->bools.playlist_portable_paths ? settings->paths.directory_menu_content : NULL); task_push_manual_content_scan(&playlist_config, directory_playlist); return 0; diff --git a/menu/cbs/menu_cbs_sublabel.c b/menu/cbs/menu_cbs_sublabel.c index b527d241f1..bf8662da2d 100644 --- a/menu/cbs/menu_cbs_sublabel.c +++ b/menu/cbs/menu_cbs_sublabel.c @@ -850,6 +850,7 @@ DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_playlist_sort_alphabetical, DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_playlist_fuzzy_archive_match, MENU_ENUM_SUBLABEL_PLAYLIST_FUZZY_ARCHIVE_MATCH) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_playlist_use_old_format, MENU_ENUM_SUBLABEL_PLAYLIST_USE_OLD_FORMAT) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_playlist_compression, MENU_ENUM_SUBLABEL_PLAYLIST_COMPRESSION) +DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_playlist_portable_paths, MENU_ENUM_SUBLABEL_PLAYLIST_PORTABLE_PATHS) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_menu_rgui_full_width_layout, MENU_ENUM_SUBLABEL_MENU_RGUI_FULL_WIDTH_LAYOUT) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_menu_rgui_extended_ascii, MENU_ENUM_SUBLABEL_MENU_RGUI_EXTENDED_ASCII) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_thumbnails_updater_list, MENU_ENUM_SUBLABEL_THUMBNAILS_UPDATER_LIST) @@ -3810,6 +3811,9 @@ int menu_cbs_init_bind_sublabel(menu_file_list_cbs_t *cbs, case MENU_ENUM_LABEL_PLAYLIST_FUZZY_ARCHIVE_MATCH: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_playlist_fuzzy_archive_match); break; + case MENU_ENUM_LABEL_PLAYLIST_PORTABLE_PATHS: + BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_playlist_portable_paths); + break; case MENU_ENUM_LABEL_PLAYLIST_USE_OLD_FORMAT: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_playlist_use_old_format); break; diff --git a/menu/menu_displaylist.c b/menu/menu_displaylist.c index d4b27351ab..335038b2fa 100644 --- a/menu/menu_displaylist.c +++ b/menu/menu_displaylist.c @@ -1671,6 +1671,7 @@ static int menu_displaylist_parse_database_entry(menu_handle_t *menu, playlist_config.old_format = settings->bools.playlist_use_old_format; playlist_config.compress = settings->bools.playlist_compression; playlist_config.fuzzy_archive_match = settings->bools.playlist_fuzzy_archive_match; + playlist_config_set_base_content_directory(&playlist_config, settings->bools.playlist_portable_paths ? settings->paths.directory_menu_content : NULL); path_playlist[0] = path_base[0] = query[0] = '\0'; @@ -2312,6 +2313,7 @@ static void menu_displaylist_set_new_playlist( playlist_config.old_format = settings->bools.playlist_use_old_format; playlist_config.compress = settings->bools.playlist_compression; playlist_config.fuzzy_archive_match = settings->bools.playlist_fuzzy_archive_match; + playlist_config_set_base_content_directory(&playlist_config, settings->bools.playlist_portable_paths ? settings->paths.directory_menu_content : NULL); menu->db_playlist_file[0] = '\0'; @@ -4910,6 +4912,7 @@ unsigned menu_displaylist_build_list( {MENU_ENUM_LABEL_OZONE_SORT_AFTER_TRUNCATE_PLAYLIST_NAME, PARSE_ONLY_BOOL, true}, {MENU_ENUM_LABEL_CONTENT_RUNTIME_LOG, PARSE_ONLY_BOOL, true}, {MENU_ENUM_LABEL_CONTENT_RUNTIME_LOG_AGGREGATE, PARSE_ONLY_BOOL, true}, + {MENU_ENUM_LABEL_PLAYLIST_PORTABLE_PATHS, PARSE_ONLY_BOOL, true}, }; for (i = 0; i < ARRAY_SIZE(build_list); i++) diff --git a/menu/menu_setting.c b/menu/menu_setting.c index 0cfec76b40..8b4de091b6 100644 --- a/menu/menu_setting.c +++ b/menu/menu_setting.c @@ -16234,6 +16234,22 @@ static bool setting_append_list( SD_FLAG_NONE ); + CONFIG_BOOL( + list, list_info, + &settings->bools.playlist_portable_paths, + MENU_ENUM_LABEL_PLAYLIST_PORTABLE_PATHS, + MENU_ENUM_LABEL_VALUE_PLAYLIST_PORTABLE_PATHS, + DEFAULT_PLAYLIST_PORTABLE_PATHS, + MENU_ENUM_LABEL_VALUE_OFF, + MENU_ENUM_LABEL_VALUE_ON, + &group_info, + &subgroup_info, + parent_group, + general_write_handler, + general_read_handler, + SD_FLAG_NONE + ); + #ifdef HAVE_OZONE if (string_is_equal(settings->arrays.menu_driver, "ozone")) { diff --git a/msg_hash.h b/msg_hash.h index 9263eb4e76..09257cdfa0 100644 --- a/msg_hash.h +++ b/msg_hash.h @@ -2872,6 +2872,7 @@ enum msg_hash_enums MENU_LABEL(PLAYLIST_FUZZY_ARCHIVE_MATCH), MENU_LABEL(PLAYLIST_SUBLABEL_RUNTIME_TYPE), MENU_LABEL(PLAYLIST_SUBLABEL_LAST_PLAYED_STYLE), + MENU_LABEL(PLAYLIST_PORTABLE_PATHS), MENU_ENUM_LABEL_VALUE_PLAYLIST_INLINE_CORE_DISPLAY_HIST_FAV, MENU_ENUM_LABEL_VALUE_PLAYLIST_INLINE_CORE_DISPLAY_ALWAYS, diff --git a/playlist.c b/playlist.c index 291d80bf31..c2ac2ae55f 100644 --- a/playlist.c +++ b/playlist.c @@ -34,11 +34,23 @@ #include "playlist.h" #include "verbosity.h" #include "file_path_special.h" +#include "core_info.h" #ifndef PLAYLIST_ENTRIES #define PLAYLIST_ENTRIES 6 #endif +#define WINDOWS_PATH_DELIMITER '\\' +#define POSIX_PATH_DELIMITER '/' + +#ifdef _WIN32 +#define LOCAL_FILE_SYSTEM_PATH_DELIMITER WINDOWS_PATH_DELIMITER +#define USING_WINDOWS_FILE_SYSTEM +#else +#define LOCAL_FILE_SYSTEM_PATH_DELIMITER POSIX_PATH_DELIMITER +#define USING_POSIX_FILE_SYSTEM +#endif + struct content_playlist { bool modified; @@ -54,6 +66,7 @@ struct content_playlist char *default_core_path; char *default_core_name; + char *base_content_directory; struct playlist_entry *entries; playlist_config_t config; @@ -105,6 +118,22 @@ void playlist_config_set_path(playlist_config_t *config, const char *path) strlcpy(config->path, path, sizeof(config->path)); } +/* Convenience function: copies base content directory + * path to specified playlist configuration object. + * Also sets autofix_paths boolean, depending on base content directory value */ +void playlist_config_set_base_content_directory(playlist_config_t* config, const char* path) +{ + if (!config) + return; + + config->autofix_paths = !string_is_empty(path); + if (!config->autofix_paths) + config->base_content_directory[0] = '\0'; + else + strlcpy(config->base_content_directory, path, sizeof(config->base_content_directory)); +} + + /* Creates a copy of the specified playlist configuration. * Returns false in the event of an error */ bool playlist_config_copy(const playlist_config_t *src, playlist_config_t *dst) @@ -113,11 +142,13 @@ bool playlist_config_copy(const playlist_config_t *src, playlist_config_t *dst) return false; strlcpy(dst->path, src->path, sizeof(dst->path)); + strlcpy(dst->base_content_directory, src->base_content_directory, sizeof(dst->base_content_directory)); dst->capacity = src->capacity; dst->old_format = src->old_format; dst->compress = src->compress; dst->fuzzy_archive_match = src->fuzzy_archive_match; + dst->autofix_paths = src->autofix_paths; return true; } @@ -133,6 +164,43 @@ playlist_config_t *playlist_get_config(playlist_t *playlist) return &playlist->config; } +static void path_replace_base_path_and_convert_to_local_file_system( + char *out_path, const char *in_path, + const char *in_oldrefpath, const char *in_refpath, + size_t size) +{ + const char fs_delimeter = LOCAL_FILE_SYSTEM_PATH_DELIMITER; + size_t in_oldrefpath_length = strlen(in_oldrefpath); + size_t in_refpath_length = strlen(in_refpath); + + /* If entry path is inside playlist base path, + * replace it with new base content directory */ + if (string_starts_with_size(in_path, in_oldrefpath, in_oldrefpath_length)) + { + memcpy(out_path, in_refpath, in_refpath_length); + memcpy(out_path + in_refpath_length, in_path + in_oldrefpath_length, + strlen(in_path) - in_oldrefpath_length + 1); + +#ifdef USING_WINDOWS_FILE_SYSTEM + /* If we are running under a win fs, '/' characters + * are not allowed anywhere. we replace with '\' and + * hope for the best... */ + string_replace_all_chars(out_path, + POSIX_PATH_DELIMITER, WINDOWS_PATH_DELIMITER); +#endif + +#ifdef USING_POSIX_FILE_SYSTEM + /* Under posix fs, we replace '\' characters with '/' */ + string_replace_all_chars(out_path, + WINDOWS_PATH_DELIMITER, POSIX_PATH_DELIMITER); +#endif + } + else + { + strlcpy(out_path, in_path, size); + } +} + /** * playlist_path_equal: * @real_path : 'Real' search path, generated by path_resolve_realpath() @@ -228,14 +296,15 @@ static bool playlist_path_equal(const char *real_path, /** * playlist_core_path_equal: - * @real_core_path : 'Real' search path, generated by path_resolve_realpath() - * @entry_core_path : Existing playlist entry 'core path' value + * @real_core_path : 'Real' search path, generated by path_resolve_realpath() + * @entry_core_path : Existing playlist entry 'core path' value + * @config : Playlist config parameters * * Returns 'true' if real_core_path matches entry_core_path * (Taking into account relative paths, case insensitive * filesystems) **/ -static bool playlist_core_path_equal(const char *real_core_path, const char *entry_core_path) +static bool playlist_core_path_equal(const char *real_core_path, const char *entry_core_path, const playlist_config_t *config) { char entry_real_core_path[PATH_MAX_LENGTH]; @@ -263,6 +332,10 @@ static bool playlist_core_path_equal(const char *real_core_path, const char *ent return true; #endif + if (config->autofix_paths && + core_info_core_file_id_is_equal(real_core_path, entry_core_path)) + return true; + return false; } @@ -699,7 +772,7 @@ bool playlist_push_runtime(playlist_t *playlist, if (!equal_path) continue; - if (!playlist_core_path_equal(real_core_path, playlist->entries[i].core_path)) + if (!playlist_core_path_equal(real_core_path, playlist->entries[i].core_path, &playlist->config)) continue; /* If top entry, we don't want to push a new entry since @@ -882,7 +955,7 @@ bool playlist_push(playlist_t *playlist, if (!equal_path) continue; - if (!playlist_core_path_equal(real_core_path, playlist->entries[i].core_path)) + if (!playlist_core_path_equal(real_core_path, playlist->entries[i].core_path, &playlist->config)) continue; if ( !string_is_empty(entry->subsystem_ident) @@ -1480,6 +1553,21 @@ void playlist_write_file(playlist_t *playlist) JSON_Writer_WriteComma(context.writer); json_write_new_line(context.writer); + if (!string_is_empty(playlist->base_content_directory)) + { + json_write_space(context.writer, 2); + JSON_Writer_WriteString(context.writer, "base_content_directory", + STRLEN_CONST("base_content_directory"), JSON_UTF8); + JSON_Writer_WriteColon(context.writer); + json_write_space(context.writer, 1); + JSON_Writer_WriteString(context.writer, + playlist->base_content_directory, + strlen(playlist->base_content_directory), + JSON_UTF8); + JSON_Writer_WriteComma(context.writer); + json_write_new_line(context.writer); + } + uint_str[0] = '\0'; snprintf(uint_str, sizeof(uint_str), "%u", playlist->label_display_mode); @@ -1759,17 +1847,24 @@ void playlist_free(playlist_t *playlist) free(playlist->default_core_name); playlist->default_core_name = NULL; - for (i = 0; i < playlist->size; i++) + if (playlist->base_content_directory != NULL) + free(playlist->base_content_directory); + playlist->base_content_directory = NULL; + + if (playlist->entries) { - struct playlist_entry *entry = &playlist->entries[i]; + for (i = 0; i < playlist->size; i++) + { + struct playlist_entry *entry = &playlist->entries[i]; - if (entry) - playlist_free_entry(entry); + if (entry) + playlist_free_entry(entry); + } + + free(playlist->entries); + playlist->entries = NULL; } - free(playlist->entries); - playlist->entries = NULL; - free(playlist); } @@ -2141,6 +2236,9 @@ static JSON_Parser_HandlerResult JSONObjectMemberHandler(JSON_Parser parser, cha pCtx->current_meta_thumbnail_mode_val = &pCtx->playlist->left_thumbnail_mode; else if (string_is_equal(pValue, "sort_mode")) pCtx->current_meta_sort_mode_val = &pCtx->playlist->sort_mode; + else if (string_is_equal(pValue, "base_content_directory")) + pCtx->current_meta_val = &pCtx->playlist->base_content_directory; + } } } @@ -2561,27 +2659,94 @@ playlist_t *playlist_init(const playlist_config_t *config) goto error; /* Set initial values */ - playlist->modified = false; - playlist->old_format = false; - playlist->compressed = false; - playlist->size = 0; - playlist->default_core_name = NULL; - playlist->default_core_path = NULL; - playlist->entries = entries; - playlist->label_display_mode = LABEL_DISPLAY_MODE_DEFAULT; - playlist->right_thumbnail_mode = PLAYLIST_THUMBNAIL_MODE_DEFAULT; - playlist->left_thumbnail_mode = PLAYLIST_THUMBNAIL_MODE_DEFAULT; - playlist->sort_mode = PLAYLIST_SORT_MODE_DEFAULT; + playlist->modified = false; + playlist->old_format = false; + playlist->compressed = false; + playlist->size = 0; + playlist->default_core_name = NULL; + playlist->default_core_path = NULL; + playlist->base_content_directory = NULL; + playlist->entries = entries; + playlist->label_display_mode = LABEL_DISPLAY_MODE_DEFAULT; + playlist->right_thumbnail_mode = PLAYLIST_THUMBNAIL_MODE_DEFAULT; + playlist->left_thumbnail_mode = PLAYLIST_THUMBNAIL_MODE_DEFAULT; + playlist->sort_mode = PLAYLIST_SORT_MODE_DEFAULT; /* Attempt to read any existing playlist file */ playlist_read_file(playlist); + /* Try auto-fixing paths if enabled, and playlist + * base content directory is different */ + if (playlist->config.autofix_paths && + !string_is_empty(playlist->base_content_directory) && + !string_is_equal(playlist->base_content_directory, + playlist->config.base_content_directory)) + { + size_t playlist_base_content_directory_length = strlen(playlist->base_content_directory); + size_t new_base_content_directory_length = strlen(playlist->config.base_content_directory); + size_t i; + size_t j; + char tmp_entry_path[PATH_MAX_LENGTH]; + + for (i = 0; i < playlist->size; i++) + { + struct playlist_entry *entry = &playlist->entries[i]; + + if (!entry || string_is_empty(entry->path)) + continue; + + /* Fix entry path */ + tmp_entry_path[0] = '\0'; + path_replace_base_path_and_convert_to_local_file_system( + tmp_entry_path, entry->path, + playlist->base_content_directory, playlist->config.base_content_directory, + sizeof(tmp_entry_path)); + + free(entry->path); + entry->path = strdup(tmp_entry_path); + + /* Fix subsystem roms paths*/ + if (entry->subsystem_roms && (entry->subsystem_roms->size > 0)) + { + struct string_list* subsystem_roms_new_paths = string_list_new(); + union string_list_elem_attr attributes = {0}; + + if (!subsystem_roms_new_paths) + goto error; + + for (j = 0; j < entry->subsystem_roms->size; j++) + { + const char *subsystem_rom_path = entry->subsystem_roms->elems[j].data; + + if (string_is_empty(subsystem_rom_path)) + continue; + + tmp_entry_path[0] = '\0'; + path_replace_base_path_and_convert_to_local_file_system( + tmp_entry_path, subsystem_rom_path, + playlist->base_content_directory, playlist->config.base_content_directory, + sizeof(tmp_entry_path)); + string_list_append(subsystem_roms_new_paths, tmp_entry_path, attributes); + } + + string_list_free(entry->subsystem_roms); + entry->subsystem_roms = subsystem_roms_new_paths; + } + } + + /* Update playlist base content directory*/ + free(playlist->base_content_directory); + playlist->base_content_directory = strdup(playlist->config.base_content_directory); + + /* Save playlist */ + playlist->modified = true; + playlist_write_file(playlist); + } + return playlist; error: - if (playlist) - free(playlist); - + playlist_free(playlist); return NULL; } @@ -2760,7 +2925,7 @@ bool playlist_entries_are_equal( path_resolve_realpath(real_core_path_a, sizeof(real_core_path_a), true); } - return playlist_core_path_equal(real_core_path_a, entry_b->core_path); + return playlist_core_path_equal(real_core_path_a, entry_b->core_path, config); } void playlist_get_crc32(playlist_t *playlist, size_t idx, diff --git a/playlist.h b/playlist.h index 1a7a73d3a2..b3231ddb20 100644 --- a/playlist.h +++ b/playlist.h @@ -119,16 +119,22 @@ struct playlist_entry typedef struct { char path[PATH_MAX_LENGTH]; + char base_content_directory[PATH_MAX_LENGTH]; size_t capacity; bool old_format; bool compress; bool fuzzy_archive_match; + bool autofix_paths; } playlist_config_t; /* Convenience function: copies specified playlist * path to specified playlist configuration object */ void playlist_config_set_path(playlist_config_t *config, const char *path); +/* Convenience function: copies base content directory + * path to specified playlist configuration object */ +void playlist_config_set_base_content_directory(playlist_config_t* config, const char* path); + /* Creates a copy of the specified playlist configuration. * Returns false in the event of an error */ bool playlist_config_copy(const playlist_config_t *src, playlist_config_t *dst); diff --git a/retroarch.c b/retroarch.c index aa53a8b92b..095c1d385e 100644 --- a/retroarch.c +++ b/retroarch.c @@ -15995,6 +15995,8 @@ bool command_event(enum event_command cmd, void *data) playlist_config.old_format = settings->bools.playlist_use_old_format; playlist_config.compress = settings->bools.playlist_compression; playlist_config.fuzzy_archive_match = settings->bools.playlist_fuzzy_archive_match; + /* don't use relative paths for content, music, video, and image histories */ + playlist_config_set_base_content_directory(&playlist_config, NULL); command_event(CMD_EVENT_HISTORY_DEINIT, NULL); @@ -39625,6 +39627,7 @@ void rarch_favorites_init(void) playlist_config.old_format = settings ? settings->bools.playlist_use_old_format : false; playlist_config.compress = settings ? settings->bools.playlist_compression : false; playlist_config.fuzzy_archive_match = settings ? settings->bools.playlist_fuzzy_archive_match : false; + playlist_config_set_base_content_directory(&playlist_config, NULL); if (!settings) return; diff --git a/tasks/task_database.c b/tasks/task_database.c index 8ff81a27e3..61ec8db0d6 100644 --- a/tasks/task_database.c +++ b/tasks/task_database.c @@ -1368,11 +1368,13 @@ bool task_push_dbscan( db->playlist_config.old_format = settings->bools.playlist_use_old_format; db->playlist_config.compress = settings->bools.playlist_compression; db->playlist_config.fuzzy_archive_match = settings->bools.playlist_fuzzy_archive_match; + playlist_config_set_base_content_directory(&db->playlist_config, settings->bools.playlist_portable_paths ? settings->paths.directory_menu_content : NULL); #else db->playlist_config.capacity = COLLECTION_SIZE; db->playlist_config.old_format = false; db->playlist_config.compress = false; db->playlist_config.fuzzy_archive_match = false; + playlist_config_set_base_content_directory(&db->playlist_config, NULL); #endif db->show_hidden_files = db_dir_show_hidden_files; db->is_directory = directory; diff --git a/tasks/task_netplay_find_content.c b/tasks/task_netplay_find_content.c index 9f902561ba..a23aea436a 100644 --- a/tasks/task_netplay_find_content.c +++ b/tasks/task_netplay_find_content.c @@ -437,6 +437,7 @@ bool task_push_netplay_crc_scan(uint32_t crc, char* name, state->playlist_config.old_format = settings->bools.playlist_use_old_format; state->playlist_config.compress = settings->bools.playlist_compression; state->playlist_config.fuzzy_archive_match = settings->bools.playlist_fuzzy_archive_match; + playlist_config_set_base_content_directory(&state->playlist_config, settings->bools.playlist_portable_paths ? settings->paths.directory_menu_content : NULL); state->content_crc[0] = '\0'; state->content_path[0] = '\0'; diff --git a/ui/drivers/qt/qt_playlist.cpp b/ui/drivers/qt/qt_playlist.cpp index 228dd2bf45..d30d81c4a2 100644 --- a/ui/drivers/qt/qt_playlist.cpp +++ b/ui/drivers/qt/qt_playlist.cpp @@ -391,6 +391,7 @@ void MainWindow::addFilesToPlaylist(QStringList files) playlist_config.old_format = settings->bools.playlist_use_old_format; playlist_config.compress = settings->bools.playlist_compression; playlist_config.fuzzy_archive_match = settings->bools.playlist_fuzzy_archive_match; + playlist_config_set_base_content_directory(&playlist_config, settings->bools.playlist_portable_paths ? settings->paths.directory_menu_content : NULL); /* Assume a blank list means we will manually enter in all fields. */ if (files.isEmpty()) @@ -697,6 +698,7 @@ bool MainWindow::updateCurrentPlaylistEntry( playlist_config.old_format = settings->bools.playlist_use_old_format; playlist_config.compress = settings->bools.playlist_compression; playlist_config.fuzzy_archive_match = settings->bools.playlist_fuzzy_archive_match; + playlist_config_set_base_content_directory(&playlist_config, settings->bools.playlist_portable_paths ? settings->paths.directory_menu_content : NULL); if ( playlistPath.isEmpty() || contentHash.isEmpty() || @@ -825,6 +827,7 @@ void MainWindow::onPlaylistWidgetContextMenuRequested(const QPoint&) playlist_config.old_format = settings->bools.playlist_use_old_format; playlist_config.compress = settings->bools.playlist_compression; playlist_config.fuzzy_archive_match = settings->bools.playlist_fuzzy_archive_match; + playlist_config_set_base_content_directory(&playlist_config, settings->bools.playlist_portable_paths ? settings->paths.directory_menu_content : NULL); if (selectedItem) { @@ -1361,6 +1364,7 @@ void MainWindow::deleteCurrentPlaylistItem() playlist_config.old_format = settings->bools.playlist_use_old_format; playlist_config.compress = settings->bools.playlist_compression; playlist_config.fuzzy_archive_match = settings->bools.playlist_fuzzy_archive_match; + playlist_config_set_base_content_directory(&playlist_config, settings->bools.playlist_portable_paths ? settings->paths.directory_menu_content : NULL); if (isAllPlaylist) return; @@ -1408,6 +1412,7 @@ QString MainWindow::getPlaylistDefaultCore(QString dbName) playlist_config.old_format = settings->bools.playlist_use_old_format; playlist_config.compress = settings->bools.playlist_compression; playlist_config.fuzzy_archive_match = settings->bools.playlist_fuzzy_archive_match; + playlist_config_set_base_content_directory(&playlist_config, settings->bools.playlist_portable_paths ? settings->paths.directory_menu_content : NULL); playlistPath[0] = '\0'; @@ -1478,6 +1483,7 @@ void PlaylistModel::getPlaylistItems(QString path) playlist_config.old_format = settings->bools.playlist_use_old_format; playlist_config.compress = settings->bools.playlist_compression; playlist_config.fuzzy_archive_match = settings->bools.playlist_fuzzy_archive_match; + playlist_config_set_base_content_directory(&playlist_config, settings->bools.playlist_portable_paths ? settings->paths.directory_menu_content : NULL); pathArray.append(path); pathData = pathArray.constData(); From 667e8a558e8856ea9d980a300237b7973f25aa57 Mon Sep 17 00:00:00 2001 From: Bernhard Schelling <14200249+schellingb@users.noreply.github.com> Date: Tue, 28 Jul 2020 01:53:13 +0900 Subject: [PATCH 03/17] Explore view - WIP1 A top content view that combines all playlists with database entries to browse by year, developer, system, etc. Depends on the quality of the available metadata in the database (RDB) files. --- Makefile.common | 3 +- griffin/griffin.c | 1 + intl/msg_hash_lbl.h | 8 + intl/msg_hash_us.h | 4 + menu/cbs/menu_cbs_left.c | 1 + menu/cbs/menu_cbs_ok.c | 1 + menu/cbs/menu_cbs_right.c | 1 + menu/cbs/menu_cbs_title.c | 1 + menu/drivers/ozone/ozone.c | 2 + menu/drivers/ozone/ozone_sidebar.c | 10 +- menu/drivers/ozone/ozone_sidebar.h | 1 + menu/drivers/ozone/ozone_texture.c | 1 + menu/drivers/xmb.c | 16 + menu/menu_displaylist.c | 18 + menu/menu_displaylist.h | 1 + menu/menu_driver.h | 1 + menu/menu_explore.c | 817 +++++++++++++++++++++++++++++ msg_hash.h | 3 + playlist.c | 6 + retroarch.c | 1 + 20 files changed, 893 insertions(+), 4 deletions(-) create mode 100644 menu/menu_explore.c diff --git a/Makefile.common b/Makefile.common index 4262472e37..04ca1b58cf 100644 --- a/Makefile.common +++ b/Makefile.common @@ -913,7 +913,8 @@ ifeq ($(HAVE_MENU_COMMON), 1) menu/cbs/menu_cbs_up.o \ menu/cbs/menu_cbs_down.o \ menu/cbs/menu_cbs_contentlist_switch.o \ - menu/menu_displaylist.o + menu/menu_displaylist.o \ + menu/menu_explore.o endif ifeq ($(HAVE_GFX_WIDGETS), 1) diff --git a/griffin/griffin.c b/griffin/griffin.c index f69561bb59..9bd8529e5f 100644 --- a/griffin/griffin.c +++ b/griffin/griffin.c @@ -1349,6 +1349,7 @@ MENU #include "../menu/cbs/menu_cbs_down.c" #include "../menu/cbs/menu_cbs_contentlist_switch.c" #include "../menu/menu_displaylist.c" +#include "../menu/menu_explore.c" #endif #if defined(HAVE_LIBNX) diff --git a/intl/msg_hash_lbl.h b/intl/msg_hash_lbl.h index 87b2cc95e7..325b04efe1 100644 --- a/intl/msg_hash_lbl.h +++ b/intl/msg_hash_lbl.h @@ -50,6 +50,14 @@ MSG_HASH( MENU_ENUM_LABEL_CONFIGURATIONS_LIST, "configurations_list" ) +MSG_HASH( + MENU_ENUM_LABEL_EXPLORE_TAB, + "explore_tab" + ) +MSG_HASH( + MENU_ENUM_LABEL_EXPLORE_ITEM, + "explore_item" + ) MSG_HASH( MENU_ENUM_LABEL_ADD_TAB, "add_tab" diff --git a/intl/msg_hash_us.h b/intl/msg_hash_us.h index 1cdbfbfc17..49ff9ad6d7 100644 --- a/intl/msg_hash_us.h +++ b/intl/msg_hash_us.h @@ -40,6 +40,10 @@ MSG_HASH( MENU_ENUM_LABEL_VALUE_NETPLAY_TAB, "Netplay" ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_EXPLORE_TAB, + "Explore" + ) MSG_HASH( MENU_ENUM_LABEL_VALUE_ADD_TAB, "Import Content" diff --git a/menu/cbs/menu_cbs_left.c b/menu/cbs/menu_cbs_left.c index 72d5020bc7..5fac2a4231 100644 --- a/menu/cbs/menu_cbs_left.c +++ b/menu/cbs/menu_cbs_left.c @@ -872,6 +872,7 @@ static int menu_cbs_init_bind_left_compare_label(menu_file_list_cbs_t *cbs, case MENU_ENUM_LABEL_SUBSYSTEM_ADD: case MENU_ENUM_LABEL_SUBSYSTEM_LOAD: case MENU_ENUM_LABEL_CONNECT_NETPLAY_ROOM: + case MENU_ENUM_LABEL_EXPLORE_ITEM: BIND_ACTION_LEFT(cbs, action_left_mainmenu); break; case MENU_ENUM_LABEL_VIDEO_SHADER_SCALE_PASS: diff --git a/menu/cbs/menu_cbs_ok.c b/menu/cbs/menu_cbs_ok.c index 09cbda4834..9f2808fca6 100644 --- a/menu/cbs/menu_cbs_ok.c +++ b/menu/cbs/menu_cbs_ok.c @@ -7133,6 +7133,7 @@ static int menu_cbs_init_bind_ok_compare_label(menu_file_list_cbs_t *cbs, {MENU_ENUM_LABEL_SCREEN_RESOLUTION, action_ok_video_resolution}, {MENU_ENUM_LABEL_PLAYLIST_MANAGER_DEFAULT_CORE, action_ok_playlist_default_core}, {MENU_ENUM_LABEL_CORE_MANAGER_LIST, action_ok_push_core_manager_list}, + {MENU_ENUM_LABEL_EXPLORE_TAB, action_ok_push_default}, }; for (i = 0; i < ARRAY_SIZE(ok_list); i++) diff --git a/menu/cbs/menu_cbs_right.c b/menu/cbs/menu_cbs_right.c index 645903f933..2bab568d48 100644 --- a/menu/cbs/menu_cbs_right.c +++ b/menu/cbs/menu_cbs_right.c @@ -994,6 +994,7 @@ static int menu_cbs_init_bind_right_compare_label(menu_file_list_cbs_t *cbs, case MENU_ENUM_LABEL_SUBSYSTEM_ADD: case MENU_ENUM_LABEL_SUBSYSTEM_LOAD: case MENU_ENUM_LABEL_CONNECT_NETPLAY_ROOM: + case MENU_ENUM_LABEL_EXPLORE_ITEM: BIND_ACTION_RIGHT(cbs, action_right_mainmenu); break; case MENU_ENUM_LABEL_VIDEO_SHADER_SCALE_PASS: diff --git a/menu/cbs/menu_cbs_title.c b/menu/cbs/menu_cbs_title.c index a52dbe2860..3df8459ad8 100644 --- a/menu/cbs/menu_cbs_title.c +++ b/menu/cbs/menu_cbs_title.c @@ -745,6 +745,7 @@ static int action_get_title_group_settings(const char *path, const char *label, {MENU_ENUM_LABEL_SETTINGS_TAB, MENU_ENUM_LABEL_VALUE_SETTINGS_TAB } , {MENU_ENUM_LABEL_PLAYLISTS_TAB, MENU_ENUM_LABEL_VALUE_PLAYLISTS_TAB} , {MENU_ENUM_LABEL_ADD_TAB, MENU_ENUM_LABEL_VALUE_ADD_TAB } , + {MENU_ENUM_LABEL_EXPLORE_TAB, MENU_ENUM_LABEL_VALUE_EXPLORE_TAB } , {MENU_ENUM_LABEL_NETPLAY_TAB, MENU_ENUM_LABEL_VALUE_NETPLAY_TAB } , {MENU_ENUM_LABEL_HORIZONTAL_MENU, MENU_ENUM_LABEL_VALUE_HORIZONTAL_MENU }, }; diff --git a/menu/drivers/ozone/ozone.c b/menu/drivers/ozone/ozone.c index e8e1b43164..f4fe44f5e1 100644 --- a/menu/drivers/ozone/ozone.c +++ b/menu/drivers/ozone/ozone.c @@ -196,6 +196,8 @@ static void *ozone_init(void **userdata, bool video_is_threaded) if (settings->bools.menu_content_show_add && !settings->bools.kiosk_mode_enable) ozone->tabs[++ozone->system_tab_end] = OZONE_SYSTEM_TAB_ADD; + ozone->tabs[++ozone->system_tab_end] = OZONE_SYSTEM_TAB_EXPLORE; + menu_driver_ctl(RARCH_MENU_CTL_UNSET_PREVENT_POPULATE, NULL); gfx_display_set_width(width); diff --git a/menu/drivers/ozone/ozone_sidebar.c b/menu/drivers/ozone/ozone_sidebar.c index 631ed6a5cd..e05141fb9b 100644 --- a/menu/drivers/ozone/ozone_sidebar.c +++ b/menu/drivers/ozone/ozone_sidebar.c @@ -46,7 +46,8 @@ enum msg_hash_enums ozone_system_tabs_value[OZONE_SYSTEM_TAB_LAST] = { #ifdef HAVE_NETWORKING MENU_ENUM_LABEL_VALUE_NETPLAY_TAB, #endif - MENU_ENUM_LABEL_VALUE_ADD_TAB + MENU_ENUM_LABEL_VALUE_ADD_TAB, + MENU_ENUM_LABEL_VALUE_EXPLORE_TAB }; enum menu_settings_type ozone_system_tabs_type[OZONE_SYSTEM_TAB_LAST] = { @@ -64,7 +65,8 @@ enum menu_settings_type ozone_system_tabs_type[OZONE_SYSTEM_TAB_LAST] = { #ifdef HAVE_NETWORKING MENU_NETPLAY_TAB, #endif - MENU_ADD_TAB + MENU_ADD_TAB, + MENU_EXPLORE_TAB }; enum msg_hash_enums ozone_system_tabs_idx[OZONE_SYSTEM_TAB_LAST] = { @@ -82,7 +84,8 @@ enum msg_hash_enums ozone_system_tabs_idx[OZONE_SYSTEM_TAB_LAST] = { #ifdef HAVE_NETWORKING MENU_ENUM_LABEL_NETPLAY_TAB, #endif - MENU_ENUM_LABEL_ADD_TAB + MENU_ENUM_LABEL_ADD_TAB, + MENU_ENUM_LABEL_EXPLORE_TAB }; unsigned ozone_system_tabs_icons[OZONE_SYSTEM_TAB_LAST] = { @@ -1007,6 +1010,7 @@ bool ozone_is_playlist(ozone_handle_t *ozone, bool depth) #ifdef HAVE_NETWORKING case OZONE_SYSTEM_TAB_NETPLAY: #endif + case OZONE_SYSTEM_TAB_EXPLORE: is_playlist = false; break; case OZONE_SYSTEM_TAB_HISTORY: diff --git a/menu/drivers/ozone/ozone_sidebar.h b/menu/drivers/ozone/ozone_sidebar.h index 06467c2d00..162239fd17 100644 --- a/menu/drivers/ozone/ozone_sidebar.h +++ b/menu/drivers/ozone/ozone_sidebar.h @@ -39,6 +39,7 @@ enum OZONE_SYSTEM_TAB_NETPLAY, #endif OZONE_SYSTEM_TAB_ADD, + OZONE_SYSTEM_TAB_EXPLORE, /* End of this enum - use the last one to determine num of possible tabs */ OZONE_SYSTEM_TAB_LAST diff --git a/menu/drivers/ozone/ozone_texture.c b/menu/drivers/ozone/ozone_texture.c index a1d233e873..d7da57fd21 100644 --- a/menu/drivers/ozone/ozone_texture.c +++ b/menu/drivers/ozone/ozone_texture.c @@ -165,6 +165,7 @@ uintptr_t ozone_entries_icon_get_texture(ozone_handle_t *ozone, case MENU_ENUM_LABEL_SYSTEM_INFORMATION: case MENU_ENUM_LABEL_UPDATE_CORE_INFO_FILES: return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_INFO]; + case MENU_ENUM_LABEL_EXPLORE_TAB: case MENU_ENUM_LABEL_UPDATE_DATABASES: case MENU_ENUM_LABEL_DATABASE_MANAGER_LIST: return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_RDB]; diff --git a/menu/drivers/xmb.c b/menu/drivers/xmb.c index 884b83ae01..4c8882c9bc 100644 --- a/menu/drivers/xmb.c +++ b/menu/drivers/xmb.c @@ -249,6 +249,7 @@ enum XMB_SYSTEM_TAB_NETPLAY, #endif XMB_SYSTEM_TAB_ADD, + XMB_SYSTEM_TAB_EXPLORE, /* End of this enum - use the last one to determine num of possible tabs */ XMB_SYSTEM_TAB_MAX_LENGTH @@ -339,6 +340,7 @@ typedef struct xmb_handle xmb_node_t history_tab_node; xmb_node_t favorites_tab_node; xmb_node_t add_tab_node; + xmb_node_t explore_tab_node; xmb_node_t netplay_tab_node; menu_input_pointer_t pointer; @@ -1864,6 +1866,8 @@ static xmb_node_t* xmb_get_node(xmb_handle_t *xmb, unsigned i) #endif case XMB_SYSTEM_TAB_ADD: return &xmb->add_tab_node; + case XMB_SYSTEM_TAB_EXPLORE: + return &xmb->explore_tab_node; default: if (i > xmb->system_tab_end) return xmb_get_userdata_from_horizontal_list( @@ -5446,6 +5450,8 @@ static void *xmb_init(void **userdata, bool video_is_threaded) && !settings->bools.kiosk_mode_enable) xmb->tabs[++xmb->system_tab_end] = XMB_SYSTEM_TAB_ADD; + xmb->tabs[++xmb->system_tab_end] = XMB_SYSTEM_TAB_EXPLORE; + menu_driver_ctl(RARCH_MENU_CTL_UNSET_PREVENT_POPULATE, NULL); /* TODO/FIXME - we don't use framebuffer at all @@ -5928,6 +5934,10 @@ static void xmb_context_reset_textures( xmb->add_tab_node.alpha = xmb->categories_active_alpha; xmb->add_tab_node.zoom = xmb->categories_active_zoom; + xmb->explore_tab_node.icon = xmb->textures.list[XMB_TEXTURE_MAIN_MENU]; + xmb->explore_tab_node.alpha = xmb->categories_active_alpha; + xmb->explore_tab_node.zoom = xmb->categories_active_zoom; + #ifdef HAVE_NETWORKING xmb->netplay_tab_node.icon = xmb->textures.list[XMB_TEXTURE_NETPLAY]; xmb->netplay_tab_node.alpha = xmb->categories_active_alpha; @@ -6354,6 +6364,12 @@ static void xmb_list_cache(void *data, enum menu_list_type type, unsigned action menu_stack->list[stack_size - 1].type = MENU_ADD_TAB; break; + case XMB_SYSTEM_TAB_EXPLORE: + menu_stack->list[stack_size - 1].label = + strdup(msg_hash_to_str(MENU_ENUM_LABEL_EXPLORE_TAB)); + menu_stack->list[stack_size - 1].type = + MENU_EXPLORE_TAB; + break; default: menu_stack->list[stack_size - 1].label = strdup(msg_hash_to_str(MENU_ENUM_LABEL_HORIZONTAL_MENU)); diff --git a/menu/menu_displaylist.c b/menu/menu_displaylist.c index 9133e32d3f..ea5e3a0729 100644 --- a/menu/menu_displaylist.c +++ b/menu/menu_displaylist.c @@ -3078,6 +3078,12 @@ static unsigned menu_displaylist_parse_playlists( count++; } + if (menu_entries_append_enum(info->list, + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_EXPLORE_TAB), + msg_hash_to_str(MENU_ENUM_LABEL_EXPLORE_TAB), + MENU_ENUM_LABEL_EXPLORE_TAB, + MENU_EXPLORE_TAB, 0, 0)) + count++; if (settings->bools.menu_content_show_favorites) if (menu_entries_append_enum(info->list, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_GOTO_FAVORITES), @@ -4357,6 +4363,11 @@ static bool menu_displaylist_push_internal( if (menu_displaylist_ctl(DISPLAYLIST_SCAN_DIRECTORY_LIST, info)) return true; } + else if (string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_EXPLORE_TAB))) + { + if (menu_displaylist_ctl(DISPLAYLIST_EXPLORE, info)) + return true; + } else if (string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_NETPLAY_TAB))) { if (menu_displaylist_ctl(DISPLAYLIST_NETPLAY_ROOM_LIST, info)) @@ -5434,6 +5445,12 @@ unsigned menu_displaylist_build_list( case DISPLAYLIST_SYSTEM_INFO: count = menu_displaylist_parse_system_info(list); break; + case DISPLAYLIST_EXPLORE: + { + unsigned menu_displaylist_explore(file_list_t *list); + count = menu_displaylist_explore(list); + } + break; case DISPLAYLIST_SCAN_DIRECTORY_LIST: #ifdef HAVE_LIBRETRODB if (menu_entries_append_enum(list, @@ -10785,6 +10802,7 @@ bool menu_displaylist_ctl(enum menu_displaylist_ctl_state type, case DISPLAYLIST_AUDIO_SYNCHRONIZATION_SETTINGS_LIST: case DISPLAYLIST_HELP_SCREEN_LIST: case DISPLAYLIST_INFORMATION_LIST: + case DISPLAYLIST_EXPLORE: case DISPLAYLIST_SCAN_DIRECTORY_LIST: case DISPLAYLIST_SYSTEM_INFO: case DISPLAYLIST_BLUETOOTH_SETTINGS_LIST: diff --git a/menu/menu_displaylist.h b/menu/menu_displaylist.h index fcd5b44c87..344a7af226 100644 --- a/menu/menu_displaylist.h +++ b/menu/menu_displaylist.h @@ -79,6 +79,7 @@ enum menu_displaylist_ctl_state DISPLAYLIST_HORIZONTAL, DISPLAYLIST_HORIZONTAL_CONTENT_ACTIONS, DISPLAYLIST_HISTORY, + DISPLAYLIST_EXPLORE, DISPLAYLIST_FAVORITES, DISPLAYLIST_PLAYLIST, DISPLAYLIST_VIDEO_HISTORY, diff --git a/menu/menu_driver.h b/menu/menu_driver.h index 3cd6612583..df0325ee64 100644 --- a/menu/menu_driver.h +++ b/menu/menu_driver.h @@ -66,6 +66,7 @@ enum menu_settings_type MENU_VIDEO_TAB, MENU_IMAGES_TAB, MENU_NETPLAY_TAB, + MENU_EXPLORE_TAB, MENU_ADD_TAB, MENU_PLAYLISTS_TAB, MENU_SETTING_DROPDOWN_ITEM, diff --git a/menu/menu_explore.c b/menu/menu_explore.c new file mode 100644 index 0000000000..c957347e69 --- /dev/null +++ b/menu/menu_explore.c @@ -0,0 +1,817 @@ +#include +#include "menu_driver.h" +#include "menu_cbs.h" +#include "../retroarch.h" +#include "../configuration.h" +#include "../playlist.h" +#include "../libretro-db/libretrodb.h" +#include +#include + +// Stretchy buffers, invented (?) by Sean Barrett +#define ex_buf__hdr(b) (((struct ex_buf_hdr *)(b))-1) + +#define ex_buf_len(b) ((b) ? ex_buf__hdr(b)->len : 0) +#define ex_buf_cap(b) ((b) ? ex_buf__hdr(b)->cap : 0) +#define ex_buf_end(b) ((b) + ex_buf_len(b)) +#define ex_buf_sizeof(b) ((b) ? ex_buf_len(b)*sizeof(*b) : 0) + +#define ex_buf_free(b) ((b) ? (free(ex_buf__hdr(b)), (b) = NULL) : 0) +#define ex_buf_fit(b, n) ((size_t)(n) <= ex_buf_cap(b) ? 0 : (*(void**)(&(b)) = ex_buf__grow((b), (n), sizeof(*(b))))) +#define ex_buf_push(b, ...) (ex_buf_fit((b), 1 + ex_buf_len(b)), (b)[ex_buf__hdr(b)->len++] = __VA_ARGS__) +#define ex_buf_pop(b) (b)[--ex_buf__hdr(b)->len] +#define ex_buf_resize(b, sz) (ex_buf_fit((b), (sz)), ((b) ? ex_buf__hdr(b)->len = (sz) : 0)) +#define ex_buf_clear(b) ((b) ? ex_buf__hdr(b)->len = 0 : 0) + +struct ex_buf_hdr { size_t len, cap; }; +static void *ex_buf__grow(const void *buf, size_t new_len, size_t elem_size) +{ + size_t new_cap = MAX(2 * ex_buf_cap(buf), MAX(new_len, 16)); + size_t new_size = sizeof(struct ex_buf_hdr) + new_cap*elem_size; + struct ex_buf_hdr *new_hdr; + if (buf) + new_hdr = (struct ex_buf_hdr *)realloc(ex_buf__hdr(buf), new_size); + else + (new_hdr = (struct ex_buf_hdr *)malloc(new_size))->len = 0; + new_hdr->cap = new_cap; + return new_hdr + 1; +} + +// Arena allocator +typedef struct ex_arena +{ + char *ptr, *end, **blocks; +} ex_arena; + +#define EX_ARENA_ALIGNMENT 8 +#define EX_ARENA_BLOCK_SIZE (64 * 1024) +#define EX_ARENA_ALIGN_UP(n, a) (((n) + (a) - 1) & ~((a) - 1)) + +static void ex_arena_grow(ex_arena *arena, size_t min_size) +{ + size_t size = EX_ARENA_ALIGN_UP(MAX(min_size, EX_ARENA_BLOCK_SIZE), EX_ARENA_ALIGNMENT); + arena->ptr = (char *)malloc(size); + arena->end = arena->ptr + size; + ex_buf_push(arena->blocks, arena->ptr); +} + +static void *ex_arena_alloc(ex_arena *arena, size_t size) +{ + if (size > (size_t)(arena->end - arena->ptr)) + ex_arena_grow(arena, size); + void *ptr = arena->ptr; + arena->ptr = (char *)EX_ARENA_ALIGN_UP((uintptr_t)(arena->ptr + size), EX_ARENA_ALIGNMENT); + return ptr; +} + +static void ex_arena_free(ex_arena *arena) +{ + for (char **it = arena->blocks; it != ex_buf_end(arena->blocks); it++) + free(*it); + ex_buf_free(arena->blocks); + arena->ptr = arena->end = NULL; + arena->blocks = NULL; +} + +//Hash function +static uint32_t ex_hash32(const char* str) +{ + uint32_t hash = (uint32_t)0x811c9dc5; + for (unsigned char c; (c = *(str++)) != '\0';) + hash = ((hash * (uint32_t)0x01000193) ^ (uint32_t)c); + return (hash ? hash : 1); +} + +static uint32_t ex_hash32_nocase_filtered(const unsigned char* str, size_t len, unsigned char f_first, unsigned char f_last) +{ + uint32_t hash = (uint32_t)0x811c9dc5; + for (const unsigned char *end = str + len; str != end;) + { + unsigned char c = *(str++); + if (c >= f_first && c <= f_last) + hash = ((hash * (uint32_t)0x01000193) ^ (uint32_t)((c >= 'A' && c <= 'Z') ? (c | 0x20) : c)); + } + return (hash ? hash : 1); +} + +//Hashmap +typedef struct ex_hashmap32 +{ + uint32_t len, cap, *keys; + uintptr_t* vals; +} ex_hashmap32; + +static void ex_hashmap32__grow(ex_hashmap32* map, uint32_t new_cap) +{ + uint32_t oldCap = map->cap, *oldKeys = map->keys; + uintptr_t *oldVals = map->vals; + map->cap = (new_cap < 16 ? 16 : new_cap); + map->keys = (uint32_t *)calloc(map->cap, sizeof(uint32_t)); + map->vals = (uintptr_t *)malloc(map->cap * sizeof(uintptr_t)); + for (size_t i = 0; i < oldCap; i++) + { + if (!oldKeys[i]) continue; + for (uint32_t key = oldKeys[i], j = key;; j++) + { + if (!map->keys[j &= map->cap - 1]) { map->keys[j] = key; map->vals[j] = oldVals[i]; break; } + } + } + free(oldKeys); + free(oldVals); +} + +static void ex_hashmap32_free(ex_hashmap32* map) { free(map->keys); free(map->vals); } + +static uintptr_t ex_hashmap32_getnum(ex_hashmap32* map, uint32_t key) +{ + if (map->len == 0 || !key) return 0; + for (uint32_t i = key;; i++) + { + if (map->keys[i &= map->cap - 1] == key) return map->vals[i]; + if (!map->keys[i]) return 0; + } +} + +static void ex_hashmap32_setnum(ex_hashmap32* map, uint32_t key, uintptr_t val) +{ + if (!key) return; + if (2 * map->len >= map->cap) ex_hashmap32__grow(map, 2 * map->cap); + for (uint32_t i = key;; i++) + { + if (!map->keys[i &= map->cap - 1]) { map->len++; map->keys[i] = key; map->vals[i] = val; return; } + if (map->keys[i] == key) { map->vals[i] = val; return; } + } +} + +static INLINE void* ex_hashmap32_getptr (ex_hashmap32* map, uint32_t key) { return (void*)ex_hashmap32_getnum(map, key); } +static INLINE void ex_hashmap32_setptr (ex_hashmap32* map, uint32_t key, void* ptr) { ex_hashmap32_setnum(map, key, (uintptr_t)ptr); } +static INLINE void* ex_hashmap32_strgetptr(ex_hashmap32* map, const char* str) { return (void*)ex_hashmap32_getnum(map, ex_hash32(str)); } +static INLINE void ex_hashmap32_strsetptr(ex_hashmap32* map, const char* str, void* ptr) { ex_hashmap32_setnum(map, ex_hash32(str), (uintptr_t)ptr); } +static INLINE uintptr_t ex_hashmap32_strgetnum(ex_hashmap32* map, const char* str) { return ex_hashmap32_getnum(map, ex_hash32(str)); } +static INLINE void ex_hashmap32_strsetnum(ex_hashmap32* map, const char* str, uintptr_t num) { ex_hashmap32_setnum(map, ex_hash32(str), num); } + +// Explore +enum +{ + EXPLORE_BY_DEVELOPER, + EXPLORE_BY_PUBLISHER, + EXPLORE_BY_RELEASEYEAR, + EXPLORE_BY_PLAYERCOUNT, + EXPLORE_BY_GENRE, + EXPLORE_BY_ORIGIN, + EXPLORE_BY_REGION, + EXPLORE_BY_FRANCHISE, + EXPLORE_BY_TAGS, + EXPLORE_BY_SYSTEM, + EXPLORE_CAT_COUNT, + + EXPLORE_TYPE_ADDITIONALFILTER = FILE_TYPE_RDB, //database icon + EXPLORE_TYPE_FILTERNULL = MENU_SETTINGS_LAST, + EXPLORE_TYPE_SEARCH, + EXPLORE_TYPE_SHOWALL, + EXPLORE_TYPE_FIRSTCATEGORY, + EXPLORE_TYPE_FIRSTITEM = EXPLORE_TYPE_FIRSTCATEGORY + EXPLORE_CAT_COUNT, +}; + +static const struct { const char *name, *rdbkey; bool use_split, is_company, is_numeric; } + explore_by_info[EXPLORE_CAT_COUNT] = +{ + { "Developer", "developer", true, true, false }, + { "Publisher", "publisher", true, true, false }, + { "Release Year", "releaseyear", false, false, true }, + { "Player Count", "users", false, false, true }, + { "Genre", "genre", true, false, false }, + { "Origin", "origin", false, false, false }, + { "Region", "region", false, false, false }, + { "Franchise", "franchise", false, false, false }, + { "Tags", "tags", true, false, false }, + { "System", "system", false, false, false }, +}; + +typedef struct +{ + uint32_t idx; + char str[1]; +} explore_string_t; + +typedef struct +{ + const struct playlist_entry* playlist_entry; + explore_string_t *by[EXPLORE_CAT_COUNT]; + explore_string_t **split; + char* original_title; +} explore_entry_t; + +typedef struct +{ + ex_arena arena; + explore_string_t **by[EXPLORE_CAT_COUNT]; + bool has_unknown[EXPLORE_CAT_COUNT]; + + explore_entry_t* entries; + playlist_t **playlists; + const char* label_explore_item_str; + char title[1024]; + char find_string[1024]; + unsigned top_depth; +} explore_state_t; + +static explore_state_t* explore_state; + +static int explore_qsort_func_strings(const void *a_, const void *b_) +{ + const explore_string_t **a = (const explore_string_t**)a_; + const explore_string_t **b = (const explore_string_t**)b_; + if ((*a)->str[0] != (*b)->str[0]) + return (unsigned char)(*a)->str[0] - (unsigned char)(*b)->str[0]; + return strcasecmp((*a)->str, (*b)->str); +} + +static int explore_qsort_func_entries(const void *a_, const void *b_) +{ + const explore_entry_t *a = (const explore_entry_t*)a_; + const explore_entry_t *b = (const explore_entry_t*)b_; + if (a->playlist_entry->label[0] != b->playlist_entry->label[0]) + return (unsigned char)a->playlist_entry->label[0] - (unsigned char)b->playlist_entry->label[0]; + return strcasecmp(a->playlist_entry->label, b->playlist_entry->label); +} + +static int explore_qsort_func_menulist(const void *a_, const void *b_) +{ + const struct item_file *a = (const struct item_file*)a_; + const struct item_file *b = (const struct item_file*)b_; + if (a->path[0] != b->path[0]) + return (unsigned char)a->path[0] - (unsigned char)b->path[0]; + return strcasecmp(a->path, b->path); +} + +static int explore_check_company_suffix(const char* p, bool search_reverse) +{ + if (search_reverse) + { + p -= (p[-1] == '.' ? 4 : 3); + if (p[-1] != ' ') return 0; + } + if (tolower(p[0]) == 'i' && tolower(p[1]) == 'n' && tolower(p[2]) == 'c') return (p[3] == '.' ? 4 : 3); //, Inc + if (tolower(p[0]) == 'l' && tolower(p[1] )== 't' && tolower(p[2]) == 'd') return (p[3] == '.' ? 4 : 3); //, Ltd + if (tolower(p[0]) == 't' && tolower(p[1] )== 'h' && tolower(p[2]) == 'e') return (p[3] == '.' ? 4 : 3); //, The + return 0; +} + +static void explore_add_unique_string(ex_hashmap32 *maps, explore_entry_t *e, unsigned cat, const char *str, explore_string_t ***split_buf) +{ + if (!str || !*str) + { + explore_state->has_unknown[cat] = true; + return; + } + + if (!explore_by_info[cat].use_split) split_buf = NULL; + bool is_company = explore_by_info[cat].is_company; + + for (const char *p = str + 1, *p_next;; p++) + { + if (*p != '/' && *p != ',' && *p != '|' && *p != '\0') + continue; + + if (!split_buf && *p != '\0') continue; + + p_next = p; + while (*str == ' ') str++; + while (p[-1] == ' ') p--; + + if (p == str) + { + if (*p == '\0') return; + continue; + } + + if (is_company && p - str > 5) + { + p -= explore_check_company_suffix(p, true); + while (p[-1] == ' ') p--; + } + + size_t len = p - str; + uint32_t hash = ex_hash32_nocase_filtered((unsigned char*)str, len, '0', 255); + explore_string_t* entry = (explore_string_t*)ex_hashmap32_getptr(&maps[cat], hash); + if (!entry) + { + entry = (explore_string_t*)ex_arena_alloc(&explore_state->arena, sizeof(explore_string_t) + len); + memcpy(entry->str, str, len); + entry->str[len] = '\0'; + ex_buf_push(explore_state->by[cat], entry); + ex_hashmap32_setptr(&maps[cat], hash, entry); + } + + if (!e->by[cat]) e->by[cat] = entry; + else ex_buf_push(*split_buf, entry); + + if (*p_next == '\0') return; + if (is_company && *p_next == ',') + { + p = p_next + 1; + while (*p == ' ') p++; + p += explore_check_company_suffix(p, false); + while (*p == ' ') p++; + if (*p == '\0') return; + if (*p == '/' || *p == ',' || *p == '|') p_next = p; + } + p = p_next; + str = p + 1; + } +} + +static void explore_free() +{ + for (int i = 0; i != EXPLORE_CAT_COUNT; i++) + ex_buf_free(explore_state->by[i]); + + ex_buf_free(explore_state->entries); + + for (size_t i = 0; i != ex_buf_len(explore_state->playlists); i++) + playlist_free(explore_state->playlists[i]); + ex_buf_free(explore_state->playlists); + + ex_arena_free(&explore_state->arena); + free(explore_state); + explore_state = NULL; +} + +static void explore_build_list() +{ + char tmp[PATH_MAX_LENGTH]; + + if (explore_state) + explore_free(); + + explore_state = calloc(1, sizeof(explore_state_t)); + explore_state->label_explore_item_str = msg_hash_to_str(MENU_ENUM_LABEL_EXPLORE_ITEM); + + settings_t *settings = config_get_ptr(); + const char *directory_playlist = settings->paths.directory_playlist; + const char *directory_database = settings->paths.path_content_database; + + ex_hashmap32 rdb_indices = {0}; + struct explore_rdb { libretrodb_t *handle; ex_hashmap32 playlist_entries; } *rdbs = NULL; + + // Index all playlists + for (libretro_vfs_implementation_dir *dir = retro_vfs_opendir_impl(directory_playlist, false); dir;) + { + if (!retro_vfs_readdir_impl(dir)) { retro_vfs_closedir_impl(dir); break; } + + playlist_config_t playlist_config = {0}; + const char* fname = retro_vfs_dirent_get_name_impl(dir), *fext = (fname ? strrchr(fname, '.') : NULL); + if (!fext || strcasecmp(fext, ".lpl")) + continue; + + fill_pathname_join(playlist_config.path, directory_playlist, fname, sizeof(playlist_config.path)); + playlist_config.capacity = COLLECTION_SIZE; + playlist_t *playlist = playlist_init(&playlist_config); + + size_t used_entries = 0; + for (size_t j = 0; j < playlist_size(playlist); j++) + { + const struct playlist_entry *entry = NULL; + playlist_get_index(playlist, j, &entry); + + // Maybe remove this to also include playlist entries with no crc/db/label (build label from file name) + if (!entry->crc32 || !*entry->crc32 || !entry->db_name || !*entry->db_name || !entry->label || !*entry->label) continue; + + uintptr_t rdb_num = ex_hashmap32_strgetnum(&rdb_indices, entry->db_name); + if (!rdb_num) + { + struct explore_rdb rdb = { libretrodb_new() }; + fill_pathname_join_noext(tmp, directory_database, entry->db_name, sizeof(tmp)); + strlcat(tmp, ".rdb", sizeof(tmp)); + if (libretrodb_open(tmp, rdb.handle) != 0) + { + // invalid rdb file + libretrodb_free(rdb.handle); + ex_hashmap32_strsetnum(&rdb_indices, entry->db_name, (uintptr_t)-1); + continue; + } + + ex_buf_push(rdbs, rdb); + rdb_num = (uintptr_t)ex_buf_len(rdbs); + ex_hashmap32_strsetnum(&rdb_indices, entry->db_name, rdb_num); + } + + if (rdb_num == (uintptr_t)-1) + continue; + + struct explore_rdb* rdb = &rdbs[rdb_num - 1]; + uint32_t entry_crc32 = (uint32_t)strtoul(entry->crc32, NULL, 16); + ex_hashmap32_setptr(&rdb->playlist_entries, entry_crc32, (void*)entry); + used_entries++; + } + + if (used_entries) + ex_buf_push(explore_state->playlists, playlist); + else + playlist_free(playlist); + } + + ex_hashmap32 map_cores = {0}; + core_info_list_t *core_list = NULL; + if (core_info_get_list(&core_list) && core_list) + for (size_t i = 0; i != core_list->count; i++) + ex_hashmap32_strsetptr(&map_cores, core_list->list[i].display_name, &core_list->list[i]); + + // Loop through all RDBs referenced in the playlists and load meta data strings + explore_string_t **split_buf = NULL; + ex_hashmap32 cat_maps[EXPLORE_CAT_COUNT] = {{0}}; + for (size_t i = 0; i != ex_buf_len(rdbs); i++) + { + struct explore_rdb* rdb = &rdbs[i]; + + struct rmsgpack_dom_value item; + libretrodb_cursor_t *cur = libretrodb_cursor_new(); + bool more = (libretrodb_cursor_open(rdb->handle, cur, NULL) == 0 + && libretrodb_cursor_read_item(cur, &item) == 0); + + for (; more; more = (rmsgpack_dom_value_free(&item), libretrodb_cursor_read_item(cur, &item) == 0)) + { + char numeric_buf[EXPLORE_CAT_COUNT][16]; + char* fields[EXPLORE_CAT_COUNT] = {NULL}; + char* original_title = NULL; + bool found_crc32 = false; + uint32_t crc32; + + if (item.type != RDT_MAP) + continue; + + for (unsigned i = 0; i < item.val.map.len; i++) + { + struct rmsgpack_dom_value *key = &item.val.map.items[i].key; + struct rmsgpack_dom_value *val = &item.val.map.items[i].value; + if (!key || !val || key->type != RDT_STRING) + continue; + + const char* key_str = key->val.string.buff; + if (string_is_equal(key_str, "crc")) + { + crc32 = swap_if_little32(*(uint32_t*)val->val.binary.buff); + found_crc32 = true; + continue; + } + if (string_is_equal(key_str, "original_title")) + { + original_title = val->val.string.buff; + continue; + } + for (unsigned cat = 0; cat != EXPLORE_CAT_COUNT; cat++) + { + if (!string_is_equal(key_str, explore_by_info[cat].rdbkey)) continue; + if (explore_by_info[cat].is_numeric) + { + if (!val->val.int_) break; + snprintf(numeric_buf[cat], sizeof(numeric_buf[cat]), "%d", (int)val->val.int_); + fields[cat] = numeric_buf[cat]; + break; + } + if (val->type != RDT_STRING) break; + fields[cat] = val->val.string.buff; + break; + } + } + + if (!found_crc32) + continue; + + const struct playlist_entry *entry = (const struct playlist_entry *)ex_hashmap32_getptr(&rdb->playlist_entries, crc32); + if (!entry) + continue; + + explore_entry_t e = {entry}; + for (unsigned cat = 0; cat != EXPLORE_CAT_COUNT; cat++) + explore_add_unique_string(cat_maps, &e, cat, fields[cat], &split_buf); + + core_info_t* core_info = (core_info_t*)ex_hashmap32_strgetptr(&map_cores, entry->core_name); + explore_add_unique_string(cat_maps, &e, EXPLORE_BY_SYSTEM, (core_info ? core_info->systemname : NULL), NULL); + + if (original_title && *original_title) + { + size_t len = strlen(original_title) + 1; + e.original_title = (char*)ex_arena_alloc(&explore_state->arena, len); + memcpy(e.original_title, original_title, len); + } + + if (ex_buf_len(split_buf)) + { + ex_buf_push(split_buf, NULL); //terminator + size_t len = ex_buf_sizeof(split_buf); + e.split = (explore_string_t **)ex_arena_alloc(&explore_state->arena, len); + memcpy(e.split, split_buf, len); + ex_buf_clear(split_buf); + } + + ex_buf_push(explore_state->entries, e); + } + libretrodb_cursor_close(cur); + libretrodb_cursor_free(cur); + libretrodb_close(rdb->handle); + libretrodb_free(rdb->handle); + ex_hashmap32_free(&rdb->playlist_entries); + } + ex_buf_free(split_buf); + ex_hashmap32_free(&map_cores); + ex_hashmap32_free(&rdb_indices); + ex_buf_free(rdbs); + + for (int i = 0; i != EXPLORE_CAT_COUNT; i++) + { + size_t len = ex_buf_len(explore_state->by[i]); + qsort(explore_state->by[i], len, sizeof(*explore_state->by[i]), explore_qsort_func_strings); + for (uint32_t idx = 0; idx != len; idx++) + explore_state->by[i][idx]->idx = idx; + + ex_hashmap32_free(&cat_maps[i]); + } + qsort(explore_state->entries, ex_buf_len(explore_state->entries), sizeof(*explore_state->entries), explore_qsort_func_entries); +} + +static int explore_action_get_title(const char *path, const char *label, unsigned menu_type, char *s, size_t len) +{ + snprintf(s, len, "%s", explore_state->title); + return 0; +} + +static void explore_append_title(const char* fmt, ...) +{ + va_list ap; + size_t len = strlen(explore_state->title); + va_start(ap, fmt); + vsnprintf(explore_state->title + len, sizeof(explore_state->title) - len, fmt, ap); + va_end(ap); +} + +static int explore_action_sublabel_spacer(file_list_t *list, unsigned type, unsigned i, const char *label, const char *path, char *s, size_t len) +{ + strlcpy(s, " ", len); + return 1; // 1 means it'll never change and can be cached +} + +static int explore_action_ok(const char *path, const char *label, unsigned type, size_t idx, size_t entry_idx) +{ + const char* explore_tab = msg_hash_to_str(MENU_ENUM_LABEL_EXPLORE_TAB); + filebrowser_clear_type(); + return generic_action_ok_displaylist_push(explore_tab, NULL, explore_tab, type, idx, entry_idx, ACTION_OK_DL_PUSH_DEFAULT); +} + +static menu_file_list_cbs_t* explore_menu_entry(file_list_t *list, const char *path, unsigned type) +{ + menu_entries_append_enum(list, path, explore_state->label_explore_item_str, MENU_ENUM_LABEL_EXPLORE_ITEM, type, 0, 0); + menu_file_list_cbs_t* cbs = ((menu_file_list_cbs_t*)list->list[list->size-1].actiondata); + cbs->action_ok = explore_action_ok; + return cbs; +} + +static void explore_menu_add_spacer(file_list_t *list) +{ + if (!list->size) return; + ((menu_file_list_cbs_t*)list->list[list->size-1].actiondata)->action_sublabel = explore_action_sublabel_spacer; +} + +static void explore_action_find_complete(void *userdata, const char *line) +{ + menu_input_dialog_end(); + if (line && *line) + { + strlcpy(explore_state->find_string, line, sizeof(explore_state->find_string)); + explore_action_ok(NULL, NULL, EXPLORE_TYPE_SEARCH, 0, 0); + } +} + +static int explore_action_ok_find(const char *path, const char *label, unsigned type, size_t idx, size_t entry_idx) +{ + menu_input_ctx_line_t line = {0}; + line.label = "Search Text"; + line.cb = explore_action_find_complete; + menu_input_dialog_start(&line); + return 0; +} + +unsigned menu_displaylist_explore(file_list_t *list) +{ + char tmp[512]; + file_list_t *menu_stack = menu_entries_get_menu_stack_ptr(0); + + if (!explore_state) + { + explore_build_list(); + explore_state->top_depth = (unsigned)menu_stack->size - 1; + } + + if (menu_stack->size > 1) + { + struct item_file *stack = &menu_stack->list[menu_stack->size - 1]; + menu_file_list_cbs_t* cbs = ((menu_file_list_cbs_t*)stack->actiondata); + cbs->action_get_title = explore_action_get_title; + } + + struct item_file *stack_top = menu_stack->list + explore_state->top_depth; + unsigned depth = (unsigned)menu_stack->size - 1 - explore_state->top_depth; + unsigned current_type = stack_top[depth].type; + unsigned current_cat = current_type - EXPLORE_TYPE_FIRSTCATEGORY; + unsigned previous_cat = stack_top[depth ? depth - 1 : 0].type - EXPLORE_TYPE_FIRSTCATEGORY; + + if (depth) + { + ((menu_file_list_cbs_t*)stack_top[depth].actiondata)->action_get_title = explore_action_get_title; + + bool clear_find_text = (current_type != EXPLORE_TYPE_SEARCH); + explore_state->title[0] = '\0'; + for (unsigned i = 1, levels = 0; i < depth; i++) + { + unsigned by_category = (stack_top[i].type - EXPLORE_TYPE_FIRSTCATEGORY); + if (stack_top[i].type == EXPLORE_TYPE_SEARCH) clear_find_text = false; + if (by_category >= EXPLORE_CAT_COUNT) continue; + + unsigned by_selected_type = stack_top[i + 1].type; + const char* name = explore_by_info[by_category].name; + explore_string_t **entries = explore_state->by[by_category]; + explore_append_title("%s%s: %s", (levels++ ? " / " : ""), name, (by_selected_type != EXPLORE_TYPE_FILTERNULL ? entries[by_selected_type - EXPLORE_TYPE_FIRSTITEM]->str : "Unknown")); + } + + if (clear_find_text) + explore_state->find_string[0] = '\0'; + + if (*explore_state->find_string) + explore_append_title(" '%s'", explore_state->find_string); + } + + void playlist_set_cached(playlist_t* pl); + playlist_set_cached(NULL); + + if (current_type == MENU_EXPLORE_TAB || current_type == EXPLORE_TYPE_ADDITIONALFILTER) + { + // Explore top or selecting an additional filter + bool is_top = (current_type == MENU_EXPLORE_TAB); + if (is_top) strlcpy(explore_state->title, "Explore", sizeof(explore_state->title)); + else explore_append_title(" - Additional Filter"); + + if (is_top || !*explore_state->find_string) + { + explore_menu_entry(list, "Search Name ...", EXPLORE_TYPE_SEARCH)->action_ok = explore_action_ok_find; + explore_menu_add_spacer(list); + } + + for (unsigned cat = 0; cat < EXPLORE_CAT_COUNT; cat++) + { + const char* name = explore_by_info[cat].name; + explore_string_t **entries = explore_state->by[cat]; + if (!ex_buf_len(entries)) continue; + + for (unsigned i = 1; i < depth; i++) + if (stack_top[i].type == cat + EXPLORE_TYPE_FIRSTCATEGORY) + goto SKIP_EXPLORE_BY_CATEGORY; + + if (!is_top) + snprintf(tmp, sizeof(tmp), "By %s", name); + else if (explore_by_info[cat].is_numeric) + snprintf(tmp, sizeof(tmp), "By %s (%s - %s)", name, entries[0]->str, entries[ex_buf_len(entries) - 1]->str); + else + snprintf(tmp, sizeof(tmp), "By %s (%u entries)", name, (unsigned)ex_buf_len(entries)); + explore_menu_entry(list, tmp, cat + EXPLORE_TYPE_FIRSTCATEGORY); + + SKIP_EXPLORE_BY_CATEGORY:; + } + + if (is_top) + { + explore_menu_add_spacer(list); + explore_menu_entry(list, "Show All", EXPLORE_TYPE_SHOWALL); + } + } + else if (depth == 1 && current_type != EXPLORE_TYPE_SEARCH && current_type != EXPLORE_TYPE_SHOWALL) + { + // List all items in a selected explore by category + explore_string_t **entries = explore_state->by[current_cat]; + for (int i = 0, i_last = ex_buf_len(entries) - 1; i <= i_last; i++) + explore_menu_entry(list, entries[i]->str, EXPLORE_TYPE_FIRSTITEM + i); + if (explore_state->has_unknown[current_cat]) + { + explore_menu_add_spacer(list); + explore_menu_entry(list, "Unknown", EXPLORE_TYPE_FILTERNULL); + } + explore_append_title("Select %s", explore_by_info[current_cat].name); + } + else if (previous_cat < EXPLORE_CAT_COUNT || current_type < EXPLORE_TYPE_FIRSTITEM) + { + unsigned levels = 0; + unsigned cats[10]; + explore_string_t* filter[10]; + bool use_split[10]; + bool use_find = (*explore_state->find_string != '\0'); + + bool is_show_all = (depth == 1 && !use_find); + bool is_filtered_category = (current_cat < EXPLORE_CAT_COUNT); + bool filtered_category_have_unknown = false; + + if (is_filtered_category) + { + // List filtered items in a selected explore by category + explore_append_title(" - Select %s", explore_by_info[current_cat].name); + } + else + { + // Game list + if (is_show_all) + { + explore_append_title("All"); + explore_menu_entry(list, "Search Name ...", EXPLORE_TYPE_SEARCH)->action_ok = explore_action_ok_find; + } + else + explore_menu_entry(list, "Add Additional Filter", EXPLORE_TYPE_ADDITIONALFILTER); + explore_menu_add_spacer(list); + } + + for (unsigned i = 1; i < depth; i++) + { + unsigned by_category = (stack_top[i].type - EXPLORE_TYPE_FIRSTCATEGORY); + if (by_category >= EXPLORE_CAT_COUNT) continue; + + unsigned by_selected_type = stack_top[i + 1].type; + explore_string_t **entries = explore_state->by[by_category]; + cats [levels] = by_category; + use_split[levels] = explore_by_info[by_category].use_split; + filter [levels] = (by_selected_type == EXPLORE_TYPE_FILTERNULL ? NULL : entries[by_selected_type - EXPLORE_TYPE_FIRSTITEM]); + levels++; + } + + ex_hashmap32 map_filtered_category = {0}; + for (explore_entry_t *e = explore_state->entries, *e_end = ex_buf_end(explore_state->entries); e != e_end; e++) + { + for (unsigned lvl = 0; lvl != levels; lvl++) + { + if (filter[lvl] == e->by[cats[lvl]]) continue; + if (use_split[lvl] && e->split) + { + explore_string_t** split = e->split; + do { if (*split == filter[lvl]) break; } while (*(++split)); + if (*split) continue; + } + goto SKIP_ENTRY; + } + + if (use_find && !strcasestr(e->playlist_entry->label, explore_state->find_string)) + goto SKIP_ENTRY; + + if (is_filtered_category) + { + explore_string_t* str = e->by[current_cat]; + if (!str) { filtered_category_have_unknown = true; continue; } + if (ex_hashmap32_getnum(&map_filtered_category, str->idx + 1)) continue; + ex_hashmap32_setnum(&map_filtered_category, str->idx + 1, 1); + explore_menu_entry(list, str->str, EXPLORE_TYPE_FIRSTITEM + str->idx); + } + else + { + explore_menu_entry(list, (e->original_title ? e->original_title : e->playlist_entry->label), EXPLORE_TYPE_FIRSTITEM + (e - explore_state->entries)); + } + + SKIP_ENTRY:; + } + + if (is_filtered_category) + qsort(list->list, list->size, sizeof(*list->list), explore_qsort_func_menulist); + + if (is_filtered_category && filtered_category_have_unknown) + { + explore_menu_add_spacer(list); + explore_menu_entry(list, "Unknown", EXPLORE_TYPE_FILTERNULL); + } + + explore_append_title(" (%u)", (unsigned) (list->size - 1)); + } + else + { + // Content page of selected game + const struct playlist_entry *pl_entry = explore_state->entries[current_type - EXPLORE_TYPE_FIRSTITEM].playlist_entry; + + snprintf(explore_state->title, sizeof(explore_state->title), "%s", pl_entry->label); + + for (int pl_idx = 0; pl_idx != ex_buf_len(explore_state->playlists); pl_idx++) + { + playlist_t* pl = explore_state->playlists[pl_idx]; + const struct playlist_entry* pl_first; + playlist_get_index(pl, 0, &pl_first); + if (pl_entry < pl_first || pl_entry >= pl_first + playlist_size(pl)) + continue; + + // Fake all the state so the content screen and information screen think we're viewing via playlist + playlist_set_cached(pl); + menu_handle_t *menu = menu_driver_get_ptr(); + menu->rpl_entry_selection_ptr = (pl_entry - pl_first); + strlcpy(menu->deferred_path, pl_entry->path, sizeof(menu->deferred_path)); + + menu_displaylist_info_t info = {0}; + info.list = list; + menu_displaylist_ctl(DISPLAYLIST_HORIZONTAL_CONTENT_ACTIONS, &info); + break; + } + } + + return list->size; +} diff --git a/msg_hash.h b/msg_hash.h index c8fdf33222..ee00e3bb46 100644 --- a/msg_hash.h +++ b/msg_hash.h @@ -1890,6 +1890,9 @@ enum msg_hash_enums MENU_LABEL(SETTINGS_TAB), MENU_LABEL(HISTORY_TAB), MENU_LABEL(FAVORITES_TAB), + MENU_ENUM_LABEL_EXPLORE_TAB, + MENU_ENUM_LABEL_EXPLORE_ITEM, + MENU_ENUM_LABEL_VALUE_EXPLORE_TAB, MENU_LABEL(ADD_TAB), MENU_LABEL(NETPLAY_TAB), MENU_LABEL(PLAYLISTS_TAB), diff --git a/playlist.c b/playlist.c index 291d80bf31..c0a66e6552 100644 --- a/playlist.c +++ b/playlist.c @@ -2993,3 +2993,9 @@ core_info_t *playlist_get_default_core_info(playlist_t* playlist) return NULL; } + +// hack for allowing the explore view to switch over to a playlist item +void playlist_set_cached(playlist_t* pl) +{ + playlist_cached = pl; +} diff --git a/retroarch.c b/retroarch.c index 906bac9ac1..a6b3a49f50 100644 --- a/retroarch.c +++ b/retroarch.c @@ -6051,6 +6051,7 @@ bool menu_entries_append_enum( if ( enum_idx != MENU_ENUM_LABEL_PLAYLIST_ENTRY && enum_idx != MENU_ENUM_LABEL_PLAYLIST_COLLECTION_ENTRY + && enum_idx != MENU_ENUM_LABEL_EXPLORE_ITEM && enum_idx != MENU_ENUM_LABEL_RDB_ENTRY) cbs->setting = menu_setting_find_enum(enum_idx); From 0d6ceafb8bcfa6c57be3dee7c348d948404982e1 Mon Sep 17 00:00:00 2001 From: twinaphex Date: Tue, 28 Jul 2020 05:26:44 +0200 Subject: [PATCH 04/17] (Menu explore) Buildfix --- menu/menu_explore.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/menu/menu_explore.c b/menu/menu_explore.c index c957347e69..539fa977d0 100644 --- a/menu/menu_explore.c +++ b/menu/menu_explore.c @@ -432,11 +432,12 @@ static void explore_build_list() for (; more; more = (rmsgpack_dom_value_free(&item), libretrodb_cursor_read_item(cur, &item) == 0)) { + uint32_t crc32; char numeric_buf[EXPLORE_CAT_COUNT][16]; char* fields[EXPLORE_CAT_COUNT] = {NULL}; - char* original_title = NULL; - bool found_crc32 = false; - uint32_t crc32; + char* original_title = NULL; + bool found_crc32 = false; + core_info_t* core_info = NULL; if (item.type != RDT_MAP) continue; @@ -487,7 +488,8 @@ static void explore_build_list() for (unsigned cat = 0; cat != EXPLORE_CAT_COUNT; cat++) explore_add_unique_string(cat_maps, &e, cat, fields[cat], &split_buf); - core_info_t* core_info = (core_info_t*)ex_hashmap32_strgetptr(&map_cores, entry->core_name); + if (!string_is_empty(entry->core_name)) + core_info = (core_info_t*)ex_hashmap32_strgetptr(&map_cores, entry->core_name); explore_add_unique_string(cat_maps, &e, EXPLORE_BY_SYSTEM, (core_info ? core_info->systemname : NULL), NULL); if (original_title && *original_title) From 1c0fb50877fdcca0e5560632946648309abf4ad3 Mon Sep 17 00:00:00 2001 From: bulzipke Date: Tue, 28 Jul 2020 15:03:43 +0900 Subject: [PATCH 05/17] Update ctr_gfx.c It fixed the sound cracking when opening or closing the 3DS screen. --- gfx/drivers/ctr_gfx.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/gfx/drivers/ctr_gfx.c b/gfx/drivers/ctr_gfx.c index 7670b0624f..b7103aa708 100644 --- a/gfx/drivers/ctr_gfx.c +++ b/gfx/drivers/ctr_gfx.c @@ -294,6 +294,19 @@ static void ctr_lcd_aptHook(APT_HookType hook, void* param) svcCloseHandle(lcd_handle); } } + + if (menu_driver_is_alive()) + { + return; + } + else if ((hook == APTHOOK_ONSUSPEND) || (hook == APTHOOK_ONSLEEP)) + { + command_event(CMD_EVENT_AUDIO_STOP, NULL); + } + else if ((hook == APTHOOK_ONRESTORE) || (hook == APTHOOK_ONWAKEUP)) + { + command_event(CMD_EVENT_AUDIO_START, NULL); + } } static void ctr_vsync_hook(ctr_video_t* ctr) From 36c27378bf20d71f922d13d5d4726f3e9ca17875 Mon Sep 17 00:00:00 2001 From: twinaphex Date: Tue, 28 Jul 2020 10:47:26 +0200 Subject: [PATCH 06/17] Cleanups; C89_BUILD buildfixes, CXX_BUILD buildfixes, etc --- menu/menu_explore.c | 1509 +++++++++++++++++++++++++------------------ playlist.c | 12 +- playlist.h | 2 + 3 files changed, 888 insertions(+), 635 deletions(-) diff --git a/menu/menu_explore.c b/menu/menu_explore.c index 539fa977d0..0688d2a719 100644 --- a/menu/menu_explore.c +++ b/menu/menu_explore.c @@ -1,3 +1,19 @@ +/* RetroArch - A frontend for libretro. + * Copyright (C) 2011-2020 - Daniel De Matteis + * Copyright (C) 2020 - Psyraven + * + * RetroArch is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with RetroArch. + * If not, see . + */ + #include #include "menu_driver.h" #include "menu_cbs.h" @@ -8,812 +24,1045 @@ #include #include -// Stretchy buffers, invented (?) by Sean Barrett -#define ex_buf__hdr(b) (((struct ex_buf_hdr *)(b))-1) +/* Stretchy buffers, invented (?) by Sean Barrett */ +#define EX_BUF__HDR(b) (((struct ex_buf_hdr *)(b))-1) -#define ex_buf_len(b) ((b) ? ex_buf__hdr(b)->len : 0) -#define ex_buf_cap(b) ((b) ? ex_buf__hdr(b)->cap : 0) -#define ex_buf_end(b) ((b) + ex_buf_len(b)) -#define ex_buf_sizeof(b) ((b) ? ex_buf_len(b)*sizeof(*b) : 0) +#define EX_BUF_LEN(b) ((b) ? EX_BUF__HDR(b)->len : 0) +#define EX_BUF_CAP(b) ((b) ? EX_BUF__HDR(b)->cap : 0) +#define EX_BUF_END(b) ((b) + EX_BUF_LEN(b)) +#define EX_BUF_SIZEOF(b) ((b) ? EX_BUF_LEN(b)*sizeof(*b) : 0) -#define ex_buf_free(b) ((b) ? (free(ex_buf__hdr(b)), (b) = NULL) : 0) -#define ex_buf_fit(b, n) ((size_t)(n) <= ex_buf_cap(b) ? 0 : (*(void**)(&(b)) = ex_buf__grow((b), (n), sizeof(*(b))))) -#define ex_buf_push(b, ...) (ex_buf_fit((b), 1 + ex_buf_len(b)), (b)[ex_buf__hdr(b)->len++] = __VA_ARGS__) -#define ex_buf_pop(b) (b)[--ex_buf__hdr(b)->len] -#define ex_buf_resize(b, sz) (ex_buf_fit((b), (sz)), ((b) ? ex_buf__hdr(b)->len = (sz) : 0)) -#define ex_buf_clear(b) ((b) ? ex_buf__hdr(b)->len = 0 : 0) - -struct ex_buf_hdr { size_t len, cap; }; -static void *ex_buf__grow(const void *buf, size_t new_len, size_t elem_size) -{ - size_t new_cap = MAX(2 * ex_buf_cap(buf), MAX(new_len, 16)); - size_t new_size = sizeof(struct ex_buf_hdr) + new_cap*elem_size; - struct ex_buf_hdr *new_hdr; - if (buf) - new_hdr = (struct ex_buf_hdr *)realloc(ex_buf__hdr(buf), new_size); - else - (new_hdr = (struct ex_buf_hdr *)malloc(new_size))->len = 0; - new_hdr->cap = new_cap; - return new_hdr + 1; -} - -// Arena allocator -typedef struct ex_arena -{ - char *ptr, *end, **blocks; -} ex_arena; +#define EX_BUF_FREE(b) ((b) ? (free(EX_BUF__HDR(b)), (b) = NULL) : 0) +#define EX_BUF_FIT(b, n) ((size_t)(n) <= EX_BUF_CAP(b) ? 0 : (*(void**)(&(b)) = ex_buf__grow((b), (n), sizeof(*(b))))) +#define EX_BUF_PUSH(b, val) (EX_BUF_FIT((b), 1 + EX_BUF_LEN(b)), (b)[EX_BUF__HDR(b)->len++] = (val)) +#define EX_BUF_POP(b) (b)[--EX_BUF__HDR(b)->len] +#define EX_BUF_RESIZE(b, sz) (EX_BUF_FIT((b), (sz)), ((b) ? EX_BUF__HDR(b)->len = (sz) : 0)) +#define EX_BUF_CLEAR(b) ((b) ? EX_BUF__HDR(b)->len = 0 : 0) #define EX_ARENA_ALIGNMENT 8 #define EX_ARENA_BLOCK_SIZE (64 * 1024) #define EX_ARENA_ALIGN_UP(n, a) (((n) + (a) - 1) & ~((a) - 1)) +/* Explore */ +enum +{ + EXPLORE_BY_DEVELOPER = 0, + EXPLORE_BY_PUBLISHER, + EXPLORE_BY_RELEASEYEAR, + EXPLORE_BY_PLAYERCOUNT, + EXPLORE_BY_GENRE, + EXPLORE_BY_ORIGIN, + EXPLORE_BY_REGION, + EXPLORE_BY_FRANCHISE, + EXPLORE_BY_TAGS, + EXPLORE_BY_SYSTEM, + EXPLORE_CAT_COUNT, + + EXPLORE_TYPE_ADDITIONALFILTER = FILE_TYPE_RDB, /* database icon */ + EXPLORE_TYPE_FILTERNULL = MENU_SETTINGS_LAST, + EXPLORE_TYPE_SEARCH, + EXPLORE_TYPE_SHOWALL, + EXPLORE_TYPE_FIRSTCATEGORY, + EXPLORE_TYPE_FIRSTITEM = EXPLORE_TYPE_FIRSTCATEGORY + EXPLORE_CAT_COUNT +}; + +struct ex_buf_hdr +{ + size_t len; + size_t cap; +}; + +/* Arena allocator */ +typedef struct ex_arena +{ + char *ptr; + char *end; + char **blocks; +} ex_arena; + +typedef struct ex_hashmap32 +{ + uint32_t len; + uint32_t cap; + uint32_t *keys; + uintptr_t *vals; +} ex_hashmap32; + +typedef struct +{ + uint32_t idx; + char str[1]; +} explore_string_t; + +typedef struct +{ + const struct playlist_entry* playlist_entry; + explore_string_t *by[EXPLORE_CAT_COUNT]; + explore_string_t **split; + char* original_title; +} explore_entry_t; + +typedef struct +{ + ex_arena arena; + explore_string_t **by[EXPLORE_CAT_COUNT]; + bool has_unknown[EXPLORE_CAT_COUNT]; + + explore_entry_t* entries; + playlist_t **playlists; + const char* label_explore_item_str; + char title[1024]; + char find_string[1024]; + unsigned top_depth; +} explore_state_t; + +static const struct { const char *name, *rdbkey; bool use_split, is_company, is_numeric; } +explore_by_info[EXPLORE_CAT_COUNT] = +{ + { "Developer", "developer", true, true, false }, + { "Publisher", "publisher", true, true, false }, + { "Release Year", "releaseyear", false, false, true }, + { "Player Count", "users", false, false, true }, + { "Genre", "genre", true, false, false }, + { "Origin", "origin", false, false, false }, + { "Region", "region", false, false, false }, + { "Franchise", "franchise", false, false, false }, + { "Tags", "tags", true, false, false }, + { "System", "system", false, false, false }, +}; + +/* TODO/FIXME - static global */ +static explore_state_t* explore_state; + +static void *ex_buf__grow(const void *buf, + size_t new_len, size_t elem_size) +{ + struct ex_buf_hdr *new_hdr; + size_t new_cap = MAX(2 * EX_BUF_CAP(buf), MAX(new_len, 16)); + size_t new_size = sizeof(struct ex_buf_hdr) + new_cap*elem_size; + if (buf) + new_hdr = (struct ex_buf_hdr *)realloc(EX_BUF__HDR(buf), new_size); + else + { + new_hdr = (struct ex_buf_hdr *)malloc(new_size); + new_hdr->len = 0; + } + new_hdr->cap = new_cap; + return new_hdr + 1; +} + static void ex_arena_grow(ex_arena *arena, size_t min_size) { - size_t size = EX_ARENA_ALIGN_UP(MAX(min_size, EX_ARENA_BLOCK_SIZE), EX_ARENA_ALIGNMENT); - arena->ptr = (char *)malloc(size); - arena->end = arena->ptr + size; - ex_buf_push(arena->blocks, arena->ptr); + size_t size = EX_ARENA_ALIGN_UP( + MAX(min_size, EX_ARENA_BLOCK_SIZE), EX_ARENA_ALIGNMENT); + arena->ptr = (char *)malloc(size); + arena->end = arena->ptr + size; + EX_BUF_PUSH(arena->blocks, arena->ptr); } static void *ex_arena_alloc(ex_arena *arena, size_t size) { - if (size > (size_t)(arena->end - arena->ptr)) - ex_arena_grow(arena, size); - void *ptr = arena->ptr; - arena->ptr = (char *)EX_ARENA_ALIGN_UP((uintptr_t)(arena->ptr + size), EX_ARENA_ALIGNMENT); - return ptr; + void *ptr = NULL; + + if (size > (size_t)(arena->end - arena->ptr)) + ex_arena_grow(arena, size); + + ptr = arena->ptr; + arena->ptr = (char *) + EX_ARENA_ALIGN_UP((uintptr_t)(arena->ptr + size), EX_ARENA_ALIGNMENT); + return ptr; } static void ex_arena_free(ex_arena *arena) { - for (char **it = arena->blocks; it != ex_buf_end(arena->blocks); it++) - free(*it); - ex_buf_free(arena->blocks); - arena->ptr = arena->end = NULL; - arena->blocks = NULL; + char **it; + + for (it = arena->blocks; it != EX_BUF_END(arena->blocks); it++) + free(*it); + + EX_BUF_FREE(arena->blocks); + arena->ptr = NULL; + arena->end = NULL; + arena->blocks = NULL; } -//Hash function +/* Hash function */ static uint32_t ex_hash32(const char* str) { - uint32_t hash = (uint32_t)0x811c9dc5; - for (unsigned char c; (c = *(str++)) != '\0';) - hash = ((hash * (uint32_t)0x01000193) ^ (uint32_t)c); - return (hash ? hash : 1); + unsigned char c; + uint32_t hash = (uint32_t)0x811c9dc5; + for (; (c = *(str++)) != '\0';) + hash = ((hash * (uint32_t)0x01000193) ^ (uint32_t)c); + if (hash) + return hash; + return 1; } -static uint32_t ex_hash32_nocase_filtered(const unsigned char* str, size_t len, unsigned char f_first, unsigned char f_last) +static uint32_t ex_hash32_nocase_filtered( + const unsigned char* str, size_t len, + unsigned char f_first, unsigned char f_last) { - uint32_t hash = (uint32_t)0x811c9dc5; - for (const unsigned char *end = str + len; str != end;) - { - unsigned char c = *(str++); - if (c >= f_first && c <= f_last) - hash = ((hash * (uint32_t)0x01000193) ^ (uint32_t)((c >= 'A' && c <= 'Z') ? (c | 0x20) : c)); - } - return (hash ? hash : 1); + const unsigned char *end = NULL; + uint32_t hash = (uint32_t)0x811c9dc5; + for (end = str + len; str != end;) + { + unsigned char c = *(str++); + if (c >= f_first && c <= f_last) + hash = ((hash * (uint32_t)0x01000193) + ^ (uint32_t)((c >= 'A' && c <= 'Z') + ? (c | 0x20) : c)); + } + if (hash) + return hash; + return 1; } -//Hashmap -typedef struct ex_hashmap32 -{ - uint32_t len, cap, *keys; - uintptr_t* vals; -} ex_hashmap32; - +/* Hashmap */ static void ex_hashmap32__grow(ex_hashmap32* map, uint32_t new_cap) { - uint32_t oldCap = map->cap, *oldKeys = map->keys; - uintptr_t *oldVals = map->vals; - map->cap = (new_cap < 16 ? 16 : new_cap); - map->keys = (uint32_t *)calloc(map->cap, sizeof(uint32_t)); - map->vals = (uintptr_t *)malloc(map->cap * sizeof(uintptr_t)); - for (size_t i = 0; i < oldCap; i++) - { - if (!oldKeys[i]) continue; - for (uint32_t key = oldKeys[i], j = key;; j++) - { - if (!map->keys[j &= map->cap - 1]) { map->keys[j] = key; map->vals[j] = oldVals[i]; break; } - } - } - free(oldKeys); - free(oldVals); + size_t i, j; + uint32_t old_cap = map->cap; + uint32_t *old_keys = map->keys; + uintptr_t *old_vals = map->vals; + + map->cap = (new_cap < 16) ? 16 : new_cap; + map->keys = (uint32_t *)calloc(map->cap, sizeof(uint32_t)); + map->vals = (uintptr_t *)malloc(map->cap * sizeof(uintptr_t)); + + for (i = 0; i < old_cap; i++) + { + uint32_t key; + if (!old_keys[i]) + continue; + + for (key = old_keys[i], j = key;; j++) + { + if (!map->keys[j &= map->cap - 1]) + { + map->keys[j] = key; + map->vals[j] = old_vals[i]; + break; + } + } + } + + free(old_keys); + free(old_vals); } -static void ex_hashmap32_free(ex_hashmap32* map) { free(map->keys); free(map->vals); } +static void ex_hashmap32_free(ex_hashmap32* map) +{ + if (!map) + return; + free(map->keys); + free(map->vals); +} static uintptr_t ex_hashmap32_getnum(ex_hashmap32* map, uint32_t key) { - if (map->len == 0 || !key) return 0; - for (uint32_t i = key;; i++) - { - if (map->keys[i &= map->cap - 1] == key) return map->vals[i]; - if (!map->keys[i]) return 0; - } + uint32_t i; + if (!map || map->len == 0 || !key) + return 0; + for (i = key;; i++) + { + if (map->keys[i &= map->cap - 1] == key) + return map->vals[i]; + if (!map->keys[i]) + break; + } + return 0; } -static void ex_hashmap32_setnum(ex_hashmap32* map, uint32_t key, uintptr_t val) +static void ex_hashmap32_setnum( + ex_hashmap32* map, uint32_t key, uintptr_t val) { - if (!key) return; - if (2 * map->len >= map->cap) ex_hashmap32__grow(map, 2 * map->cap); - for (uint32_t i = key;; i++) - { - if (!map->keys[i &= map->cap - 1]) { map->len++; map->keys[i] = key; map->vals[i] = val; return; } - if (map->keys[i] == key) { map->vals[i] = val; return; } - } + uint32_t i; + if (!key) + return; + if (2 * map->len >= map->cap) + ex_hashmap32__grow(map, 2 * map->cap); + + for (i = key;; i++) + { + if (!map->keys[i &= map->cap - 1]) + { + map->len++; + map->keys[i] = key; + map->vals[i] = val; + return; + } + if (map->keys[i] == key) + { + map->vals[i] = val; + return; + } + } } -static INLINE void* ex_hashmap32_getptr (ex_hashmap32* map, uint32_t key) { return (void*)ex_hashmap32_getnum(map, key); } +static INLINE void * ex_hashmap32_getptr (ex_hashmap32* map, uint32_t key) { return (void*)ex_hashmap32_getnum(map, key); } static INLINE void ex_hashmap32_setptr (ex_hashmap32* map, uint32_t key, void* ptr) { ex_hashmap32_setnum(map, key, (uintptr_t)ptr); } static INLINE void* ex_hashmap32_strgetptr(ex_hashmap32* map, const char* str) { return (void*)ex_hashmap32_getnum(map, ex_hash32(str)); } static INLINE void ex_hashmap32_strsetptr(ex_hashmap32* map, const char* str, void* ptr) { ex_hashmap32_setnum(map, ex_hash32(str), (uintptr_t)ptr); } static INLINE uintptr_t ex_hashmap32_strgetnum(ex_hashmap32* map, const char* str) { return ex_hashmap32_getnum(map, ex_hash32(str)); } static INLINE void ex_hashmap32_strsetnum(ex_hashmap32* map, const char* str, uintptr_t num) { ex_hashmap32_setnum(map, ex_hash32(str), num); } -// Explore -enum -{ - EXPLORE_BY_DEVELOPER, - EXPLORE_BY_PUBLISHER, - EXPLORE_BY_RELEASEYEAR, - EXPLORE_BY_PLAYERCOUNT, - EXPLORE_BY_GENRE, - EXPLORE_BY_ORIGIN, - EXPLORE_BY_REGION, - EXPLORE_BY_FRANCHISE, - EXPLORE_BY_TAGS, - EXPLORE_BY_SYSTEM, - EXPLORE_CAT_COUNT, - - EXPLORE_TYPE_ADDITIONALFILTER = FILE_TYPE_RDB, //database icon - EXPLORE_TYPE_FILTERNULL = MENU_SETTINGS_LAST, - EXPLORE_TYPE_SEARCH, - EXPLORE_TYPE_SHOWALL, - EXPLORE_TYPE_FIRSTCATEGORY, - EXPLORE_TYPE_FIRSTITEM = EXPLORE_TYPE_FIRSTCATEGORY + EXPLORE_CAT_COUNT, -}; - -static const struct { const char *name, *rdbkey; bool use_split, is_company, is_numeric; } - explore_by_info[EXPLORE_CAT_COUNT] = -{ - { "Developer", "developer", true, true, false }, - { "Publisher", "publisher", true, true, false }, - { "Release Year", "releaseyear", false, false, true }, - { "Player Count", "users", false, false, true }, - { "Genre", "genre", true, false, false }, - { "Origin", "origin", false, false, false }, - { "Region", "region", false, false, false }, - { "Franchise", "franchise", false, false, false }, - { "Tags", "tags", true, false, false }, - { "System", "system", false, false, false }, -}; - -typedef struct -{ - uint32_t idx; - char str[1]; -} explore_string_t; - -typedef struct -{ - const struct playlist_entry* playlist_entry; - explore_string_t *by[EXPLORE_CAT_COUNT]; - explore_string_t **split; - char* original_title; -} explore_entry_t; - -typedef struct -{ - ex_arena arena; - explore_string_t **by[EXPLORE_CAT_COUNT]; - bool has_unknown[EXPLORE_CAT_COUNT]; - - explore_entry_t* entries; - playlist_t **playlists; - const char* label_explore_item_str; - char title[1024]; - char find_string[1024]; - unsigned top_depth; -} explore_state_t; - -static explore_state_t* explore_state; - static int explore_qsort_func_strings(const void *a_, const void *b_) { - const explore_string_t **a = (const explore_string_t**)a_; - const explore_string_t **b = (const explore_string_t**)b_; - if ((*a)->str[0] != (*b)->str[0]) - return (unsigned char)(*a)->str[0] - (unsigned char)(*b)->str[0]; - return strcasecmp((*a)->str, (*b)->str); + const explore_string_t **a = (const explore_string_t**)a_; + const explore_string_t **b = (const explore_string_t**)b_; + if ((*a)->str[0] != (*b)->str[0]) + return (unsigned char)(*a)->str[0] - (unsigned char)(*b)->str[0]; + return strcasecmp((*a)->str, (*b)->str); } static int explore_qsort_func_entries(const void *a_, const void *b_) { - const explore_entry_t *a = (const explore_entry_t*)a_; - const explore_entry_t *b = (const explore_entry_t*)b_; - if (a->playlist_entry->label[0] != b->playlist_entry->label[0]) - return (unsigned char)a->playlist_entry->label[0] - (unsigned char)b->playlist_entry->label[0]; - return strcasecmp(a->playlist_entry->label, b->playlist_entry->label); + const explore_entry_t *a = (const explore_entry_t*)a_; + const explore_entry_t *b = (const explore_entry_t*)b_; + if (a->playlist_entry->label[0] != b->playlist_entry->label[0]) + return (unsigned char)a->playlist_entry->label[0] - (unsigned char)b->playlist_entry->label[0]; + return strcasecmp(a->playlist_entry->label, b->playlist_entry->label); } static int explore_qsort_func_menulist(const void *a_, const void *b_) { - const struct item_file *a = (const struct item_file*)a_; - const struct item_file *b = (const struct item_file*)b_; - if (a->path[0] != b->path[0]) - return (unsigned char)a->path[0] - (unsigned char)b->path[0]; - return strcasecmp(a->path, b->path); + const struct item_file *a = (const struct item_file*)a_; + const struct item_file *b = (const struct item_file*)b_; + if (a->path[0] != b->path[0]) + return (unsigned char)a->path[0] - (unsigned char)b->path[0]; + return strcasecmp(a->path, b->path); } static int explore_check_company_suffix(const char* p, bool search_reverse) { - if (search_reverse) - { - p -= (p[-1] == '.' ? 4 : 3); - if (p[-1] != ' ') return 0; - } - if (tolower(p[0]) == 'i' && tolower(p[1]) == 'n' && tolower(p[2]) == 'c') return (p[3] == '.' ? 4 : 3); //, Inc - if (tolower(p[0]) == 'l' && tolower(p[1] )== 't' && tolower(p[2]) == 'd') return (p[3] == '.' ? 4 : 3); //, Ltd - if (tolower(p[0]) == 't' && tolower(p[1] )== 'h' && tolower(p[2]) == 'e') return (p[3] == '.' ? 4 : 3); //, The - return 0; + if (search_reverse) + { + p -= (p[-1] == '.' ? 4 : 3); + if (p[-1] != ' ') + return 0; + } + if (tolower(p[0]) == 'i' && tolower(p[1]) == 'n' && tolower(p[2]) == 'c') + return (p[3] == '.' ? 4 : 3); /*, Inc */ + if (tolower(p[0]) == 'l' && tolower(p[1] )== 't' && tolower(p[2]) == 'd') + return (p[3] == '.' ? 4 : 3); /*, Ltd */ + if (tolower(p[0]) == 't' && tolower(p[1] )== 'h' && tolower(p[2]) == 'e') + return (p[3] == '.' ? 4 : 3); /*, The */ + return 0; } static void explore_add_unique_string(ex_hashmap32 *maps, explore_entry_t *e, unsigned cat, const char *str, explore_string_t ***split_buf) { - if (!str || !*str) - { - explore_state->has_unknown[cat] = true; - return; - } + bool is_company; + const char *p; + const char *p_next; + if (!str || !*str) + { + explore_state->has_unknown[cat] = true; + return; + } - if (!explore_by_info[cat].use_split) split_buf = NULL; - bool is_company = explore_by_info[cat].is_company; + if (!explore_by_info[cat].use_split) + split_buf = NULL; + is_company = explore_by_info[cat].is_company; - for (const char *p = str + 1, *p_next;; p++) - { - if (*p != '/' && *p != ',' && *p != '|' && *p != '\0') - continue; + for (p = str + 1;; p++) + { + size_t len = 0; + uint32_t hash = 0; + explore_string_t* entry = NULL; - if (!split_buf && *p != '\0') continue; + if (*p != '/' && *p != ',' && *p != '|' && *p != '\0') + continue; - p_next = p; - while (*str == ' ') str++; - while (p[-1] == ' ') p--; + if (!split_buf && *p != '\0') + continue; - if (p == str) - { - if (*p == '\0') return; - continue; - } + p_next = p; + while (*str == ' ') + str++; + while (p[-1] == ' ') + p--; - if (is_company && p - str > 5) - { - p -= explore_check_company_suffix(p, true); - while (p[-1] == ' ') p--; - } + if (p == str) + { + if (*p == '\0') + return; + continue; + } - size_t len = p - str; - uint32_t hash = ex_hash32_nocase_filtered((unsigned char*)str, len, '0', 255); - explore_string_t* entry = (explore_string_t*)ex_hashmap32_getptr(&maps[cat], hash); - if (!entry) - { - entry = (explore_string_t*)ex_arena_alloc(&explore_state->arena, sizeof(explore_string_t) + len); - memcpy(entry->str, str, len); - entry->str[len] = '\0'; - ex_buf_push(explore_state->by[cat], entry); - ex_hashmap32_setptr(&maps[cat], hash, entry); - } + if (is_company && p - str > 5) + { + p -= explore_check_company_suffix(p, true); + while (p[-1] == ' ') + p--; + } - if (!e->by[cat]) e->by[cat] = entry; - else ex_buf_push(*split_buf, entry); + len = p - str; + hash = ex_hash32_nocase_filtered( + (unsigned char*)str, len, '0', 255); + entry = + (explore_string_t*)ex_hashmap32_getptr(&maps[cat], hash); - if (*p_next == '\0') return; - if (is_company && *p_next == ',') - { - p = p_next + 1; - while (*p == ' ') p++; - p += explore_check_company_suffix(p, false); - while (*p == ' ') p++; - if (*p == '\0') return; - if (*p == '/' || *p == ',' || *p == '|') p_next = p; - } - p = p_next; - str = p + 1; - } + if (!entry) + { + entry = (explore_string_t*) + ex_arena_alloc(&explore_state->arena, + sizeof(explore_string_t) + len); + memcpy(entry->str, str, len); + entry->str[len] = '\0'; + EX_BUF_PUSH(explore_state->by[cat], entry); + ex_hashmap32_setptr(&maps[cat], hash, entry); + } + + if (!e->by[cat]) + e->by[cat] = entry; + else + EX_BUF_PUSH(*split_buf, entry); + + if (*p_next == '\0') + return; + if (is_company && *p_next == ',') + { + p = p_next + 1; + while (*p == ' ') + p++; + p += explore_check_company_suffix(p, false); + while (*p == ' ') + p++; + if (*p == '\0') + return; + if (*p == '/' || *p == ',' || *p == '|') + p_next = p; + } + p = p_next; + str = p + 1; + } } -static void explore_free() +static void explore_free(void) { - for (int i = 0; i != EXPLORE_CAT_COUNT; i++) - ex_buf_free(explore_state->by[i]); + unsigned i; + for (i = 0; i != EXPLORE_CAT_COUNT; i++) + EX_BUF_FREE(explore_state->by[i]); - ex_buf_free(explore_state->entries); + EX_BUF_FREE(explore_state->entries); - for (size_t i = 0; i != ex_buf_len(explore_state->playlists); i++) - playlist_free(explore_state->playlists[i]); - ex_buf_free(explore_state->playlists); + for (i = 0; i != EX_BUF_LEN(explore_state->playlists); i++) + playlist_free(explore_state->playlists[i]); + EX_BUF_FREE(explore_state->playlists); - ex_arena_free(&explore_state->arena); - free(explore_state); - explore_state = NULL; + ex_arena_free(&explore_state->arena); + free(explore_state); + explore_state = NULL; } -static void explore_build_list() +static void explore_build_list(void) { - char tmp[PATH_MAX_LENGTH]; + unsigned i; + char tmp[PATH_MAX_LENGTH]; + struct explore_rdb { libretrodb_t *handle; ex_hashmap32 playlist_entries; } *rdbs = NULL; + core_info_list_t *core_list = NULL; + ex_hashmap32 map_cores = {0}; + ex_hashmap32 rdb_indices = {0}; + explore_string_t **split_buf = NULL; + ex_hashmap32 cat_maps[EXPLORE_CAT_COUNT] = {{0}}; + settings_t *settings = NULL; + const char *directory_playlist = NULL; + const char *directory_database = NULL; + libretro_vfs_implementation_dir *dir = NULL; - if (explore_state) - explore_free(); + if (explore_state) + explore_free(); - explore_state = calloc(1, sizeof(explore_state_t)); - explore_state->label_explore_item_str = msg_hash_to_str(MENU_ENUM_LABEL_EXPLORE_ITEM); + explore_state = calloc(1, sizeof(explore_state_t)); + explore_state->label_explore_item_str = + msg_hash_to_str(MENU_ENUM_LABEL_EXPLORE_ITEM); - settings_t *settings = config_get_ptr(); - const char *directory_playlist = settings->paths.directory_playlist; - const char *directory_database = settings->paths.path_content_database; + settings = config_get_ptr(); + directory_playlist = settings->paths.directory_playlist; + directory_database = settings->paths.path_content_database; - ex_hashmap32 rdb_indices = {0}; - struct explore_rdb { libretrodb_t *handle; ex_hashmap32 playlist_entries; } *rdbs = NULL; + /* Index all playlists */ + for (dir = retro_vfs_opendir_impl(directory_playlist, false); dir;) + { + size_t j, used_entries = 0; + playlist_config_t playlist_config = {0}; + playlist_t *playlist = NULL; + const char *fext = NULL; + const char *fname = NULL; - // Index all playlists - for (libretro_vfs_implementation_dir *dir = retro_vfs_opendir_impl(directory_playlist, false); dir;) - { - if (!retro_vfs_readdir_impl(dir)) { retro_vfs_closedir_impl(dir); break; } + if (!retro_vfs_readdir_impl(dir)) + { + retro_vfs_closedir_impl(dir); + break; + } - playlist_config_t playlist_config = {0}; - const char* fname = retro_vfs_dirent_get_name_impl(dir), *fext = (fname ? strrchr(fname, '.') : NULL); - if (!fext || strcasecmp(fext, ".lpl")) - continue; + fname = retro_vfs_dirent_get_name_impl(dir); + if (fname) + fext = strrchr(fname, '.'); - fill_pathname_join(playlist_config.path, directory_playlist, fname, sizeof(playlist_config.path)); - playlist_config.capacity = COLLECTION_SIZE; - playlist_t *playlist = playlist_init(&playlist_config); + if (!fext || strcasecmp(fext, ".lpl")) + continue; - size_t used_entries = 0; - for (size_t j = 0; j < playlist_size(playlist); j++) - { - const struct playlist_entry *entry = NULL; - playlist_get_index(playlist, j, &entry); + fill_pathname_join(playlist_config.path, + directory_playlist, fname, sizeof(playlist_config.path)); + playlist_config.capacity = COLLECTION_SIZE; + playlist = playlist_init(&playlist_config); - // Maybe remove this to also include playlist entries with no crc/db/label (build label from file name) - if (!entry->crc32 || !*entry->crc32 || !entry->db_name || !*entry->db_name || !entry->label || !*entry->label) continue; + for (j = 0; j < playlist_size(playlist); j++) + { + uintptr_t rdb_num; + uint32_t entry_crc32; + struct explore_rdb* rdb = NULL; + const struct playlist_entry *entry = NULL; + playlist_get_index(playlist, j, &entry); - uintptr_t rdb_num = ex_hashmap32_strgetnum(&rdb_indices, entry->db_name); - if (!rdb_num) - { - struct explore_rdb rdb = { libretrodb_new() }; - fill_pathname_join_noext(tmp, directory_database, entry->db_name, sizeof(tmp)); - strlcat(tmp, ".rdb", sizeof(tmp)); - if (libretrodb_open(tmp, rdb.handle) != 0) - { - // invalid rdb file - libretrodb_free(rdb.handle); - ex_hashmap32_strsetnum(&rdb_indices, entry->db_name, (uintptr_t)-1); - continue; - } + /* Maybe remove this to also include playlist entries + * with no CRC/db/label (build label from file name) */ + if ( + !entry->crc32 + || !*entry->crc32 + || !entry->db_name + || !*entry->db_name + || !entry->label + || !*entry->label) + continue; - ex_buf_push(rdbs, rdb); - rdb_num = (uintptr_t)ex_buf_len(rdbs); - ex_hashmap32_strsetnum(&rdb_indices, entry->db_name, rdb_num); - } + rdb_num = ex_hashmap32_strgetnum(&rdb_indices, entry->db_name); + if (!rdb_num) + { + struct explore_rdb rdb; - if (rdb_num == (uintptr_t)-1) - continue; + rdb.handle = libretrodb_new(); + rdb.playlist_entries.len = 0; + rdb.playlist_entries.cap = 0; + rdb.playlist_entries.keys = NULL; + rdb.playlist_entries.vals = NULL; - struct explore_rdb* rdb = &rdbs[rdb_num - 1]; - uint32_t entry_crc32 = (uint32_t)strtoul(entry->crc32, NULL, 16); - ex_hashmap32_setptr(&rdb->playlist_entries, entry_crc32, (void*)entry); - used_entries++; - } + fill_pathname_join_noext( + tmp, directory_database, entry->db_name, sizeof(tmp)); + strlcat(tmp, ".rdb", sizeof(tmp)); - if (used_entries) - ex_buf_push(explore_state->playlists, playlist); - else - playlist_free(playlist); - } + if (libretrodb_open(tmp, rdb.handle) != 0) + { + /* invalid rdb file */ + libretrodb_free(rdb.handle); + ex_hashmap32_strsetnum(&rdb_indices, + entry->db_name, (uintptr_t)-1); + continue; + } - ex_hashmap32 map_cores = {0}; - core_info_list_t *core_list = NULL; - if (core_info_get_list(&core_list) && core_list) - for (size_t i = 0; i != core_list->count; i++) - ex_hashmap32_strsetptr(&map_cores, core_list->list[i].display_name, &core_list->list[i]); + EX_BUF_PUSH(rdbs, rdb); + rdb_num = (uintptr_t)EX_BUF_LEN(rdbs); + ex_hashmap32_strsetnum(&rdb_indices, entry->db_name, rdb_num); + } - // Loop through all RDBs referenced in the playlists and load meta data strings - explore_string_t **split_buf = NULL; - ex_hashmap32 cat_maps[EXPLORE_CAT_COUNT] = {{0}}; - for (size_t i = 0; i != ex_buf_len(rdbs); i++) - { - struct explore_rdb* rdb = &rdbs[i]; + if (rdb_num == (uintptr_t)-1) + continue; - struct rmsgpack_dom_value item; - libretrodb_cursor_t *cur = libretrodb_cursor_new(); - bool more = (libretrodb_cursor_open(rdb->handle, cur, NULL) == 0 - && libretrodb_cursor_read_item(cur, &item) == 0); + rdb = &rdbs[rdb_num - 1]; + entry_crc32 = (uint32_t)strtoul(entry->crc32, NULL, 16); + ex_hashmap32_setptr(&rdb->playlist_entries, + entry_crc32, (void*)entry); + used_entries++; + } - for (; more; more = (rmsgpack_dom_value_free(&item), libretrodb_cursor_read_item(cur, &item) == 0)) - { - uint32_t crc32; - char numeric_buf[EXPLORE_CAT_COUNT][16]; - char* fields[EXPLORE_CAT_COUNT] = {NULL}; - char* original_title = NULL; - bool found_crc32 = false; - core_info_t* core_info = NULL; + if (used_entries) + EX_BUF_PUSH(explore_state->playlists, playlist); + else + playlist_free(playlist); + } - if (item.type != RDT_MAP) - continue; + if (core_info_get_list(&core_list) && core_list) + for (i = 0; i != core_list->count; i++) + ex_hashmap32_strsetptr(&map_cores, core_list->list[i].display_name, &core_list->list[i]); - for (unsigned i = 0; i < item.val.map.len; i++) - { - struct rmsgpack_dom_value *key = &item.val.map.items[i].key; - struct rmsgpack_dom_value *val = &item.val.map.items[i].value; - if (!key || !val || key->type != RDT_STRING) - continue; + /* Loop through all RDBs referenced in the playlists + * and load meta data strings */ + for (i = 0; i != EX_BUF_LEN(rdbs); i++) + { + struct rmsgpack_dom_value item; + struct explore_rdb* rdb = &rdbs[i]; + libretrodb_cursor_t *cur = libretrodb_cursor_new(); + bool more = + ( + libretrodb_cursor_open(rdb->handle, cur, NULL) == 0 + && libretrodb_cursor_read_item(cur, &item) == 0); - const char* key_str = key->val.string.buff; - if (string_is_equal(key_str, "crc")) - { - crc32 = swap_if_little32(*(uint32_t*)val->val.binary.buff); - found_crc32 = true; - continue; - } - if (string_is_equal(key_str, "original_title")) - { - original_title = val->val.string.buff; - continue; - } - for (unsigned cat = 0; cat != EXPLORE_CAT_COUNT; cat++) - { - if (!string_is_equal(key_str, explore_by_info[cat].rdbkey)) continue; - if (explore_by_info[cat].is_numeric) - { - if (!val->val.int_) break; - snprintf(numeric_buf[cat], sizeof(numeric_buf[cat]), "%d", (int)val->val.int_); - fields[cat] = numeric_buf[cat]; - break; - } - if (val->type != RDT_STRING) break; - fields[cat] = val->val.string.buff; - break; - } - } + for (; more; more = (rmsgpack_dom_value_free(&item), + libretrodb_cursor_read_item(cur, &item) == 0)) + { + unsigned k, l, cat; + uint32_t crc32; + explore_entry_t e; + char numeric_buf[EXPLORE_CAT_COUNT][16]; + char* fields[EXPLORE_CAT_COUNT] = {NULL}; + const struct playlist_entry *entry = NULL; + char* original_title = NULL; + bool found_crc32 = false; + core_info_t* core_info = NULL; - if (!found_crc32) - continue; + if (item.type != RDT_MAP) + continue; - const struct playlist_entry *entry = (const struct playlist_entry *)ex_hashmap32_getptr(&rdb->playlist_entries, crc32); - if (!entry) - continue; + for (k = 0; k < item.val.map.len; k++) + { + const char *key_str = NULL; + struct rmsgpack_dom_value *key = &item.val.map.items[k].key; + struct rmsgpack_dom_value *val = &item.val.map.items[k].value; + if (!key || !val || key->type != RDT_STRING) + continue; - explore_entry_t e = {entry}; - for (unsigned cat = 0; cat != EXPLORE_CAT_COUNT; cat++) - explore_add_unique_string(cat_maps, &e, cat, fields[cat], &split_buf); + key_str = key->val.string.buff; + if (string_is_equal(key_str, "crc")) + { + crc32 = swap_if_little32(*(uint32_t*)val->val.binary.buff); + found_crc32 = true; + continue; + } + else if (string_is_equal(key_str, "original_title")) + { + original_title = val->val.string.buff; + continue; + } + + for (cat = 0; cat != EXPLORE_CAT_COUNT; cat++) + { + if (!string_is_equal(key_str, explore_by_info[cat].rdbkey)) + continue; + + if (explore_by_info[cat].is_numeric) + { + if (!val->val.int_) + break; + snprintf(numeric_buf[cat], + sizeof(numeric_buf[cat]), + "%d", (int)val->val.int_); + fields[cat] = numeric_buf[cat]; + break; + } + if (val->type != RDT_STRING) + break; + fields[cat] = val->val.string.buff; + break; + } + } + + if (!found_crc32) + continue; + + entry = (const struct playlist_entry *)ex_hashmap32_getptr( + &rdb->playlist_entries, crc32); + if (!entry) + continue; + + e.playlist_entry = entry; + for (l = 0; l < EXPLORE_CAT_COUNT; l++) + e.by[l] = NULL; + e.split = NULL; + e.original_title = NULL; + + for (cat = 0; cat != EXPLORE_CAT_COUNT; cat++) + explore_add_unique_string(cat_maps, &e, cat, + fields[cat], &split_buf); if (!string_is_empty(entry->core_name)) - core_info = (core_info_t*)ex_hashmap32_strgetptr(&map_cores, entry->core_name); - explore_add_unique_string(cat_maps, &e, EXPLORE_BY_SYSTEM, (core_info ? core_info->systemname : NULL), NULL); + core_info = (core_info_t*) + ex_hashmap32_strgetptr(&map_cores, entry->core_name); + explore_add_unique_string(cat_maps, &e, + EXPLORE_BY_SYSTEM, + (core_info ? core_info->systemname : NULL), NULL); - if (original_title && *original_title) - { - size_t len = strlen(original_title) + 1; - e.original_title = (char*)ex_arena_alloc(&explore_state->arena, len); - memcpy(e.original_title, original_title, len); - } + if (original_title && *original_title) + { + size_t len = strlen(original_title) + 1; + e.original_title = (char*) + ex_arena_alloc(&explore_state->arena, len); + memcpy(e.original_title, original_title, len); + } - if (ex_buf_len(split_buf)) - { - ex_buf_push(split_buf, NULL); //terminator - size_t len = ex_buf_sizeof(split_buf); - e.split = (explore_string_t **)ex_arena_alloc(&explore_state->arena, len); - memcpy(e.split, split_buf, len); - ex_buf_clear(split_buf); - } + if (EX_BUF_LEN(split_buf)) + { + size_t len; - ex_buf_push(explore_state->entries, e); - } - libretrodb_cursor_close(cur); - libretrodb_cursor_free(cur); - libretrodb_close(rdb->handle); - libretrodb_free(rdb->handle); - ex_hashmap32_free(&rdb->playlist_entries); - } - ex_buf_free(split_buf); - ex_hashmap32_free(&map_cores); - ex_hashmap32_free(&rdb_indices); - ex_buf_free(rdbs); + EX_BUF_PUSH(split_buf, NULL); /* terminator */ + len = EX_BUF_SIZEOF(split_buf); + e.split = (explore_string_t **) + ex_arena_alloc(&explore_state->arena, len); + memcpy(e.split, split_buf, len); + EX_BUF_CLEAR(split_buf); + } - for (int i = 0; i != EXPLORE_CAT_COUNT; i++) - { - size_t len = ex_buf_len(explore_state->by[i]); - qsort(explore_state->by[i], len, sizeof(*explore_state->by[i]), explore_qsort_func_strings); - for (uint32_t idx = 0; idx != len; idx++) - explore_state->by[i][idx]->idx = idx; + EX_BUF_PUSH(explore_state->entries, e); + } - ex_hashmap32_free(&cat_maps[i]); - } - qsort(explore_state->entries, ex_buf_len(explore_state->entries), sizeof(*explore_state->entries), explore_qsort_func_entries); + libretrodb_cursor_close(cur); + libretrodb_cursor_free(cur); + libretrodb_close(rdb->handle); + libretrodb_free(rdb->handle); + ex_hashmap32_free(&rdb->playlist_entries); + } + EX_BUF_FREE(split_buf); + ex_hashmap32_free(&map_cores); + ex_hashmap32_free(&rdb_indices); + EX_BUF_FREE(rdbs); + + for (i = 0; i != EXPLORE_CAT_COUNT; i++) + { + uint32_t idx; + size_t len = EX_BUF_LEN(explore_state->by[i]); + qsort(explore_state->by[i], len, sizeof(*explore_state->by[i]), explore_qsort_func_strings); + for (idx = 0; idx != len; idx++) + explore_state->by[i][idx]->idx = idx; + + ex_hashmap32_free(&cat_maps[i]); + } + qsort(explore_state->entries, EX_BUF_LEN(explore_state->entries), sizeof(*explore_state->entries), explore_qsort_func_entries); } static int explore_action_get_title(const char *path, const char *label, unsigned menu_type, char *s, size_t len) { - snprintf(s, len, "%s", explore_state->title); - return 0; + snprintf(s, len, "%s", explore_state->title); + return 0; } static void explore_append_title(const char* fmt, ...) { - va_list ap; - size_t len = strlen(explore_state->title); - va_start(ap, fmt); - vsnprintf(explore_state->title + len, sizeof(explore_state->title) - len, fmt, ap); - va_end(ap); + va_list ap; + size_t len = strlen(explore_state->title); + va_start(ap, fmt); + vsnprintf(explore_state->title + len, + sizeof(explore_state->title) - len, fmt, ap); + va_end(ap); } static int explore_action_sublabel_spacer(file_list_t *list, unsigned type, unsigned i, const char *label, const char *path, char *s, size_t len) { - strlcpy(s, " ", len); - return 1; // 1 means it'll never change and can be cached + strlcpy(s, " ", len); + return 1; /* 1 means it'll never change and can be cached */ } -static int explore_action_ok(const char *path, const char *label, unsigned type, size_t idx, size_t entry_idx) +static int explore_action_ok(const char *path, const char *label, + unsigned type, size_t idx, size_t entry_idx) { - const char* explore_tab = msg_hash_to_str(MENU_ENUM_LABEL_EXPLORE_TAB); - filebrowser_clear_type(); - return generic_action_ok_displaylist_push(explore_tab, NULL, explore_tab, type, idx, entry_idx, ACTION_OK_DL_PUSH_DEFAULT); + const char* explore_tab = msg_hash_to_str(MENU_ENUM_LABEL_EXPLORE_TAB); + filebrowser_clear_type(); + return generic_action_ok_displaylist_push(explore_tab, NULL, explore_tab, type, idx, entry_idx, ACTION_OK_DL_PUSH_DEFAULT); } static menu_file_list_cbs_t* explore_menu_entry(file_list_t *list, const char *path, unsigned type) { - menu_entries_append_enum(list, path, explore_state->label_explore_item_str, MENU_ENUM_LABEL_EXPLORE_ITEM, type, 0, 0); - menu_file_list_cbs_t* cbs = ((menu_file_list_cbs_t*)list->list[list->size-1].actiondata); - cbs->action_ok = explore_action_ok; - return cbs; + menu_file_list_cbs_t* cbs = NULL; + menu_entries_append_enum(list, path, explore_state->label_explore_item_str, MENU_ENUM_LABEL_EXPLORE_ITEM, type, 0, 0); + cbs = ((menu_file_list_cbs_t*)list->list[list->size-1].actiondata); + if (!cbs) + return NULL; + cbs->action_ok = explore_action_ok; + return cbs; } static void explore_menu_add_spacer(file_list_t *list) { - if (!list->size) return; - ((menu_file_list_cbs_t*)list->list[list->size-1].actiondata)->action_sublabel = explore_action_sublabel_spacer; + if (!list->size) + return; + ((menu_file_list_cbs_t*)list->list[list->size-1].actiondata)->action_sublabel = explore_action_sublabel_spacer; } static void explore_action_find_complete(void *userdata, const char *line) { - menu_input_dialog_end(); - if (line && *line) - { - strlcpy(explore_state->find_string, line, sizeof(explore_state->find_string)); - explore_action_ok(NULL, NULL, EXPLORE_TYPE_SEARCH, 0, 0); - } + menu_input_dialog_end(); + if (line && *line) + { + strlcpy(explore_state->find_string, line, sizeof(explore_state->find_string)); + explore_action_ok(NULL, NULL, EXPLORE_TYPE_SEARCH, 0, 0); + } } static int explore_action_ok_find(const char *path, const char *label, unsigned type, size_t idx, size_t entry_idx) { - menu_input_ctx_line_t line = {0}; - line.label = "Search Text"; - line.cb = explore_action_find_complete; - menu_input_dialog_start(&line); - return 0; + menu_input_ctx_line_t line = {0}; + line.label = "Search Text"; + line.cb = explore_action_find_complete; + menu_input_dialog_start(&line); + return 0; } unsigned menu_displaylist_explore(file_list_t *list) { - char tmp[512]; - file_list_t *menu_stack = menu_entries_get_menu_stack_ptr(0); + unsigned i, cat; + char tmp[512]; + unsigned depth, current_type, current_cat, previous_cat; + unsigned levels = 0; + struct item_file *stack_top = NULL; + file_list_t *menu_stack = menu_entries_get_menu_stack_ptr(0); - if (!explore_state) - { - explore_build_list(); - explore_state->top_depth = (unsigned)menu_stack->size - 1; - } + if (!explore_state) + { + explore_build_list(); + explore_state->top_depth = (unsigned)menu_stack->size - 1; + } - if (menu_stack->size > 1) - { - struct item_file *stack = &menu_stack->list[menu_stack->size - 1]; - menu_file_list_cbs_t* cbs = ((menu_file_list_cbs_t*)stack->actiondata); - cbs->action_get_title = explore_action_get_title; - } + if (menu_stack->size > 1) + { + struct item_file *stack = &menu_stack->list[menu_stack->size - 1]; + menu_file_list_cbs_t* cbs = ((menu_file_list_cbs_t*)stack->actiondata); + cbs->action_get_title = explore_action_get_title; + } - struct item_file *stack_top = menu_stack->list + explore_state->top_depth; - unsigned depth = (unsigned)menu_stack->size - 1 - explore_state->top_depth; - unsigned current_type = stack_top[depth].type; - unsigned current_cat = current_type - EXPLORE_TYPE_FIRSTCATEGORY; - unsigned previous_cat = stack_top[depth ? depth - 1 : 0].type - EXPLORE_TYPE_FIRSTCATEGORY; + stack_top = menu_stack->list + explore_state->top_depth; + depth = (unsigned)menu_stack->size - 1 - explore_state->top_depth; + current_type = stack_top[depth].type; + current_cat = current_type - EXPLORE_TYPE_FIRSTCATEGORY; + previous_cat = stack_top[depth ? depth - 1 : 0].type - EXPLORE_TYPE_FIRSTCATEGORY; - if (depth) - { - ((menu_file_list_cbs_t*)stack_top[depth].actiondata)->action_get_title = explore_action_get_title; + if (depth) + { + bool clear_find_text = false; + ((menu_file_list_cbs_t*)stack_top[depth].actiondata)->action_get_title = explore_action_get_title; - bool clear_find_text = (current_type != EXPLORE_TYPE_SEARCH); - explore_state->title[0] = '\0'; - for (unsigned i = 1, levels = 0; i < depth; i++) - { - unsigned by_category = (stack_top[i].type - EXPLORE_TYPE_FIRSTCATEGORY); - if (stack_top[i].type == EXPLORE_TYPE_SEARCH) clear_find_text = false; - if (by_category >= EXPLORE_CAT_COUNT) continue; + clear_find_text = (current_type != EXPLORE_TYPE_SEARCH); + explore_state->title[0] = '\0'; - unsigned by_selected_type = stack_top[i + 1].type; - const char* name = explore_by_info[by_category].name; - explore_string_t **entries = explore_state->by[by_category]; - explore_append_title("%s%s: %s", (levels++ ? " / " : ""), name, (by_selected_type != EXPLORE_TYPE_FILTERNULL ? entries[by_selected_type - EXPLORE_TYPE_FIRSTITEM]->str : "Unknown")); - } + for (i = 1; i < depth; i++) + { + unsigned by_selected_type; + explore_string_t **entries; + const char* name = NULL; + unsigned by_category = (stack_top[i].type - + EXPLORE_TYPE_FIRSTCATEGORY); - if (clear_find_text) - explore_state->find_string[0] = '\0'; + if (stack_top[i].type == EXPLORE_TYPE_SEARCH) + clear_find_text = false; + if (by_category >= EXPLORE_CAT_COUNT) + continue; - if (*explore_state->find_string) - explore_append_title(" '%s'", explore_state->find_string); - } + by_selected_type = stack_top[i + 1].type; + name = explore_by_info[by_category].name; + entries = explore_state->by[by_category]; + explore_append_title("%s%s: %s", (levels++ ? " / " : ""), name, (by_selected_type != EXPLORE_TYPE_FILTERNULL ? entries[by_selected_type - EXPLORE_TYPE_FIRSTITEM]->str : "Unknown")); + } - void playlist_set_cached(playlist_t* pl); - playlist_set_cached(NULL); + if (clear_find_text) + explore_state->find_string[0] = '\0'; - if (current_type == MENU_EXPLORE_TAB || current_type == EXPLORE_TYPE_ADDITIONALFILTER) - { - // Explore top or selecting an additional filter - bool is_top = (current_type == MENU_EXPLORE_TAB); - if (is_top) strlcpy(explore_state->title, "Explore", sizeof(explore_state->title)); - else explore_append_title(" - Additional Filter"); + if (*explore_state->find_string) + explore_append_title(" '%s'", explore_state->find_string); + } - if (is_top || !*explore_state->find_string) - { - explore_menu_entry(list, "Search Name ...", EXPLORE_TYPE_SEARCH)->action_ok = explore_action_ok_find; - explore_menu_add_spacer(list); - } + playlist_set_cached(NULL); - for (unsigned cat = 0; cat < EXPLORE_CAT_COUNT; cat++) - { - const char* name = explore_by_info[cat].name; - explore_string_t **entries = explore_state->by[cat]; - if (!ex_buf_len(entries)) continue; + if ( current_type == MENU_EXPLORE_TAB + || current_type == EXPLORE_TYPE_ADDITIONALFILTER) + { + /* Explore top or selecting an additional filter */ + bool is_top = (current_type == MENU_EXPLORE_TAB); + if (is_top) + strlcpy(explore_state->title, "Explore", sizeof(explore_state->title)); + else + explore_append_title(" - Additional Filter"); - for (unsigned i = 1; i < depth; i++) - if (stack_top[i].type == cat + EXPLORE_TYPE_FIRSTCATEGORY) - goto SKIP_EXPLORE_BY_CATEGORY; + if (is_top || !*explore_state->find_string) + { + explore_menu_entry(list, "Search Name ...", + EXPLORE_TYPE_SEARCH)->action_ok = explore_action_ok_find; + explore_menu_add_spacer(list); + } - if (!is_top) - snprintf(tmp, sizeof(tmp), "By %s", name); - else if (explore_by_info[cat].is_numeric) - snprintf(tmp, sizeof(tmp), "By %s (%s - %s)", name, entries[0]->str, entries[ex_buf_len(entries) - 1]->str); - else - snprintf(tmp, sizeof(tmp), "By %s (%u entries)", name, (unsigned)ex_buf_len(entries)); - explore_menu_entry(list, tmp, cat + EXPLORE_TYPE_FIRSTCATEGORY); + for (cat = 0; cat < EXPLORE_CAT_COUNT; cat++) + { + const char *name = explore_by_info[cat].name; + explore_string_t **entries = explore_state->by[cat]; + if (!EX_BUF_LEN(entries)) + continue; - SKIP_EXPLORE_BY_CATEGORY:; - } + for (i = 1; i < depth; i++) + if (stack_top[i].type == cat + EXPLORE_TYPE_FIRSTCATEGORY) + goto SKIP_EXPLORE_BY_CATEGORY; - if (is_top) - { - explore_menu_add_spacer(list); - explore_menu_entry(list, "Show All", EXPLORE_TYPE_SHOWALL); - } - } - else if (depth == 1 && current_type != EXPLORE_TYPE_SEARCH && current_type != EXPLORE_TYPE_SHOWALL) - { - // List all items in a selected explore by category - explore_string_t **entries = explore_state->by[current_cat]; - for (int i = 0, i_last = ex_buf_len(entries) - 1; i <= i_last; i++) - explore_menu_entry(list, entries[i]->str, EXPLORE_TYPE_FIRSTITEM + i); - if (explore_state->has_unknown[current_cat]) - { - explore_menu_add_spacer(list); - explore_menu_entry(list, "Unknown", EXPLORE_TYPE_FILTERNULL); - } - explore_append_title("Select %s", explore_by_info[current_cat].name); - } - else if (previous_cat < EXPLORE_CAT_COUNT || current_type < EXPLORE_TYPE_FIRSTITEM) - { - unsigned levels = 0; - unsigned cats[10]; - explore_string_t* filter[10]; - bool use_split[10]; - bool use_find = (*explore_state->find_string != '\0'); + if (!is_top) + snprintf(tmp, sizeof(tmp), "By %s", name); + else if (explore_by_info[cat].is_numeric) + snprintf(tmp, sizeof(tmp), "By %s (%s - %s)", name, entries[0]->str, entries[EX_BUF_LEN(entries) - 1]->str); + else + snprintf(tmp, sizeof(tmp), "By %s (%u entries)", name, + (unsigned)EX_BUF_LEN(entries)); + explore_menu_entry(list, tmp, cat + EXPLORE_TYPE_FIRSTCATEGORY); - bool is_show_all = (depth == 1 && !use_find); - bool is_filtered_category = (current_cat < EXPLORE_CAT_COUNT); - bool filtered_category_have_unknown = false; +SKIP_EXPLORE_BY_CATEGORY:; + } - if (is_filtered_category) - { - // List filtered items in a selected explore by category - explore_append_title(" - Select %s", explore_by_info[current_cat].name); - } - else - { - // Game list - if (is_show_all) - { - explore_append_title("All"); - explore_menu_entry(list, "Search Name ...", EXPLORE_TYPE_SEARCH)->action_ok = explore_action_ok_find; - } - else - explore_menu_entry(list, "Add Additional Filter", EXPLORE_TYPE_ADDITIONALFILTER); - explore_menu_add_spacer(list); - } + if (is_top) + { + explore_menu_add_spacer(list); + explore_menu_entry(list, "Show All", EXPLORE_TYPE_SHOWALL); + } + } + else if ( + depth == 1 + && current_type != EXPLORE_TYPE_SEARCH + && current_type != EXPLORE_TYPE_SHOWALL) + { + /* List all items in a selected explore by category */ + explore_string_t **entries = explore_state->by[current_cat]; + unsigned i_last = EX_BUF_LEN(entries) - 1; + for (i = 0; i <= i_last; i++) + explore_menu_entry(list, entries[i]->str, EXPLORE_TYPE_FIRSTITEM + i); + if (explore_state->has_unknown[current_cat]) + { + explore_menu_add_spacer(list); + explore_menu_entry(list, "Unknown", EXPLORE_TYPE_FILTERNULL); + } + explore_append_title("Select %s", explore_by_info[current_cat].name); + } + else if ( + previous_cat < EXPLORE_CAT_COUNT + || current_type < EXPLORE_TYPE_FIRSTITEM) + { + bool use_split[10]; + unsigned cats[10]; + explore_string_t* filter[10]; + explore_entry_t *e = NULL; + explore_entry_t *e_end = NULL; + ex_hashmap32 map_filtered_category = {0}; + unsigned levels = 0; + bool use_find = ( + *explore_state->find_string != '\0'); - for (unsigned i = 1; i < depth; i++) - { - unsigned by_category = (stack_top[i].type - EXPLORE_TYPE_FIRSTCATEGORY); - if (by_category >= EXPLORE_CAT_COUNT) continue; + bool is_show_all = (depth == 1 && !use_find); + bool is_filtered_category = (current_cat < EXPLORE_CAT_COUNT); + bool filtered_category_have_unknown = false; - unsigned by_selected_type = stack_top[i + 1].type; - explore_string_t **entries = explore_state->by[by_category]; - cats [levels] = by_category; - use_split[levels] = explore_by_info[by_category].use_split; - filter [levels] = (by_selected_type == EXPLORE_TYPE_FILTERNULL ? NULL : entries[by_selected_type - EXPLORE_TYPE_FIRSTITEM]); - levels++; - } + /* List filtered items in a selected explore by category */ + if (is_filtered_category) + explore_append_title(" - Select %s", + explore_by_info[current_cat].name); + else + { + /* Game list */ + if (is_show_all) + { + explore_append_title("All"); + explore_menu_entry(list, "Search Name ...", + EXPLORE_TYPE_SEARCH)->action_ok = explore_action_ok_find; + } + else + explore_menu_entry(list, "Add Additional Filter", + EXPLORE_TYPE_ADDITIONALFILTER); + explore_menu_add_spacer(list); + } - ex_hashmap32 map_filtered_category = {0}; - for (explore_entry_t *e = explore_state->entries, *e_end = ex_buf_end(explore_state->entries); e != e_end; e++) - { - for (unsigned lvl = 0; lvl != levels; lvl++) - { - if (filter[lvl] == e->by[cats[lvl]]) continue; - if (use_split[lvl] && e->split) - { - explore_string_t** split = e->split; - do { if (*split == filter[lvl]) break; } while (*(++split)); - if (*split) continue; - } - goto SKIP_ENTRY; - } + for (i = 1; i < depth; i++) + { + explore_string_t **entries; + unsigned by_selected_type = 0; + unsigned by_category = (stack_top[i].type + - EXPLORE_TYPE_FIRSTCATEGORY); - if (use_find && !strcasestr(e->playlist_entry->label, explore_state->find_string)) - goto SKIP_ENTRY; + if (by_category >= EXPLORE_CAT_COUNT) + continue; - if (is_filtered_category) - { - explore_string_t* str = e->by[current_cat]; - if (!str) { filtered_category_have_unknown = true; continue; } - if (ex_hashmap32_getnum(&map_filtered_category, str->idx + 1)) continue; - ex_hashmap32_setnum(&map_filtered_category, str->idx + 1, 1); - explore_menu_entry(list, str->str, EXPLORE_TYPE_FIRSTITEM + str->idx); - } - else - { - explore_menu_entry(list, (e->original_title ? e->original_title : e->playlist_entry->label), EXPLORE_TYPE_FIRSTITEM + (e - explore_state->entries)); - } + by_selected_type = stack_top[i + 1].type; + entries = explore_state->by[by_category]; + cats [levels] = by_category; + use_split[levels] = explore_by_info[by_category].use_split; + filter [levels] = + (by_selected_type == EXPLORE_TYPE_FILTERNULL + ? NULL + : entries[by_selected_type - EXPLORE_TYPE_FIRSTITEM]); + levels++; + } - SKIP_ENTRY:; - } + e = explore_state->entries; + e_end = EX_BUF_END(explore_state->entries); + for (; e != e_end; e++) + { + unsigned lvl; + for (lvl = 0; lvl != levels; lvl++) + { + if (filter[lvl] == e->by[cats[lvl]]) + continue; + if (use_split[lvl] && e->split) + { + explore_string_t** split = e->split; + do { + if (*split == filter[lvl]) + break; + }while(*(++split)); + if (*split) + continue; + } + goto SKIP_ENTRY; + } - if (is_filtered_category) - qsort(list->list, list->size, sizeof(*list->list), explore_qsort_func_menulist); + if (use_find && !strcasestr(e->playlist_entry->label, explore_state->find_string)) + goto SKIP_ENTRY; - if (is_filtered_category && filtered_category_have_unknown) - { - explore_menu_add_spacer(list); - explore_menu_entry(list, "Unknown", EXPLORE_TYPE_FILTERNULL); - } + if (is_filtered_category) + { + explore_string_t* str = e->by[current_cat]; + if (!str) + { + filtered_category_have_unknown = true; + continue; + } + if (ex_hashmap32_getnum(&map_filtered_category, str->idx + 1)) + continue; + ex_hashmap32_setnum(&map_filtered_category, str->idx + 1, 1); + explore_menu_entry(list, str->str, EXPLORE_TYPE_FIRSTITEM + str->idx); + } + else + { + explore_menu_entry(list, (e->original_title ? e->original_title : e->playlist_entry->label), EXPLORE_TYPE_FIRSTITEM + (e - explore_state->entries)); + } - explore_append_title(" (%u)", (unsigned) (list->size - 1)); - } - else - { - // Content page of selected game - const struct playlist_entry *pl_entry = explore_state->entries[current_type - EXPLORE_TYPE_FIRSTITEM].playlist_entry; +SKIP_ENTRY:; + } - snprintf(explore_state->title, sizeof(explore_state->title), "%s", pl_entry->label); + if (is_filtered_category) + qsort(list->list, list->size, sizeof(*list->list), explore_qsort_func_menulist); - for (int pl_idx = 0; pl_idx != ex_buf_len(explore_state->playlists); pl_idx++) - { - playlist_t* pl = explore_state->playlists[pl_idx]; - const struct playlist_entry* pl_first; - playlist_get_index(pl, 0, &pl_first); - if (pl_entry < pl_first || pl_entry >= pl_first + playlist_size(pl)) - continue; + if (is_filtered_category && filtered_category_have_unknown) + { + explore_menu_add_spacer(list); + explore_menu_entry(list, "Unknown", EXPLORE_TYPE_FILTERNULL); + } - // Fake all the state so the content screen and information screen think we're viewing via playlist - playlist_set_cached(pl); - menu_handle_t *menu = menu_driver_get_ptr(); - menu->rpl_entry_selection_ptr = (pl_entry - pl_first); - strlcpy(menu->deferred_path, pl_entry->path, sizeof(menu->deferred_path)); + explore_append_title(" (%u)", (unsigned) (list->size - 1)); + } + else + { + /* Content page of selected game */ + int pl_idx; + const struct playlist_entry *pl_entry = explore_state->entries[current_type - EXPLORE_TYPE_FIRSTITEM].playlist_entry; - menu_displaylist_info_t info = {0}; - info.list = list; - menu_displaylist_ctl(DISPLAYLIST_HORIZONTAL_CONTENT_ACTIONS, &info); - break; - } - } + snprintf(explore_state->title, + sizeof(explore_state->title), "%s", pl_entry->label); - return list->size; + for (pl_idx = 0; pl_idx != EX_BUF_LEN(explore_state->playlists); pl_idx++) + { + const struct playlist_entry* pl_first = NULL; + playlist_t *pl = + explore_state->playlists[pl_idx]; + menu_handle_t *menu = menu_driver_get_ptr(); + menu_displaylist_info_t info = {0}; + + playlist_get_index(pl, 0, &pl_first); + + if ( pl_entry < pl_first || + pl_entry >= pl_first + playlist_size(pl)) + continue; + + /* Fake all the state so the content screen + * and information screen think we're viewing via playlist */ + playlist_set_cached(pl); + menu->rpl_entry_selection_ptr = (pl_entry - pl_first); + strlcpy(menu->deferred_path, + pl_entry->path, sizeof(menu->deferred_path)); + info.list = list; + menu_displaylist_ctl(DISPLAYLIST_HORIZONTAL_CONTENT_ACTIONS, &info); + break; + } + } + + return list->size; } diff --git a/playlist.c b/playlist.c index c0a66e6552..294fb687b7 100644 --- a/playlist.c +++ b/playlist.c @@ -92,6 +92,13 @@ typedef int (playlist_sort_fun_t)( const struct playlist_entry *a, const struct playlist_entry *b); +/* TODO/FIXME - hack for allowing the explore view to switch + * over to a playlist item */ +void playlist_set_cached(playlist_t* pl) +{ + playlist_cached = pl; +} + /* Convenience function: copies specified playlist * path to specified playlist configuration object */ void playlist_config_set_path(playlist_config_t *config, const char *path) @@ -2994,8 +3001,3 @@ core_info_t *playlist_get_default_core_info(playlist_t* playlist) return NULL; } -// hack for allowing the explore view to switch over to a playlist item -void playlist_set_cached(playlist_t* pl) -{ - playlist_cached = pl; -} diff --git a/playlist.h b/playlist.h index 1a7a73d3a2..72388ad249 100644 --- a/playlist.h +++ b/playlist.h @@ -345,6 +345,8 @@ core_info_t *playlist_entry_get_core_info(const struct playlist_entry* entry); * default core association */ core_info_t *playlist_get_default_core_info(playlist_t* playlist); +void playlist_set_cached(playlist_t* pl); + RETRO_END_DECLS #endif From 079d1fc2fc571995d37555f728f83651fcc0c329 Mon Sep 17 00:00:00 2001 From: twinaphex Date: Tue, 28 Jul 2020 11:42:51 +0200 Subject: [PATCH 07/17] (menu explore) Further cleanups --- menu/menu_explore.c | 265 +++++++++++++++++++++++++++++--------------- 1 file changed, 176 insertions(+), 89 deletions(-) diff --git a/menu/menu_explore.c b/menu/menu_explore.c index 0688d2a719..033d2908a5 100644 --- a/menu/menu_explore.c +++ b/menu/menu_explore.c @@ -46,7 +46,7 @@ /* Explore */ enum { - EXPLORE_BY_DEVELOPER = 0, + EXPLORE_BY_DEVELOPER = 0, EXPLORE_BY_PUBLISHER, EXPLORE_BY_RELEASEYEAR, EXPLORE_BY_PLAYERCOUNT, @@ -59,11 +59,11 @@ enum EXPLORE_CAT_COUNT, EXPLORE_TYPE_ADDITIONALFILTER = FILE_TYPE_RDB, /* database icon */ - EXPLORE_TYPE_FILTERNULL = MENU_SETTINGS_LAST, + EXPLORE_TYPE_FILTERNULL = MENU_SETTINGS_LAST, EXPLORE_TYPE_SEARCH, EXPLORE_TYPE_SHOWALL, EXPLORE_TYPE_FIRSTCATEGORY, - EXPLORE_TYPE_FIRSTITEM = EXPLORE_TYPE_FIRSTCATEGORY + EXPLORE_CAT_COUNT + EXPLORE_TYPE_FIRSTITEM = EXPLORE_TYPE_FIRSTCATEGORY + EXPLORE_CAT_COUNT }; struct ex_buf_hdr @@ -299,12 +299,37 @@ static void ex_hashmap32_setnum( } } -static INLINE void * ex_hashmap32_getptr (ex_hashmap32* map, uint32_t key) { return (void*)ex_hashmap32_getnum(map, key); } -static INLINE void ex_hashmap32_setptr (ex_hashmap32* map, uint32_t key, void* ptr) { ex_hashmap32_setnum(map, key, (uintptr_t)ptr); } -static INLINE void* ex_hashmap32_strgetptr(ex_hashmap32* map, const char* str) { return (void*)ex_hashmap32_getnum(map, ex_hash32(str)); } -static INLINE void ex_hashmap32_strsetptr(ex_hashmap32* map, const char* str, void* ptr) { ex_hashmap32_setnum(map, ex_hash32(str), (uintptr_t)ptr); } -static INLINE uintptr_t ex_hashmap32_strgetnum(ex_hashmap32* map, const char* str) { return ex_hashmap32_getnum(map, ex_hash32(str)); } -static INLINE void ex_hashmap32_strsetnum(ex_hashmap32* map, const char* str, uintptr_t num) { ex_hashmap32_setnum(map, ex_hash32(str), num); } +static INLINE void *ex_hashmap32_getptr(ex_hashmap32* map, uint32_t key) +{ + return (void*)ex_hashmap32_getnum(map, key); +} + +static INLINE void ex_hashmap32_setptr(ex_hashmap32* map, + uint32_t key, void* ptr) +{ + ex_hashmap32_setnum(map, key, (uintptr_t)ptr); +} + +static INLINE void *ex_hashmap32_strgetptr(ex_hashmap32* map, const char* str) +{ + return (void*)ex_hashmap32_getnum(map, ex_hash32(str)); +} + +static INLINE void ex_hashmap32_strsetptr(ex_hashmap32* map, + const char* str, void* ptr) +{ + ex_hashmap32_setnum(map, ex_hash32(str), (uintptr_t)ptr); +} +static INLINE uintptr_t ex_hashmap32_strgetnum( + ex_hashmap32* map, const char* str) +{ + return ex_hashmap32_getnum(map, ex_hash32(str)); +} +static INLINE void ex_hashmap32_strsetnum(ex_hashmap32* map, + const char* str, uintptr_t num) +{ + ex_hashmap32_setnum(map, ex_hash32(str), num); +} static int explore_qsort_func_strings(const void *a_, const void *b_) { @@ -350,7 +375,10 @@ static int explore_check_company_suffix(const char* p, bool search_reverse) return 0; } -static void explore_add_unique_string(ex_hashmap32 *maps, explore_entry_t *e, unsigned cat, const char *str, explore_string_t ***split_buf) +static void explore_add_unique_string( + ex_hashmap32 *maps, explore_entry_t *e, + unsigned cat, const char *str, + explore_string_t ***split_buf) { bool is_company; const char *p; @@ -439,53 +467,55 @@ static void explore_add_unique_string(ex_hashmap32 *maps, explore_entry_t *e, un } } -static void explore_free(void) +static void explore_free(explore_state_t *state) { unsigned i; + if (!state) + return; for (i = 0; i != EXPLORE_CAT_COUNT; i++) - EX_BUF_FREE(explore_state->by[i]); + EX_BUF_FREE(state->by[i]); - EX_BUF_FREE(explore_state->entries); + EX_BUF_FREE(state->entries); - for (i = 0; i != EX_BUF_LEN(explore_state->playlists); i++) - playlist_free(explore_state->playlists[i]); - EX_BUF_FREE(explore_state->playlists); - - ex_arena_free(&explore_state->arena); - free(explore_state); - explore_state = NULL; + for (i = 0; i != EX_BUF_LEN(state->playlists); i++) + playlist_free(state->playlists[i]); + EX_BUF_FREE(state->playlists); + ex_arena_free(&state->arena); } static void explore_build_list(void) { unsigned i; char tmp[PATH_MAX_LENGTH]; - struct explore_rdb { libretrodb_t *handle; ex_hashmap32 playlist_entries; } *rdbs = NULL; + struct explore_rdb { libretrodb_t *handle; ex_hashmap32 playlist_entries; } + *rdbs = NULL; core_info_list_t *core_list = NULL; ex_hashmap32 map_cores = {0}; ex_hashmap32 rdb_indices = {0}; explore_string_t **split_buf = NULL; ex_hashmap32 cat_maps[EXPLORE_CAT_COUNT] = {{0}}; - settings_t *settings = NULL; - const char *directory_playlist = NULL; - const char *directory_database = NULL; + settings_t *settings = config_get_ptr(); + const char *directory_playlist = settings->paths.directory_playlist; + const char *directory_database = settings->paths.path_content_database; libretro_vfs_implementation_dir *dir = NULL; if (explore_state) - explore_free(); + { + explore_free(explore_state); + free(explore_state); + explore_state = NULL; + } + + explore_state = (explore_state_t*)calloc( + 1, sizeof(explore_state_t)); - explore_state = calloc(1, sizeof(explore_state_t)); explore_state->label_explore_item_str = msg_hash_to_str(MENU_ENUM_LABEL_EXPLORE_ITEM); - settings = config_get_ptr(); - directory_playlist = settings->paths.directory_playlist; - directory_database = settings->paths.path_content_database; - /* Index all playlists */ for (dir = retro_vfs_opendir_impl(directory_playlist, false); dir;) { - size_t j, used_entries = 0; + size_t j, used_entries = 0; playlist_config_t playlist_config = {0}; playlist_t *playlist = NULL; const char *fext = NULL; @@ -520,7 +550,7 @@ static void explore_build_list(void) /* Maybe remove this to also include playlist entries * with no CRC/db/label (build label from file name) */ if ( - !entry->crc32 + !entry->crc32 || !*entry->crc32 || !entry->db_name || !*entry->db_name @@ -545,7 +575,7 @@ static void explore_build_list(void) if (libretrodb_open(tmp, rdb.handle) != 0) { - /* invalid rdb file */ + /* Invalid RDB file */ libretrodb_free(rdb.handle); ex_hashmap32_strsetnum(&rdb_indices, entry->db_name, (uintptr_t)-1); @@ -575,7 +605,9 @@ static void explore_build_list(void) if (core_info_get_list(&core_list) && core_list) for (i = 0; i != core_list->count; i++) - ex_hashmap32_strsetptr(&map_cores, core_list->list[i].display_name, &core_list->list[i]); + ex_hashmap32_strsetptr(&map_cores, + core_list->list[i].display_name, + &core_list->list[i]); /* Loop through all RDBs referenced in the playlists * and load meta data strings */ @@ -616,8 +648,9 @@ static void explore_build_list(void) key_str = key->val.string.buff; if (string_is_equal(key_str, "crc")) { - crc32 = swap_if_little32(*(uint32_t*)val->val.binary.buff); - found_crc32 = true; + crc32 = + swap_if_little32(*(uint32_t*)val->val.binary.buff); + found_crc32 = true; continue; } else if (string_is_equal(key_str, "original_title")) @@ -717,26 +750,33 @@ static void explore_build_list(void) ex_hashmap32_free(&cat_maps[i]); } - qsort(explore_state->entries, EX_BUF_LEN(explore_state->entries), sizeof(*explore_state->entries), explore_qsort_func_entries); + qsort(explore_state->entries, + EX_BUF_LEN(explore_state->entries), + sizeof(*explore_state->entries), explore_qsort_func_entries); } -static int explore_action_get_title(const char *path, const char *label, unsigned menu_type, char *s, size_t len) +static int explore_action_get_title( + const char *path, const char *label, + unsigned menu_type, char *s, size_t len) { - snprintf(s, len, "%s", explore_state->title); + strlcpy(s, explore_state->title, len); return 0; } -static void explore_append_title(const char* fmt, ...) +static void explore_append_title(explore_state_t *state, + const char* fmt, ...) { va_list ap; - size_t len = strlen(explore_state->title); + size_t len = strlen(state->title); va_start(ap, fmt); - vsnprintf(explore_state->title + len, - sizeof(explore_state->title) - len, fmt, ap); + vsnprintf(state->title + len, + sizeof(state->title) - len, fmt, ap); va_end(ap); } -static int explore_action_sublabel_spacer(file_list_t *list, unsigned type, unsigned i, const char *label, const char *path, char *s, size_t len) +static int explore_action_sublabel_spacer( + file_list_t *list, unsigned type, unsigned i, + const char *label, const char *path, char *s, size_t len) { strlcpy(s, " ", len); return 1; /* 1 means it'll never change and can be cached */ @@ -747,14 +787,22 @@ static int explore_action_ok(const char *path, const char *label, { const char* explore_tab = msg_hash_to_str(MENU_ENUM_LABEL_EXPLORE_TAB); filebrowser_clear_type(); - return generic_action_ok_displaylist_push(explore_tab, NULL, explore_tab, type, idx, entry_idx, ACTION_OK_DL_PUSH_DEFAULT); + return generic_action_ok_displaylist_push(explore_tab, + NULL, explore_tab, type, idx, entry_idx, ACTION_OK_DL_PUSH_DEFAULT); } -static menu_file_list_cbs_t* explore_menu_entry(file_list_t *list, const char *path, unsigned type) +static menu_file_list_cbs_t *explore_menu_entry( + file_list_t *list, + explore_state_t *state, + const char *path, unsigned type) { - menu_file_list_cbs_t* cbs = NULL; - menu_entries_append_enum(list, path, explore_state->label_explore_item_str, MENU_ENUM_LABEL_EXPLORE_ITEM, type, 0, 0); - cbs = ((menu_file_list_cbs_t*)list->list[list->size-1].actiondata); + menu_file_list_cbs_t *cbs = NULL; + if (!state) + return NULL; + menu_entries_append_enum(list, path, + state->label_explore_item_str, + MENU_ENUM_LABEL_EXPLORE_ITEM, type, 0, 0); + cbs = ((menu_file_list_cbs_t*)list->list[list->size-1].actiondata); if (!cbs) return NULL; cbs->action_ok = explore_action_ok; @@ -763,9 +811,8 @@ static menu_file_list_cbs_t* explore_menu_entry(file_list_t *list, const char *p static void explore_menu_add_spacer(file_list_t *list) { - if (!list->size) - return; - ((menu_file_list_cbs_t*)list->list[list->size-1].actiondata)->action_sublabel = explore_action_sublabel_spacer; + if (list->size) + ((menu_file_list_cbs_t*)list->list[list->size-1].actiondata)->action_sublabel = explore_action_sublabel_spacer; } static void explore_action_find_complete(void *userdata, const char *line) @@ -773,7 +820,8 @@ static void explore_action_find_complete(void *userdata, const char *line) menu_input_dialog_end(); if (line && *line) { - strlcpy(explore_state->find_string, line, sizeof(explore_state->find_string)); + strlcpy(explore_state->find_string, line, + sizeof(explore_state->find_string)); explore_action_ok(NULL, NULL, EXPLORE_TYPE_SEARCH, 0, 0); } } @@ -792,14 +840,14 @@ unsigned menu_displaylist_explore(file_list_t *list) unsigned i, cat; char tmp[512]; unsigned depth, current_type, current_cat, previous_cat; - unsigned levels = 0; - struct item_file *stack_top = NULL; - file_list_t *menu_stack = menu_entries_get_menu_stack_ptr(0); + unsigned levels = 0; + struct item_file *stack_top = NULL; + file_list_t *menu_stack = menu_entries_get_menu_stack_ptr(0); if (!explore_state) { explore_build_list(); - explore_state->top_depth = (unsigned)menu_stack->size - 1; + explore_state->top_depth = (unsigned)menu_stack->size - 1; } if (menu_stack->size > 1) @@ -818,7 +866,8 @@ unsigned menu_displaylist_explore(file_list_t *list) if (depth) { bool clear_find_text = false; - ((menu_file_list_cbs_t*)stack_top[depth].actiondata)->action_get_title = explore_action_get_title; + ((menu_file_list_cbs_t*)stack_top[depth].actiondata)->action_get_title = + explore_action_get_title; clear_find_text = (current_type != EXPLORE_TYPE_SEARCH); explore_state->title[0] = '\0'; @@ -839,14 +888,17 @@ unsigned menu_displaylist_explore(file_list_t *list) by_selected_type = stack_top[i + 1].type; name = explore_by_info[by_category].name; entries = explore_state->by[by_category]; - explore_append_title("%s%s: %s", (levels++ ? " / " : ""), name, (by_selected_type != EXPLORE_TYPE_FILTERNULL ? entries[by_selected_type - EXPLORE_TYPE_FIRSTITEM]->str : "Unknown")); + explore_append_title(explore_state, + "%s%s: %s", (levels++ ? " / " : ""), + name, (by_selected_type != EXPLORE_TYPE_FILTERNULL ? entries[by_selected_type - EXPLORE_TYPE_FIRSTITEM]->str : "Unknown")); } if (clear_find_text) explore_state->find_string[0] = '\0'; if (*explore_state->find_string) - explore_append_title(" '%s'", explore_state->find_string); + explore_append_title(explore_state, + " '%s'", explore_state->find_string); } playlist_set_cached(NULL); @@ -859,12 +911,16 @@ unsigned menu_displaylist_explore(file_list_t *list) if (is_top) strlcpy(explore_state->title, "Explore", sizeof(explore_state->title)); else - explore_append_title(" - Additional Filter"); + explore_append_title(explore_state, + " - Additional Filter"); if (is_top || !*explore_state->find_string) { - explore_menu_entry(list, "Search Name ...", - EXPLORE_TYPE_SEARCH)->action_ok = explore_action_ok_find; + menu_file_list_cbs_t *new_cbs = explore_menu_entry( + list, explore_state, + "Search Name ...", EXPLORE_TYPE_SEARCH); + if (new_cbs) + new_cbs->action_ok = explore_action_ok_find; explore_menu_add_spacer(list); } @@ -872,6 +928,7 @@ unsigned menu_displaylist_explore(file_list_t *list) { const char *name = explore_by_info[cat].name; explore_string_t **entries = explore_state->by[cat]; + if (!EX_BUF_LEN(entries)) continue; @@ -886,7 +943,8 @@ unsigned menu_displaylist_explore(file_list_t *list) else snprintf(tmp, sizeof(tmp), "By %s (%u entries)", name, (unsigned)EX_BUF_LEN(entries)); - explore_menu_entry(list, tmp, cat + EXPLORE_TYPE_FIRSTCATEGORY); + explore_menu_entry(list, explore_state, + tmp, cat + EXPLORE_TYPE_FIRSTCATEGORY); SKIP_EXPLORE_BY_CATEGORY:; } @@ -894,7 +952,8 @@ SKIP_EXPLORE_BY_CATEGORY:; if (is_top) { explore_menu_add_spacer(list); - explore_menu_entry(list, "Show All", EXPLORE_TYPE_SHOWALL); + explore_menu_entry(list, explore_state, + "Show All", EXPLORE_TYPE_SHOWALL); } } else if ( @@ -906,16 +965,21 @@ SKIP_EXPLORE_BY_CATEGORY:; explore_string_t **entries = explore_state->by[current_cat]; unsigned i_last = EX_BUF_LEN(entries) - 1; for (i = 0; i <= i_last; i++) - explore_menu_entry(list, entries[i]->str, EXPLORE_TYPE_FIRSTITEM + i); + explore_menu_entry(list, explore_state, + entries[i]->str, EXPLORE_TYPE_FIRSTITEM + i); + if (explore_state->has_unknown[current_cat]) { explore_menu_add_spacer(list); - explore_menu_entry(list, "Unknown", EXPLORE_TYPE_FILTERNULL); + explore_menu_entry(list, explore_state, + "Unknown", EXPLORE_TYPE_FILTERNULL); } - explore_append_title("Select %s", explore_by_info[current_cat].name); + + explore_append_title(explore_state, + "Select %s", explore_by_info[current_cat].name); } else if ( - previous_cat < EXPLORE_CAT_COUNT + previous_cat < EXPLORE_CAT_COUNT || current_type < EXPLORE_TYPE_FIRSTITEM) { bool use_split[10]; @@ -934,26 +998,33 @@ SKIP_EXPLORE_BY_CATEGORY:; /* List filtered items in a selected explore by category */ if (is_filtered_category) - explore_append_title(" - Select %s", + explore_append_title(explore_state, + " - Select %s", explore_by_info[current_cat].name); else { /* Game list */ if (is_show_all) { - explore_append_title("All"); - explore_menu_entry(list, "Search Name ...", - EXPLORE_TYPE_SEARCH)->action_ok = explore_action_ok_find; + menu_file_list_cbs_t *new_cbs = NULL; + explore_append_title(explore_state, + "All"); + new_cbs = explore_menu_entry( + list, explore_state, "Search Name ...", + EXPLORE_TYPE_SEARCH); + if (new_cbs) + new_cbs->action_ok = explore_action_ok_find; } else - explore_menu_entry(list, "Add Additional Filter", + explore_menu_entry(list, explore_state, + "Add Additional Filter", EXPLORE_TYPE_ADDITIONALFILTER); explore_menu_add_spacer(list); } for (i = 1; i < depth; i++) { - explore_string_t **entries; + explore_string_t **entries = NULL; unsigned by_selected_type = 0; unsigned by_category = (stack_top[i].type - EXPLORE_TYPE_FIRSTCATEGORY); @@ -972,8 +1043,9 @@ SKIP_EXPLORE_BY_CATEGORY:; levels++; } - e = explore_state->entries; - e_end = EX_BUF_END(explore_state->entries); + e = explore_state->entries; + e_end = EX_BUF_END(explore_state->entries); + for (; e != e_end; e++) { unsigned lvl; @@ -984,17 +1056,20 @@ SKIP_EXPLORE_BY_CATEGORY:; if (use_split[lvl] && e->split) { explore_string_t** split = e->split; - do { + do + { if (*split == filter[lvl]) break; - }while(*(++split)); + } while (*(++split)); if (*split) continue; } goto SKIP_ENTRY; } - if (use_find && !strcasestr(e->playlist_entry->label, explore_state->find_string)) + if (use_find && + !strcasestr(e->playlist_entry->label, + explore_state->find_string)) goto SKIP_ENTRY; if (is_filtered_category) @@ -1008,11 +1083,18 @@ SKIP_EXPLORE_BY_CATEGORY:; if (ex_hashmap32_getnum(&map_filtered_category, str->idx + 1)) continue; ex_hashmap32_setnum(&map_filtered_category, str->idx + 1, 1); - explore_menu_entry(list, str->str, EXPLORE_TYPE_FIRSTITEM + str->idx); + explore_menu_entry(list, explore_state, + str->str, + EXPLORE_TYPE_FIRSTITEM + str->idx); } else { - explore_menu_entry(list, (e->original_title ? e->original_title : e->playlist_entry->label), EXPLORE_TYPE_FIRSTITEM + (e - explore_state->entries)); + explore_menu_entry(list, + explore_state, + (e->original_title + ? e->original_title + : e->playlist_entry->label), + EXPLORE_TYPE_FIRSTITEM + (e - explore_state->entries)); } SKIP_ENTRY:; @@ -1024,27 +1106,32 @@ SKIP_ENTRY:; if (is_filtered_category && filtered_category_have_unknown) { explore_menu_add_spacer(list); - explore_menu_entry(list, "Unknown", EXPLORE_TYPE_FILTERNULL); + explore_menu_entry(list, explore_state, + "Unknown", EXPLORE_TYPE_FILTERNULL); } - explore_append_title(" (%u)", (unsigned) (list->size - 1)); + explore_append_title(explore_state, + " (%u)", (unsigned) (list->size - 1)); } else { /* Content page of selected game */ int pl_idx; - const struct playlist_entry *pl_entry = explore_state->entries[current_type - EXPLORE_TYPE_FIRSTITEM].playlist_entry; + const struct playlist_entry *pl_entry = + explore_state->entries[current_type - EXPLORE_TYPE_FIRSTITEM].playlist_entry; + menu_handle_t *menu = menu_driver_get_ptr(); - snprintf(explore_state->title, - sizeof(explore_state->title), "%s", pl_entry->label); + strlcpy(explore_state->title, + pl_entry->label, sizeof(explore_state->title)); for (pl_idx = 0; pl_idx != EX_BUF_LEN(explore_state->playlists); pl_idx++) { + menu_displaylist_info_t info; const struct playlist_entry* pl_first = NULL; playlist_t *pl = explore_state->playlists[pl_idx]; - menu_handle_t *menu = menu_driver_get_ptr(); - menu_displaylist_info_t info = {0}; + + menu_displaylist_info_init(&info); playlist_get_index(pl, 0, &pl_first); From 4c6136d970917e3ea55fcf9868c85062b074151d Mon Sep 17 00:00:00 2001 From: twinaphex Date: Tue, 28 Jul 2020 12:15:05 +0200 Subject: [PATCH 08/17] Stub out menu_explore.c when HAVE_LIBRETRODB is not defined --- Makefile.common | 10 ++- griffin/griffin.c | 2 + menu/drivers/ozone/ozone.c | 2 + menu/drivers/ozone/ozone_sidebar.c | 14 +++++ menu/drivers/xmb.c | 2 + menu/menu_displaylist.c | 98 ++++++++++++++++-------------- 6 files changed, 79 insertions(+), 49 deletions(-) diff --git a/Makefile.common b/Makefile.common index 04ca1b58cf..a7c4263101 100644 --- a/Makefile.common +++ b/Makefile.common @@ -531,6 +531,10 @@ ifeq ($(HAVE_LIBRETRODB), 1) database_info.o \ tasks/task_database.o \ tasks/task_database_cue.o + + ifeq ($(HAVE_MENU), 1) + OBJ += menu/menu_explore.o + endif endif ifeq ($(HAVE_BUILTINMBEDTLS), 1) @@ -541,7 +545,8 @@ ifeq ($(HAVE_BUILTINMBEDTLS), 1) DEFINES += -DMBEDTLS_SSL_DEBUG_ALL endif - # MinGW requires this for some reason, even though the include paths are relative to the source + # MinGW requires this for some reason, + # even though the include paths are relative to the source INCLUDE_DIRS += -Ideps/mbedtls OBJS_TLS_CRYPTO = deps/mbedtls/aes.o \ @@ -913,8 +918,7 @@ ifeq ($(HAVE_MENU_COMMON), 1) menu/cbs/menu_cbs_up.o \ menu/cbs/menu_cbs_down.o \ menu/cbs/menu_cbs_contentlist_switch.o \ - menu/menu_displaylist.o \ - menu/menu_explore.o + menu/menu_displaylist.o endif ifeq ($(HAVE_GFX_WIDGETS), 1) diff --git a/griffin/griffin.c b/griffin/griffin.c index 9bd8529e5f..14ceac4d33 100644 --- a/griffin/griffin.c +++ b/griffin/griffin.c @@ -1349,8 +1349,10 @@ MENU #include "../menu/cbs/menu_cbs_down.c" #include "../menu/cbs/menu_cbs_contentlist_switch.c" #include "../menu/menu_displaylist.c" +#ifdef HAVE_LIBRETRODB #include "../menu/menu_explore.c" #endif +#endif #if defined(HAVE_LIBNX) #include "../gfx/drivers_display/gfx_display_switch.c" diff --git a/menu/drivers/ozone/ozone.c b/menu/drivers/ozone/ozone.c index f4fe44f5e1..07878673db 100644 --- a/menu/drivers/ozone/ozone.c +++ b/menu/drivers/ozone/ozone.c @@ -196,7 +196,9 @@ static void *ozone_init(void **userdata, bool video_is_threaded) if (settings->bools.menu_content_show_add && !settings->bools.kiosk_mode_enable) ozone->tabs[++ozone->system_tab_end] = OZONE_SYSTEM_TAB_ADD; +#ifdef HAVE_LIBRETRODB ozone->tabs[++ozone->system_tab_end] = OZONE_SYSTEM_TAB_EXPLORE; +#endif menu_driver_ctl(RARCH_MENU_CTL_UNSET_PREVENT_POPULATE, NULL); diff --git a/menu/drivers/ozone/ozone_sidebar.c b/menu/drivers/ozone/ozone_sidebar.c index e05141fb9b..0cef8496be 100644 --- a/menu/drivers/ozone/ozone_sidebar.c +++ b/menu/drivers/ozone/ozone_sidebar.c @@ -46,8 +46,12 @@ enum msg_hash_enums ozone_system_tabs_value[OZONE_SYSTEM_TAB_LAST] = { #ifdef HAVE_NETWORKING MENU_ENUM_LABEL_VALUE_NETPLAY_TAB, #endif +#ifdef HAVE_LIBRETRODB MENU_ENUM_LABEL_VALUE_ADD_TAB, MENU_ENUM_LABEL_VALUE_EXPLORE_TAB +#else + MENU_ENUM_LABEL_VALUE_ADD_TAB +#endif }; enum menu_settings_type ozone_system_tabs_type[OZONE_SYSTEM_TAB_LAST] = { @@ -65,8 +69,12 @@ enum menu_settings_type ozone_system_tabs_type[OZONE_SYSTEM_TAB_LAST] = { #ifdef HAVE_NETWORKING MENU_NETPLAY_TAB, #endif +#ifdef HAVE_LIBRETRODB MENU_ADD_TAB, MENU_EXPLORE_TAB +#else + MENU_ADD_TAB +#endif }; enum msg_hash_enums ozone_system_tabs_idx[OZONE_SYSTEM_TAB_LAST] = { @@ -84,8 +92,12 @@ enum msg_hash_enums ozone_system_tabs_idx[OZONE_SYSTEM_TAB_LAST] = { #ifdef HAVE_NETWORKING MENU_ENUM_LABEL_NETPLAY_TAB, #endif +#ifdef HAVE_LIBRETRODB MENU_ENUM_LABEL_ADD_TAB, MENU_ENUM_LABEL_EXPLORE_TAB +#else + MENU_ENUM_LABEL_ADD_TAB +#endif }; unsigned ozone_system_tabs_icons[OZONE_SYSTEM_TAB_LAST] = { @@ -1010,7 +1022,9 @@ bool ozone_is_playlist(ozone_handle_t *ozone, bool depth) #ifdef HAVE_NETWORKING case OZONE_SYSTEM_TAB_NETPLAY: #endif +#ifdef HAVE_LIBRETRODB case OZONE_SYSTEM_TAB_EXPLORE: +#endif is_playlist = false; break; case OZONE_SYSTEM_TAB_HISTORY: diff --git a/menu/drivers/xmb.c b/menu/drivers/xmb.c index 4c8882c9bc..f11f91d581 100644 --- a/menu/drivers/xmb.c +++ b/menu/drivers/xmb.c @@ -5450,7 +5450,9 @@ static void *xmb_init(void **userdata, bool video_is_threaded) && !settings->bools.kiosk_mode_enable) xmb->tabs[++xmb->system_tab_end] = XMB_SYSTEM_TAB_ADD; +#ifdef HAVE_LIBRETRODB xmb->tabs[++xmb->system_tab_end] = XMB_SYSTEM_TAB_EXPLORE; +#endif menu_driver_ctl(RARCH_MENU_CTL_UNSET_PREVENT_POPULATE, NULL); diff --git a/menu/menu_displaylist.c b/menu/menu_displaylist.c index ea5e3a0729..39ba8eda9f 100644 --- a/menu/menu_displaylist.c +++ b/menu/menu_displaylist.c @@ -3053,68 +3053,70 @@ static unsigned menu_displaylist_parse_playlists( * from the main menu 'Scan Content' entry. Placing * them here as well is unnecessary/ugly duplication */ if (settings->bools.menu_content_show_add && - !(string_is_equal(menu_ident, "glui") && - !settings->bools.menu_materialui_show_nav_bar)) + !(string_is_equal(menu_ident, "glui") && + !settings->bools.menu_materialui_show_nav_bar)) { #ifdef HAVE_LIBRETRODB if (menu_entries_append_enum(info->list, - msg_hash_to_str(MENU_ENUM_LABEL_VALUE_SCAN_DIRECTORY), - msg_hash_to_str(MENU_ENUM_LABEL_SCAN_DIRECTORY), - MENU_ENUM_LABEL_SCAN_DIRECTORY, - MENU_SETTING_ACTION, 0, 0)) + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_SCAN_DIRECTORY), + msg_hash_to_str(MENU_ENUM_LABEL_SCAN_DIRECTORY), + MENU_ENUM_LABEL_SCAN_DIRECTORY, + MENU_SETTING_ACTION, 0, 0)) count++; if (menu_entries_append_enum(info->list, - msg_hash_to_str(MENU_ENUM_LABEL_VALUE_SCAN_FILE), - msg_hash_to_str(MENU_ENUM_LABEL_SCAN_FILE), - MENU_ENUM_LABEL_SCAN_FILE, - MENU_SETTING_ACTION, 0, 0)) + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_SCAN_FILE), + msg_hash_to_str(MENU_ENUM_LABEL_SCAN_FILE), + MENU_ENUM_LABEL_SCAN_FILE, + MENU_SETTING_ACTION, 0, 0)) count++; #endif if (menu_entries_append_enum(info->list, - msg_hash_to_str(MENU_ENUM_LABEL_VALUE_MANUAL_CONTENT_SCAN_LIST), - msg_hash_to_str(MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_LIST), - MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_LIST, - MENU_SETTING_ACTION, 0, 0)) + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_MANUAL_CONTENT_SCAN_LIST), + msg_hash_to_str(MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_LIST), + MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_LIST, + MENU_SETTING_ACTION, 0, 0)) count++; } - if (menu_entries_append_enum(info->list, - msg_hash_to_str(MENU_ENUM_LABEL_VALUE_EXPLORE_TAB), - msg_hash_to_str(MENU_ENUM_LABEL_EXPLORE_TAB), - MENU_ENUM_LABEL_EXPLORE_TAB, - MENU_EXPLORE_TAB, 0, 0)) - count++; - if (settings->bools.menu_content_show_favorites) +#ifdef HAVE_LIBRETRODB if (menu_entries_append_enum(info->list, - msg_hash_to_str(MENU_ENUM_LABEL_VALUE_GOTO_FAVORITES), - msg_hash_to_str(MENU_ENUM_LABEL_GOTO_FAVORITES), - MENU_ENUM_LABEL_GOTO_FAVORITES, - MENU_SETTING_ACTION, 0, 0)) - count++; - if (settings->bools.menu_content_show_images) - if (menu_entries_append_enum(info->list, - msg_hash_to_str(MENU_ENUM_LABEL_VALUE_GOTO_IMAGES), - msg_hash_to_str(MENU_ENUM_LABEL_GOTO_IMAGES), - MENU_ENUM_LABEL_GOTO_IMAGES, - MENU_SETTING_ACTION, 0, 0)) + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_EXPLORE_TAB), + msg_hash_to_str(MENU_ENUM_LABEL_EXPLORE_TAB), + MENU_ENUM_LABEL_EXPLORE_TAB, + MENU_EXPLORE_TAB, 0, 0)) count++; +#endif + if (settings->bools.menu_content_show_favorites) + if (menu_entries_append_enum(info->list, + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_GOTO_FAVORITES), + msg_hash_to_str(MENU_ENUM_LABEL_GOTO_FAVORITES), + MENU_ENUM_LABEL_GOTO_FAVORITES, + MENU_SETTING_ACTION, 0, 0)) + count++; + if (settings->bools.menu_content_show_images) + if (menu_entries_append_enum(info->list, + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_GOTO_IMAGES), + msg_hash_to_str(MENU_ENUM_LABEL_GOTO_IMAGES), + MENU_ENUM_LABEL_GOTO_IMAGES, + MENU_SETTING_ACTION, 0, 0)) + count++; - if (settings->bools.menu_content_show_music) - if (menu_entries_append_enum(info->list, - msg_hash_to_str(MENU_ENUM_LABEL_VALUE_GOTO_MUSIC), - msg_hash_to_str(MENU_ENUM_LABEL_GOTO_MUSIC), - MENU_ENUM_LABEL_GOTO_MUSIC, - MENU_SETTING_ACTION, 0, 0)) - count++; + if (settings->bools.menu_content_show_music) + if (menu_entries_append_enum(info->list, + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_GOTO_MUSIC), + msg_hash_to_str(MENU_ENUM_LABEL_GOTO_MUSIC), + MENU_ENUM_LABEL_GOTO_MUSIC, + MENU_SETTING_ACTION, 0, 0)) + count++; #if defined(HAVE_FFMPEG) || defined(HAVE_MPV) - if (settings->bools.menu_content_show_video) - if (menu_entries_append_enum(info->list, - msg_hash_to_str(MENU_ENUM_LABEL_VALUE_GOTO_VIDEO), - msg_hash_to_str(MENU_ENUM_LABEL_GOTO_VIDEO), - MENU_ENUM_LABEL_GOTO_VIDEO, - MENU_SETTING_ACTION, 0, 0)) - count++; + if (settings->bools.menu_content_show_video) + if (menu_entries_append_enum(info->list, + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_GOTO_VIDEO), + msg_hash_to_str(MENU_ENUM_LABEL_GOTO_VIDEO), + MENU_ENUM_LABEL_GOTO_VIDEO, + MENU_SETTING_ACTION, 0, 0)) + count++; #endif } @@ -4363,11 +4365,13 @@ static bool menu_displaylist_push_internal( if (menu_displaylist_ctl(DISPLAYLIST_SCAN_DIRECTORY_LIST, info)) return true; } +#ifdef HAVE_LIBRETRODB else if (string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_EXPLORE_TAB))) { if (menu_displaylist_ctl(DISPLAYLIST_EXPLORE, info)) return true; } +#endif else if (string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_NETPLAY_TAB))) { if (menu_displaylist_ctl(DISPLAYLIST_NETPLAY_ROOM_LIST, info)) @@ -5446,10 +5450,12 @@ unsigned menu_displaylist_build_list( count = menu_displaylist_parse_system_info(list); break; case DISPLAYLIST_EXPLORE: +#ifdef HAVE_LIBRETRODB { unsigned menu_displaylist_explore(file_list_t *list); count = menu_displaylist_explore(list); } +#endif break; case DISPLAYLIST_SCAN_DIRECTORY_LIST: #ifdef HAVE_LIBRETRODB From 59549d9f62e038cbdd08846bcede5c525315c110 Mon Sep 17 00:00:00 2001 From: Twinaphex Date: Tue, 28 Jul 2020 12:17:16 +0200 Subject: [PATCH 09/17] Remove unused variable --- retroarch.c | 1 - 1 file changed, 1 deletion(-) diff --git a/retroarch.c b/retroarch.c index a6b3a49f50..2e0c2efdd3 100644 --- a/retroarch.c +++ b/retroarch.c @@ -33409,7 +33409,6 @@ static bool video_driver_get_flags(gfx_ctx_flags_t *flags) gfx_ctx_flags_t video_driver_get_flags_wrapper(void) { gfx_ctx_flags_t flags; - struct rarch_state *p_rarch = &rarch_st; flags.flags = 0; if (!video_driver_get_flags(&flags)) From 89567dae5d0be43ba12bf6fad415aefd58a75c86 Mon Sep 17 00:00:00 2001 From: twinaphex Date: Tue, 28 Jul 2020 12:42:33 +0200 Subject: [PATCH 10/17] (input hid) Change back to original code --- input/common/hid/device_ds3.c | 7 +++---- input/common/hid/device_wiiu_gca.c | 7 +++---- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/input/common/hid/device_ds3.c b/input/common/hid/device_ds3.c index 23fb1da089..199dcb44c9 100644 --- a/input/common/hid/device_ds3.c +++ b/input/common/hid/device_ds3.c @@ -330,10 +330,9 @@ static const char *ds3_get_name(void *data) { return "Sony DualShock 3"; } static int16_t ds3_button(void *data, uint16_t joykey) { ds3_instance_t *pad = (ds3_instance_t *)data; - if (joykey < 31) - if (pad) - return (pad->buttons & (1 << joykey)); - return 0; + if (!pad || joykey > 31) + return 0; + return pad->buttons & (1 << joykey); } pad_connection_interface_t ds3_pad_connection = { diff --git a/input/common/hid/device_wiiu_gca.c b/input/common/hid/device_wiiu_gca.c index dab22852de..83b7a75c44 100644 --- a/input/common/hid/device_wiiu_gca.c +++ b/input/common/hid/device_wiiu_gca.c @@ -328,10 +328,9 @@ static const char *wiiu_gca_get_name(void *data) static int16_t wiiu_gca_button(void *data, uint16_t joykey) { gca_pad_t *pad = (gca_pad_t *)data; - if (joykey < 31) - if (pad) - return pad->buttons & (1 << joykey); - return 0; + if(!pad || joykey > 31) + return 0; + return pad->buttons & (1 << joykey); } pad_connection_interface_t wiiu_gca_pad_connection = { From 77a7e7eecdfe084b20bfedc1ca8301826bc15710 Mon Sep 17 00:00:00 2001 From: Bernhard Schelling <14200249+schellingb@users.noreply.github.com> Date: Tue, 28 Jul 2020 21:06:20 +0900 Subject: [PATCH 11/17] Fix 2 memory leaks in explore view Free cached playlist that could have been loaded before entering explore view Free temporary hashmap used to filter sub-categories --- menu/menu_explore.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/menu/menu_explore.c b/menu/menu_explore.c index 033d2908a5..1b128aa74a 100644 --- a/menu/menu_explore.c +++ b/menu/menu_explore.c @@ -901,6 +901,12 @@ unsigned menu_displaylist_explore(file_list_t *list) " '%s'", explore_state->find_string); } + if (current_type == MENU_EXPLORE_TAB) + { + // free any existing playlist when entering the explore view + playlist_free_cached(); + } + playlist_set_cached(NULL); if ( current_type == MENU_EXPLORE_TAB @@ -1112,6 +1118,8 @@ SKIP_ENTRY:; explore_append_title(explore_state, " (%u)", (unsigned) (list->size - 1)); + + ex_hashmap32_free(&map_filtered_category); } else { From 395de4bae5dc49675827008edf9509b6b0a03cc9 Mon Sep 17 00:00:00 2001 From: twinaphex Date: Tue, 28 Jul 2020 14:32:46 +0200 Subject: [PATCH 12/17] (Menu explore) jdgleaver's memory leak fixes --- menu/menu_driver.h | 2 ++ menu/menu_explore.c | 22 +++++++++++++++------- retroarch.c | 3 +++ 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/menu/menu_driver.h b/menu/menu_driver.h index df0325ee64..aa8e92a069 100644 --- a/menu/menu_driver.h +++ b/menu/menu_driver.h @@ -474,6 +474,8 @@ void menu_display_handle_wallpaper_upload(retro_task_t *task, void *task_data, void *user_data, const char *err); +void menu_explore_free(void); + menu_handle_t *menu_driver_get_ptr(void); enum action_iterate_type diff --git a/menu/menu_explore.c b/menu/menu_explore.c index 1b128aa74a..8dd4bd1862 100644 --- a/menu/menu_explore.c +++ b/menu/menu_explore.c @@ -499,12 +499,7 @@ static void explore_build_list(void) const char *directory_database = settings->paths.path_content_database; libretro_vfs_implementation_dir *dir = NULL; - if (explore_state) - { - explore_free(explore_state); - free(explore_state); - explore_state = NULL; - } + menu_explore_free(); explore_state = (explore_state_t*)calloc( 1, sizeof(explore_state_t)); @@ -744,7 +739,10 @@ static void explore_build_list(void) { uint32_t idx; size_t len = EX_BUF_LEN(explore_state->by[i]); - qsort(explore_state->by[i], len, sizeof(*explore_state->by[i]), explore_qsort_func_strings); + + if (explore_state->by[i]) + qsort(explore_state->by[i], len, sizeof(*explore_state->by[i]), explore_qsort_func_strings); + for (idx = 0; idx != len; idx++) explore_state->by[i][idx]->idx = idx; @@ -1161,3 +1159,13 @@ SKIP_ENTRY:; return list->size; } + +void menu_explore_free(void) +{ + if (explore_state) + { + explore_free(explore_state); + free(explore_state); + explore_state = NULL; + } +} diff --git a/retroarch.c b/retroarch.c index 2e0c2efdd3..546718e2b8 100644 --- a/retroarch.c +++ b/retroarch.c @@ -7355,6 +7355,9 @@ bool menu_driver_ctl(enum rarch_menu_ctl_state state, void *data) #ifdef HAVE_NETWORKING core_updater_list_free_cached(); #endif +#ifdef HAVE_LIBRETRODB + menu_explore_free(); +#endif if (p_rarch->menu_driver_data) { From 6ccb758939931dd1005092deeff79cb336a2f608 Mon Sep 17 00:00:00 2001 From: twinaphex Date: Tue, 28 Jul 2020 14:33:23 +0200 Subject: [PATCH 13/17] Update --- menu/menu_explore.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/menu/menu_explore.c b/menu/menu_explore.c index 8dd4bd1862..62ad596794 100644 --- a/menu/menu_explore.c +++ b/menu/menu_explore.c @@ -901,7 +901,8 @@ unsigned menu_displaylist_explore(file_list_t *list) if (current_type == MENU_EXPLORE_TAB) { - // free any existing playlist when entering the explore view + /* free any existing playlist + * when entering the explore view */ playlist_free_cached(); } From 6c8506399c82f20c2bbf2283cf184468daf7f304 Mon Sep 17 00:00:00 2001 From: Bernhard Schelling <14200249+schellingb@users.noreply.github.com> Date: Tue, 28 Jul 2020 23:51:06 +0900 Subject: [PATCH 14/17] Fix crash when quitting while viewing item details When RetroArch was closed while viewing/playing a content item opened through the explore view there would be a crash during cleanup because the cached playlist would be freed twice (once by playlists own playlist_free_cached and once more through explore_free). --- menu/menu_explore.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/menu/menu_explore.c b/menu/menu_explore.c index 62ad596794..7946d9db3e 100644 --- a/menu/menu_explore.c +++ b/menu/menu_explore.c @@ -114,6 +114,7 @@ typedef struct char title[1024]; char find_string[1024]; unsigned top_depth; + playlist_t *cached_playlist; } explore_state_t; static const struct { const char *name, *rdbkey; bool use_split, is_company, is_numeric; } @@ -478,7 +479,8 @@ static void explore_free(explore_state_t *state) EX_BUF_FREE(state->entries); for (i = 0; i != EX_BUF_LEN(state->playlists); i++) - playlist_free(state->playlists[i]); + if (state->playlists[i] != state->cached_playlist) + playlist_free(state->playlists[i]); EX_BUF_FREE(state->playlists); ex_arena_free(&state->arena); } @@ -907,6 +909,7 @@ unsigned menu_displaylist_explore(file_list_t *list) } playlist_set_cached(NULL); + explore_state->cached_playlist = NULL; if ( current_type == MENU_EXPLORE_TAB || current_type == EXPLORE_TYPE_ADDITIONALFILTER) @@ -1149,6 +1152,7 @@ SKIP_ENTRY:; /* Fake all the state so the content screen * and information screen think we're viewing via playlist */ playlist_set_cached(pl); + explore_state->cached_playlist = pl; menu->rpl_entry_selection_ptr = (pl_entry - pl_first); strlcpy(menu->deferred_path, pl_entry->path, sizeof(menu->deferred_path)); From dd524d7d3592a44f09f5e8a82814b3a59a1e2032 Mon Sep 17 00:00:00 2001 From: twinaphex Date: Tue, 28 Jul 2020 16:57:29 +0200 Subject: [PATCH 15/17] (MaterialUI) Add placeholder Explore tab icon --- menu/drivers/materialui.c | 1 + 1 file changed, 1 insertion(+) diff --git a/menu/drivers/materialui.c b/menu/drivers/materialui.c index 2ded50bc46..c91106cdd6 100644 --- a/menu/drivers/materialui.c +++ b/menu/drivers/materialui.c @@ -9157,6 +9157,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_EXPLORE_TAB: node->icon_texture_index = MUI_TEXTURE_CORES; node->has_icon = true; break; From b1acb008324b80a6f69e02ecab59f1a51775a2a7 Mon Sep 17 00:00:00 2001 From: twinaphex Date: Tue, 28 Jul 2020 17:38:50 +0200 Subject: [PATCH 16/17] (menu_explore.c) Cleanups - prevent some implicit memsets --- menu/menu_explore.c | 44 +++++++++++++++++++++++++++++--------------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/menu/menu_explore.c b/menu/menu_explore.c index 7946d9db3e..d39a3a4976 100644 --- a/menu/menu_explore.c +++ b/menu/menu_explore.c @@ -494,8 +494,8 @@ static void explore_build_list(void) core_info_list_t *core_list = NULL; ex_hashmap32 map_cores = {0}; ex_hashmap32 rdb_indices = {0}; - explore_string_t **split_buf = NULL; ex_hashmap32 cat_maps[EXPLORE_CAT_COUNT] = {{0}}; + explore_string_t **split_buf = NULL; settings_t *settings = config_get_ptr(); const char *directory_playlist = settings->paths.directory_playlist; const char *directory_database = settings->paths.path_content_database; @@ -512,11 +512,19 @@ static void explore_build_list(void) /* Index all playlists */ for (dir = retro_vfs_opendir_impl(directory_playlist, false); dir;) { - size_t j, used_entries = 0; - playlist_config_t playlist_config = {0}; - playlist_t *playlist = NULL; - const char *fext = NULL; - const char *fname = NULL; + playlist_config_t playlist_config; + size_t j, used_entries = 0; + playlist_t *playlist = NULL; + const char *fext = NULL; + const char *fname = NULL; + + playlist_config.path[0] = '\0'; + playlist_config.base_content_directory[0] = '\0'; + playlist_config.capacity = 0; + playlist_config.old_format = false; + playlist_config.compress = false; + playlist_config.fuzzy_archive_match = false; + playlist_config.autofix_paths = false; if (!retro_vfs_readdir_impl(dir)) { @@ -624,30 +632,33 @@ static void explore_build_list(void) unsigned k, l, cat; uint32_t crc32; explore_entry_t e; + char *fields[EXPLORE_CAT_COUNT]; char numeric_buf[EXPLORE_CAT_COUNT][16]; - char* fields[EXPLORE_CAT_COUNT] = {NULL}; const struct playlist_entry *entry = NULL; - char* original_title = NULL; + char *original_title = NULL; bool found_crc32 = false; core_info_t* core_info = NULL; if (item.type != RDT_MAP) continue; + for (k = 0; k < EXPLORE_CAT_COUNT; k++) + fields[k] = NULL; + for (k = 0; k < item.val.map.len; k++) { - const char *key_str = NULL; - struct rmsgpack_dom_value *key = &item.val.map.items[k].key; - struct rmsgpack_dom_value *val = &item.val.map.items[k].value; + const char *key_str = NULL; + struct rmsgpack_dom_value *key = &item.val.map.items[k].key; + struct rmsgpack_dom_value *val = &item.val.map.items[k].value; if (!key || !val || key->type != RDT_STRING) continue; - key_str = key->val.string.buff; + key_str = key->val.string.buff; if (string_is_equal(key_str, "crc")) { - crc32 = + crc32 = swap_if_little32(*(uint32_t*)val->val.binary.buff); - found_crc32 = true; + found_crc32 = true; continue; } else if (string_is_equal(key_str, "original_title")) @@ -828,8 +839,11 @@ static void explore_action_find_complete(void *userdata, const char *line) static int explore_action_ok_find(const char *path, const char *label, unsigned type, size_t idx, size_t entry_idx) { - menu_input_ctx_line_t line = {0}; + menu_input_ctx_line_t line; line.label = "Search Text"; + line.label_setting = NULL; + line.type = 0; + line.idx = 0; line.cb = explore_action_find_complete; menu_input_dialog_start(&line); return 0; From 8f068fae6c6c57858fea7ff8c11d118312c5e71a Mon Sep 17 00:00:00 2001 From: Twinaphex Date: Tue, 28 Jul 2020 19:49:17 +0200 Subject: [PATCH 17/17] Cleanups --- playlist.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/playlist.c b/playlist.c index ab37a0d258..f6a720af52 100644 --- a/playlist.c +++ b/playlist.c @@ -176,7 +176,6 @@ static void path_replace_base_path_and_convert_to_local_file_system( const char *in_oldrefpath, const char *in_refpath, size_t size) { - const char fs_delimeter = LOCAL_FILE_SYSTEM_PATH_DELIMITER; size_t in_oldrefpath_length = strlen(in_oldrefpath); size_t in_refpath_length = strlen(in_refpath); @@ -2689,8 +2688,6 @@ playlist_t *playlist_init(const playlist_config_t *config) !string_is_equal(playlist->base_content_directory, playlist->config.base_content_directory)) { - size_t playlist_base_content_directory_length = strlen(playlist->base_content_directory); - size_t new_base_content_directory_length = strlen(playlist->config.base_content_directory); size_t i; size_t j; char tmp_entry_path[PATH_MAX_LENGTH];