diff --git a/core_info.c b/core_info.c index d7e8c5d593..1fbee987a9 100644 --- a/core_info.c +++ b/core_info.c @@ -42,6 +42,7 @@ core_info_list_t *core_info_list_new(const char *modules_path) for (size_t i = 0; i < contents->size; i++) { +#if 0 char buffer[PATH_MAX]; char info_path[PATH_MAX]; @@ -63,6 +64,11 @@ core_info_list_t *core_info_list_new(const char *modules_path) snprintf(info_path, PATH_MAX, "%s.info", buffer); else fill_pathname(info_path, buffer, ".info", sizeof(info_path)); +#else + core_info[i].path = strdup(contents->elems[i].data); + char info_path[PATH_MAX]; + fill_pathname(info_path, core_info[i].path, ".info", sizeof(info_path)); +#endif core_info[i].data = config_file_new(info_path); @@ -78,6 +84,27 @@ core_info_list_t *core_info_list_new(const char *modules_path) core_info[i].display_name = strdup(path_basename(core_info[i].path)); } + size_t all_ext_len = 0; + for (size_t i = 0; i < core_info_list->count; i++) + { + all_ext_len += core_info_list->list[i].supported_extensions ? + (strlen(core_info_list->list[i].supported_extensions) + 2) : 0; + } + + if (all_ext_len) + core_info_list->all_ext = (char*)calloc(1, all_ext_len); + if (core_info_list->all_ext) + { + for (size_t i = 0; i < core_info_list->count; i++) + { + if (core_info_list->list[i].supported_extensions) + { + strlcat(core_info_list->all_ext, core_info_list->list[i].supported_extensions, all_ext_len); + strlcat(core_info_list->all_ext, "|", all_ext_len); + } + } + } + dir_list_free(contents); return core_info_list; @@ -102,11 +129,12 @@ void core_info_list_free(core_info_list_t *core_info_list) config_file_free(core_info_list->list[i].data); } + free(core_info_list->all_ext); free(core_info_list->list); free(core_info_list); } -bool core_info_list_does_support_file(core_info_t *core, const char *path) +bool core_info_does_support_file(const core_info_t *core, const char *path) { if (!path || !core || !core->supported_extensions_list) return false; @@ -114,3 +142,38 @@ bool core_info_list_does_support_file(core_info_t *core, const char *path) return string_list_find_elem_prefix(core->supported_extensions_list, ".", path_get_extension(path)); } +const char *core_info_list_get_all_extensions(core_info_list_t *core_info_list) +{ + return core_info_list->all_ext; +} + +static const char *core_info_tmp_path; // qsort_r() is not in standard C, sadly. +static int core_info_qsort_cmp(const void *a_, const void *b_) +{ + const core_info_t *a = (const core_info_t*)a_; + const core_info_t *b = (const core_info_t*)b_; + int support_a = core_info_does_support_file(a, core_info_tmp_path); + int support_b = core_info_does_support_file(b, core_info_tmp_path); + if (support_a != support_b) + return support_b - support_a; + else + return strcasecmp(a->display_name, b->display_name); +} + +void core_info_list_get_supported_cores(core_info_list_t *core_info_list, const char *path, + const core_info_t **infos, size_t *num_infos) +{ + core_info_tmp_path = path; + qsort(core_info_list->list, core_info_list->count, sizeof(core_info_t), core_info_qsort_cmp); + + size_t supported = 0; + for (size_t i = 0; i < core_info_list->count; i++, supported++) + { + if (!core_info_does_support_file(&core_info_list->list[i], path)) + break; + } + + *infos = core_info_list->list; + *num_infos = supported; +} + diff --git a/core_info.h b/core_info.h index 4148f0db59..cbf4f26d59 100644 --- a/core_info.h +++ b/core_info.h @@ -35,12 +35,19 @@ typedef struct { typedef struct { core_info_t *list; size_t count; + char *all_ext; } core_info_list_t; core_info_list_t *core_info_list_new(const char *modules_path); void core_info_list_free(core_info_list_t *core_info_list); -bool core_info_list_does_support_file(core_info_t *core, const char *path); +bool core_info_does_support_file(const core_info_t *core, const char *path); + +// Non-reentrant, does not allocate. Returns pointer to internal state. +void core_info_list_get_supported_cores(core_info_list_t *core_info_list, const char *path, + const core_info_t **infos, size_t *num_infos); + +const char *core_info_list_get_all_extensions(core_info_list_t *core_info_list); #ifdef __cplusplus } diff --git a/frontend/menu/menu_common.c b/frontend/menu/menu_common.c index 69b07a7278..aedde6103c 100644 --- a/frontend/menu/menu_common.c +++ b/frontend/menu/menu_common.c @@ -408,6 +408,11 @@ static void menu_update_libretro_info(void) #else retro_get_system_info(&rgui->info); #endif + + core_info_list_free(rgui->core_info); + rgui->core_info = NULL; + if (*rgui->libretro_dir) + rgui->core_info = core_info_list_new(rgui->libretro_dir); } bool load_menu_game(void) @@ -513,6 +518,7 @@ void menu_free(void) #endif rom_history_free(rgui->history); + core_info_list_free(rgui->core_info); free(rgui); } diff --git a/frontend/menu/menu_common.h b/frontend/menu/menu_common.h index 198b5f5e69..42b5a3ae8c 100644 --- a/frontend/menu/menu_common.h +++ b/frontend/menu/menu_common.h @@ -24,6 +24,7 @@ #endif #include "../../performance.h" +#include "../../core_info.h" #ifdef HAVE_RGUI #define MENU_TEXTURE_FULLSCREEN false @@ -98,8 +99,10 @@ typedef enum // settings options are done here too RGUI_SETTINGS_OPEN_FILEBROWSER, + RGUI_SETTINGS_OPEN_FILEBROWSER_DEFERRED_CORE, RGUI_SETTINGS_OPEN_HISTORY, RGUI_SETTINGS_CORE, + RGUI_SETTINGS_DEFERRED_CORE, RGUI_SETTINGS_CONFIG, RGUI_SETTINGS_SAVE_CONFIG, RGUI_SETTINGS_CORE_OPTIONS, @@ -288,6 +291,10 @@ typedef struct bool need_refresh; bool msg_force; + core_info_list_t *core_info; + bool defer_core; + char deferred_path[PATH_MAX]; + // Quick jumping indices with L/R. // Rebuilt when parsing directory. size_t scroll_indices[2 * (26 + 2) + 1]; diff --git a/frontend/menu/menu_settings.c b/frontend/menu/menu_settings.c index de739e1694..5031165990 100644 --- a/frontend/menu/menu_settings.c +++ b/frontend/menu/menu_settings.c @@ -610,7 +610,11 @@ int menu_set_settings(unsigned setting, unsigned action) #ifdef HAVE_DYNAMIC case RGUI_LIBRETRO_DIR_PATH: if (action == RGUI_ACTION_START) + { *rgui->libretro_dir = '\0'; + core_info_list_free(rgui->core_info); + rgui->core_info = NULL; + } break; #endif case RGUI_CONFIG_DIR_PATH: diff --git a/frontend/menu/rgui.c b/frontend/menu/rgui.c index 48fce55e8f..2c2cecf113 100644 --- a/frontend/menu/rgui.c +++ b/frontend/menu/rgui.c @@ -227,6 +227,18 @@ static void rgui_resolve_libretro_names(rgui_list_t *list, const char *dir) } } +static void rgui_resolve_supported_cores(rgui_handle_t *rgui) +{ + const core_info_t *info = NULL; + size_t cores = 0; + core_info_list_get_supported_cores(rgui->core_info, rgui->deferred_path, &info, &cores); + for (size_t i = 0; i < cores; i++) + { + rgui_list_push(rgui->selection_buf, info[i].path, RGUI_FILE_PLAIN, 0); + rgui_list_set_alt_at_offset(rgui->selection_buf, i, info[i].display_name); + } +} + static int rgui_settings_toggle_setting(rgui_handle_t *rgui, unsigned setting, rgui_action_t action, unsigned menu_type) { #ifdef HAVE_SHADER_MANAGER @@ -277,7 +289,18 @@ static void rgui_settings_populate_entries(rgui_handle_t *rgui) #endif if (rgui->history) rgui_list_push(rgui->selection_buf, "Load Game (History)", RGUI_SETTINGS_OPEN_HISTORY, 0); - rgui_list_push(rgui->selection_buf, "Load Game", RGUI_SETTINGS_OPEN_FILEBROWSER, 0); + + if (rgui->core_info) + rgui_list_push(rgui->selection_buf, "Load Game (Autodetect Core)", RGUI_SETTINGS_OPEN_FILEBROWSER_DEFERRED_CORE, 0); + + if (rgui->info.library_name || g_extern.system.info.library_name) + { + char load_game_core_msg[64]; + snprintf(load_game_core_msg, sizeof(load_game_core_msg), "Load Game (%s)", + rgui->info.library_name ? rgui->info.library_name : g_extern.system.info.library_name); + rgui_list_push(rgui->selection_buf, load_game_core_msg, RGUI_SETTINGS_OPEN_FILEBROWSER, 0); + } + rgui_list_push(rgui->selection_buf, "Core Options", RGUI_SETTINGS_CORE_OPTIONS, 0); rgui_list_push(rgui->selection_buf, "Video Options", RGUI_SETTINGS_VIDEO_OPTIONS, 0); rgui_list_push(rgui->selection_buf, "Audio Options", RGUI_SETTINGS_AUDIO_OPTIONS, 0); @@ -799,8 +822,10 @@ static int rgui_settings_iterate(rgui_handle_t *rgui, rgui_action_t action) case RGUI_ACTION_RIGHT: case RGUI_ACTION_OK: case RGUI_ACTION_START: - if (type == RGUI_SETTINGS_OPEN_FILEBROWSER && action == RGUI_ACTION_OK) + if ((type == RGUI_SETTINGS_OPEN_FILEBROWSER || type == RGUI_SETTINGS_OPEN_FILEBROWSER_DEFERRED_CORE) + && action == RGUI_ACTION_OK) { + rgui->defer_core = type == RGUI_SETTINGS_OPEN_FILEBROWSER_DEFERRED_CORE; rgui_list_push(rgui->menu_stack, rgui->base_path, RGUI_FILE_DIRECTORY, rgui->selection_ptr); rgui->selection_ptr = 0; rgui->need_refresh = true; @@ -1066,6 +1091,8 @@ static bool rgui_directory_parse(rgui_handle_t *rgui, const char *directory, uns #endif else if (menu_type_is_directory_browser(menu_type)) exts = ""; // we ignore files anyway + else if (rgui->defer_core) + exts = rgui->core_info ? core_info_list_get_all_extensions(rgui->core_info) : ""; else if (rgui->info.valid_extensions) { exts = ext_buf; @@ -1238,7 +1265,23 @@ static int rgui_iterate(void *data, unsigned action) } else #endif - if (menu_type == RGUI_SETTINGS_CORE) + if (menu_type == RGUI_SETTINGS_DEFERRED_CORE) + { + // FIXME: Add for consoles. +#ifdef HAVE_DYNAMIC + strlcpy(g_settings.libretro, path, sizeof(g_settings.libretro)); + libretro_free_system_info(&rgui->info); + libretro_get_system_info(g_settings.libretro, &rgui->info, + &rgui->load_no_rom); + + strlcpy(g_extern.fullpath, rgui->deferred_path, sizeof(g_extern.fullpath)); + g_extern.lifecycle_mode_state |= (1ULL << MODE_LOAD_GAME); + rgui->msg_force = true; + ret = -1; +#endif + rgui_flush_menu_stack_type(rgui, RGUI_SETTINGS); + } + else if (menu_type == RGUI_SETTINGS_CORE) { #if defined(HAVE_DYNAMIC) fill_pathname_join(g_settings.libretro, dir, path, sizeof(g_settings.libretro)); @@ -1346,6 +1389,10 @@ static int rgui_iterate(void *data, unsigned action) else if (menu_type == RGUI_LIBRETRO_DIR_PATH) { strlcpy(rgui->libretro_dir, dir, sizeof(rgui->libretro_dir)); + core_info_list_free(rgui->core_info); + rgui->core_info = NULL; + if (*rgui->libretro_dir) + rgui->core_info = core_info_list_new(rgui->libretro_dir); rgui_flush_menu_stack_type(rgui, RGUI_SETTINGS_PATH_OPTIONS); } else if (menu_type == RGUI_CONFIG_DIR_PATH) @@ -1366,12 +1413,47 @@ static int rgui_iterate(void *data, unsigned action) } else { - fill_pathname_join(g_extern.fullpath, dir, path, sizeof(g_extern.fullpath)); - g_extern.lifecycle_mode_state |= (1ULL << MODE_LOAD_GAME); + if (rgui->defer_core) + { + fill_pathname_join(rgui->deferred_path, dir, path, sizeof(rgui->deferred_path)); - rgui_flush_menu_stack_type(rgui, RGUI_SETTINGS); - rgui->msg_force = true; - ret = -1; + const core_info_t *info = NULL; + size_t supported = 0; + if (rgui->core_info) + core_info_list_get_supported_cores(rgui->core_info, rgui->deferred_path, &info, &supported); + + if (supported == 1) // Can make a decision right now. + { + strlcpy(g_extern.fullpath, rgui->deferred_path, sizeof(g_extern.fullpath)); + + strlcpy(g_settings.libretro, info->path, sizeof(g_settings.libretro)); + +#ifdef HAVE_DYNAMIC + libretro_free_system_info(&rgui->info); + libretro_get_system_info(g_settings.libretro, &rgui->info, + &rgui->load_no_rom); +#endif + + g_extern.lifecycle_mode_state |= (1ULL << MODE_LOAD_GAME); + rgui_flush_menu_stack_type(rgui, RGUI_SETTINGS); + rgui->msg_force = true; + ret = -1; + } + else // Present a selection. + { + rgui_list_push(rgui->menu_stack, rgui->libretro_dir, RGUI_SETTINGS_DEFERRED_CORE, 0); + rgui->need_refresh = true; + } + } + else + { + fill_pathname_join(g_extern.fullpath, dir, path, sizeof(g_extern.fullpath)); + g_extern.lifecycle_mode_state |= (1ULL << MODE_LOAD_GAME); + + rgui_flush_menu_stack_type(rgui, RGUI_SETTINGS); + rgui->msg_force = true; + ret = -1; + } } } break; @@ -1401,6 +1483,7 @@ static int rgui_iterate(void *data, unsigned action) #ifdef HAVE_OVERLAY menu_type == RGUI_SETTINGS_OVERLAY_PRESET || #endif + menu_type == RGUI_SETTINGS_DEFERRED_CORE || menu_type == RGUI_SETTINGS_CORE || menu_type == RGUI_SETTINGS_CONFIG || menu_type == RGUI_SETTINGS_OPEN_HISTORY || @@ -1412,11 +1495,13 @@ static int rgui_iterate(void *data, unsigned action) rgui->scroll_indices_size = 0; if (menu_type == RGUI_SETTINGS_OPEN_HISTORY) history_parse(rgui); - else + else if (menu_type != RGUI_SETTINGS_DEFERRED_CORE) rgui_directory_parse(rgui, dir, menu_type, rgui->selection_buf); if (menu_type == RGUI_SETTINGS_CORE) rgui_resolve_libretro_names(rgui->selection_buf, dir); + else if (menu_type == RGUI_SETTINGS_DEFERRED_CORE) + rgui_resolve_supported_cores(rgui); // Before a refresh, we could have deleted a file on disk, causing // selection_ptr to suddendly be out of range. Ensure it doesn't overflow. diff --git a/frontend/menu/rguidisp_bitmap.c b/frontend/menu/rguidisp_bitmap.c index 8b9332801d..dae7c08f0c 100644 --- a/frontend/menu/rguidisp_bitmap.c +++ b/frontend/menu/rguidisp_bitmap.c @@ -232,6 +232,8 @@ static void render_text(rgui_handle_t *rgui) if (menu_type == RGUI_SETTINGS_CORE) snprintf(title, sizeof(title), "CORE SELECTION %s", dir); + else if (menu_type == RGUI_SETTINGS_DEFERRED_CORE) + snprintf(title, sizeof(title), "DETECTED CORES %s", dir); else if (menu_type == RGUI_SETTINGS_CONFIG) snprintf(title, sizeof(title), "CONFIG %s", dir); else if (menu_type == RGUI_SETTINGS_DISK_APPEND) @@ -360,9 +362,8 @@ static void render_text(rgui_handle_t *rgui) } else #endif -#ifdef HAVE_DYNAMIC // Pretty-print libretro cores from menu. - if (menu_type == RGUI_SETTINGS_CORE) + if (menu_type == RGUI_SETTINGS_CORE || menu_type == RGUI_SETTINGS_DEFERRED_CORE) { if (type == RGUI_FILE_PLAIN) { @@ -377,10 +378,7 @@ static void render_text(rgui_handle_t *rgui) w = 5; } } - else -#endif - if (menu_type == RGUI_SETTINGS_CORE || - menu_type == RGUI_SETTINGS_CONFIG || + else if (menu_type == RGUI_SETTINGS_CONFIG || #ifdef HAVE_OVERLAY menu_type == RGUI_SETTINGS_OVERLAY_PRESET || #endif @@ -568,6 +566,7 @@ static void render_text(rgui_handle_t *rgui) strlcpy(type_str, "", sizeof(type_str)); break; case RGUI_SETTINGS_OPEN_FILEBROWSER: + case RGUI_SETTINGS_OPEN_FILEBROWSER_DEFERRED_CORE: case RGUI_SETTINGS_OPEN_HISTORY: case RGUI_SETTINGS_CORE_OPTIONS: case RGUI_SETTINGS_CUSTOM_VIEWPORT: