diff --git a/Makefile b/Makefile index 6011d5a5f5..08fd696eee 100644 --- a/Makefile +++ b/Makefile @@ -22,6 +22,7 @@ OBJ = frontend/frontend.o \ core_options.o \ compat/compat.o \ cheats.o \ + core_info.o \ conf/config_file.o \ screenshot.o \ gfx/scaler/scaler.o \ diff --git a/Makefile.win b/Makefile.win index 21570b6593..41cf028f4f 100644 --- a/Makefile.win +++ b/Makefile.win @@ -23,6 +23,7 @@ OBJ = frontend/frontend.o \ compat/compat.o \ screenshot.o \ cheats.o \ + core_info.o \ audio/utils.o \ input/overlay.o \ fifo_buffer.o \ diff --git a/apple/common/RAModuleInfo.m b/apple/common/RAModuleInfo.m index 581bd0d116..8a1cbf6984 100644 --- a/apple/common/RAModuleInfo.m +++ b/apple/common/RAModuleInfo.m @@ -26,7 +26,7 @@ NSArray* apple_get_modules() { if (!moduleList) { - coreList = get_core_info_list(apple_platform.coreDirectory.UTF8String); + coreList = core_info_list_new(apple_platform.coreDirectory.UTF8String); if (!coreList) return nil; @@ -182,4 +182,4 @@ static NSString* build_string_pair(NSString* stringA, NSString* stringB) @end -#endif \ No newline at end of file +#endif diff --git a/blackberry-qnx/bb10/src/RetroArch-Cascades.cpp b/blackberry-qnx/bb10/src/RetroArch-Cascades.cpp index e57e8a68ad..c2e14a16dc 100644 --- a/blackberry-qnx/bb10/src/RetroArch-Cascades.cpp +++ b/blackberry-qnx/bb10/src/RetroArch-Cascades.cpp @@ -87,7 +87,7 @@ RetroArch::RetroArch() //Get core DropDown reference to populate it in C++ coreSelection = mAppPane->findChild("dropdown_core"); connect(coreSelection, SIGNAL(selectedValueChanged(QVariant)), this, SLOT(onCoreSelected(QVariant))); - core_info_list = get_core_info_list(g_settings.libretro); + core_info_list = core_info_list_new(g_settings.libretro); populateCores(core_info_list); Application::instance()->setScene(mAppPane); @@ -112,7 +112,7 @@ RetroArch::RetroArch() RetroArch::~RetroArch() { - free_core_info_list(core_info_list); + core_info_list_free(core_info_list); } void RetroArch::aboutToQuit() diff --git a/core_info.c b/core_info.c index 6d3e1f0657..5be6ade2dd 100644 --- a/core_info.c +++ b/core_info.c @@ -17,55 +17,58 @@ #include "general.h" #include "file.h" #include "file_ext.h" +#include "file_extract.h" #include "config.def.h" -core_info_list_t *get_core_info_list(const char *modules_path) +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +core_info_list_t *core_info_list_new(const char *modules_path) { struct string_list *contents = dir_list_new(modules_path, EXT_EXECUTABLES, false); - core_info_t *core_info; - core_info_list_t *core_info_list; - unsigned i; + core_info_t *core_info = NULL; + core_info_list_t *core_info_list = NULL; if (!contents) return NULL; - core_info = (core_info_t*)malloc(contents->size * sizeof(core_info_t)); - memset(core_info, 0, contents->size * sizeof(core_info_t)); + core_info_list = (core_info_list_t*)calloc(1, sizeof(*core_info_list)); + if (!core_info_list) + goto error; + + core_info = (core_info_t*)calloc(contents->size, sizeof(*core_info)); + if (!core_info) + goto error; - core_info_list = (core_info_list_t*)malloc(sizeof(core_info_list_t)); - memset(core_info_list, 0, sizeof(core_info_list_t)); core_info_list->list = core_info; core_info_list->count = contents->size; - for (i = 0; i < contents->size; i ++) + for (size_t i = 0; i < contents->size; i++) { - char buffer[PATH_MAX]; char info_path[PATH_MAX]; - char *substr; - core_info[i].path = strdup(contents->elems[i].data); + if (!core_info[i].path) + break; - // NOTE: This assumes all modules are named module_name_{tag}.ext - // {tag} must not contain an underscore. (This isn't true for PC versions) - snprintf(buffer, PATH_MAX, "%s", contents->elems[i].data); - substr = strrchr(buffer, '_'); +#if defined(IOS) || defined(HAVE_BB10) || defined(__QNX__) + // Libs are deployed with a suffix (*_ios.dylib, *_qnx.so, etc). + char buffer[PATH_MAX]; + strlcpy(buffer, contents->elems[i].data, sizeof(buffer)); + char *substr = strrchr(buffer, '_'); if (substr) - *substr = 0; - - // NOTE: Can't just use fill_pathname on iOS as it will cut at RetroArch.app; - // perhaps fill_pathname shouldn't cut before the last path element. - if (substr) - snprintf(info_path, PATH_MAX, "%s.info", buffer); - else - fill_pathname(info_path, buffer, ".info", PATH_MAX); + *substr = '\0'; + fill_pathname(info_path, buffer, ".info", sizeof(info_path)); +#else + fill_pathname(info_path, core_info[i].path, ".info", sizeof(info_path)); +#endif core_info[i].data = config_file_new(info_path); if (core_info[i].data) { config_get_string(core_info[i].data, "display_name", &core_info[i].display_name); - if (config_get_string(core_info[i].data, "supported_extensions", &core_info[i].supported_extensions) && core_info[i].supported_extensions) core_info[i].supported_extensions_list = string_split(core_info[i].supported_extensions, "|"); @@ -75,19 +78,48 @@ core_info_list_t *get_core_info_list(const char *modules_path) core_info[i].display_name = strdup(path_basename(core_info[i].path)); } - dir_list_free(contents); + 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) + { + all_ext_len += strlen("|zip"); + 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); + } + } + strlcat(core_info_list->all_ext, "|zip", all_ext_len); + } + + dir_list_free(contents); return core_info_list; + +error: + if (contents) + dir_list_free(contents); + core_info_list_free(core_info_list); + return NULL; } -void free_core_info_list(core_info_list_t *core_info_list) +void core_info_list_free(core_info_list_t *core_info_list) { - int i; - if (!core_info_list) return; - for (i = 0; i < core_info_list->count; i++) + for (size_t i = 0; i < core_info_list->count; i++) { free(core_info_list->list[i].path); free(core_info_list->list[i].display_name); @@ -96,11 +128,38 @@ void free_core_info_list(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 does_core_support_file(core_info_t* core, const char *path) +bool core_info_list_get_display_name(core_info_list_t *core_info_list, const char *path, char *buf, size_t size) +{ + for (size_t i = 0; i < core_info_list->count; i++) + { + const core_info_t *info = &core_info_list->list[i]; + if (!strcmp(info->path, path) && info->display_name) + { + strlcpy(buf, info->display_name, size); + return true; + } + } + + return false; +} + +bool core_info_does_support_any_file(const core_info_t *core, const struct string_list *list) +{ + if (!list || !core || !core->supported_extensions_list) + return false; + + for (size_t i = 0; i < list->size; i++) + if (string_list_find_elem_prefix(core->supported_extensions_list, ".", path_get_extension(list->elems[i].data))) + return true; + return false; +} + +bool core_info_does_support_file(const core_info_t *core, const char *path) { if (!path || !core || !core->supported_extensions_list) return false; @@ -108,3 +167,60 @@ bool does_core_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; +} + +// qsort_r() is not in standard C, sadly. +static const char *core_info_tmp_path; +static const struct string_list *core_info_tmp_list; + +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_any_file(a, core_info_tmp_list) || + core_info_does_support_file(a, core_info_tmp_path); + int support_b = core_info_does_support_any_file(b, core_info_tmp_list) || + 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; + +#ifdef HAVE_ZLIB + struct string_list *list = NULL; + if (!strcasecmp(path_get_extension(path), "zip")) + list = zlib_get_file_list(path); + core_info_tmp_list = list; +#endif + + // Let supported core come first in list so we can return a pointer to them. + 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++) + { + const core_info_t *core = &core_info_list->list[i]; + if (!core_info_does_support_file(core, path) && !core_info_does_support_any_file(core, list)) + break; + } + +#ifdef HAVE_ZLIB + if (list) + string_list_free(list); +#endif + + *infos = core_info_list->list; + *num_infos = supported; +} + diff --git a/core_info.h b/core_info.h index 28f24ec9d5..0b25ac56f8 100644 --- a/core_info.h +++ b/core_info.h @@ -16,29 +16,41 @@ #ifndef CORE_INFO_H_ #define CORE_INFO_H_ +#include "conf/config_file.h" +#include "file.h" +#include + #ifdef __cplusplus extern "C" { #endif -#include "conf/config_file.h" - typedef struct { - char * path; - config_file_t* data; - char * display_name; - char * supported_extensions; - struct string_list * supported_extensions_list; + char *path; + config_file_t *data; + char *display_name; + char *supported_extensions; + struct string_list *supported_extensions_list; } core_info_t; typedef struct { core_info_t *list; - int count; + size_t count; + char *all_ext; } core_info_list_t; -core_info_list_t *get_core_info_list(const char *modules_path); -void free_core_info_list(core_info_list_t * core_info_list); +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 does_core_support_file(core_info_t* core, const char *path); +bool core_info_does_support_file(const core_info_t *core, const char *path); +bool core_info_does_support_any_file(const core_info_t *core, const struct string_list *list); + +// 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); + +bool core_info_list_get_display_name(core_info_list_t *core_info_list, const char *path, char *buf, size_t size); #ifdef __cplusplus } diff --git a/file.h b/file.h index 24e1cd9519..854c515373 100644 --- a/file.h +++ b/file.h @@ -68,15 +68,20 @@ void dir_list_free(struct string_list *list); bool string_list_find_elem(const struct string_list *list, const char *elem); bool string_list_find_elem_prefix(const struct string_list *list, const char *prefix, const char *elem); struct string_list *string_split(const char *str, const char *delim); +struct string_list *string_list_new(void); +bool string_list_append(struct string_list *list, const char *elem, union string_list_elem_attr attr); void string_list_free(struct string_list *list); bool path_is_directory(const char *path); bool path_file_exists(const char *path); + +// Gets extension of file. Only '.'s after the last slash are considered. const char *path_get_extension(const char *path); bool path_mkdir(const char *dir); -// Removes all text after and including the last '.' +// Removes all text after and including the last '.'. +// Only '.'s after the last slash are considered. char *path_remove_extension(char *path); // Returns basename from path. @@ -100,6 +105,7 @@ bool path_is_absolute(const char *path); // Replaces filename extension with 'replace' and outputs result to out_path. // The extension here is considered to be the string from the last '.' to the end. +// Only '.'s after the last slash are considered as extensions. // If no '.' is present, in_path and replace will simply be concatenated. // 'size' is buffer size of 'out_path'. // E.g.: in_path = "/foo/bar/baz/boo.c", replace = ".asm" => out_path = "/foo/bar/baz/boo.asm" diff --git a/file_extract.c b/file_extract.c index 74a9b5768d..3bb8dfbd47 100644 --- a/file_extract.c +++ b/file_extract.c @@ -18,6 +18,7 @@ #include "compat/strl.h" #include #include +#include #ifdef WANT_MINIZ #include "deps/miniz/zlib.h" @@ -46,7 +47,7 @@ static uint32_t read_le(const uint8_t *data, unsigned size) return val; } -static bool inflate_data_to_file(const char *path, uint8_t *cdata, +static bool inflate_data_to_file(const char *path, const uint8_t *cdata, uint32_t csize, uint32_t size, uint32_t crc32) { bool ret = true; @@ -60,7 +61,7 @@ static bool inflate_data_to_file(const char *path, uint8_t *cdata, if (inflateInit2(&stream, -MAX_WBITS) != Z_OK) GOTO_END_ERROR(); - stream.next_in = cdata; + stream.next_in = (uint8_t*)cdata; stream.avail_in = csize; stream.next_out = out_data; stream.avail_out = size; @@ -85,24 +86,14 @@ end: return ret; } -bool zlib_extract_first_rom(char *zip_path, size_t zip_path_size, const char *valid_exts) +bool zlib_parse_file(const char *file, zlib_file_cb file_cb, void *userdata) { const uint8_t *footer = NULL; const uint8_t *directory = NULL; bool ret = true; - if (!valid_exts) - { - RARCH_ERR("Libretro implementation does not have any valid extensions. Cannot unzip without knowing this.\n"); - return false; - } - - struct string_list *list = string_split(valid_exts, "|"); - if (!list) - return false; - uint8_t *data = NULL; - ssize_t zip_size = read_file(zip_path, (void**)&data); + ssize_t zip_size = read_file(file, (void**)&data); if (zip_size < 22) GOTO_END_ERROR(); @@ -150,44 +141,126 @@ bool zlib_extract_first_rom(char *zip_path, size_t zip_path_size, const char *va RARCH_LOG("OFFSET: %u, CSIZE: %u, SIZE: %u.\n", offset + 30 + offsetNL + offsetEL, csize, size); - // Extract first ROM that matches our list. - const char *ext = path_get_extension(filename); - if (ext && string_list_find_elem(list, ext)) - { - char new_path[PATH_MAX]; - fill_pathname_resolve_relative(new_path, zip_path, - path_basename(filename), sizeof(new_path)); - - switch (cmode) - { - case 0: // Uncompressed - if (!write_file(new_path, cdata, size)) - GOTO_END_ERROR(); - goto end; - - case 8: // Deflate - if (inflate_data_to_file(new_path, cdata, csize, size, crc32)) - { - strlcpy(zip_path, new_path, zip_path_size); - goto end; - } - else - GOTO_END_ERROR(); - - default: - GOTO_END_ERROR(); - } - } + if (!file_cb(filename, cdata, cmode, csize, size, crc32, userdata)) + break; directory += 46 + namelength + extralength + commentlength; } - RARCH_ERR("Didn't find any ROMS that matched valid extensions for libretro implementation.\n"); - GOTO_END_ERROR(); - end: free(data); - string_list_free(list); return ret; } +struct zip_extract_userdata +{ + char *zip_path; + size_t zip_path_size; + struct string_list *ext; + bool found_rom; +}; + +static bool zip_extract_cb(const char *name, const uint8_t *cdata, unsigned cmode, uint32_t csize, uint32_t size, + uint32_t crc32, void *userdata) +{ + struct zip_extract_userdata *data = (struct zip_extract_userdata*)userdata; + + // Extract first ROM that matches our list. + const char *ext = path_get_extension(name); + if (ext && string_list_find_elem(data->ext, ext)) + { + char new_path[PATH_MAX]; + fill_pathname_resolve_relative(new_path, data->zip_path, + path_basename(name), sizeof(new_path)); + + switch (cmode) + { + case 0: // Uncompressed + data->found_rom = write_file(new_path, cdata, size); + return false; + + case 8: // Deflate + if (inflate_data_to_file(new_path, cdata, csize, size, crc32)) + { + strlcpy(data->zip_path, new_path, data->zip_path_size); + data->found_rom = true; + return false; + } + else + return false; + + default: + return false; + } + } + + return true; +} + +bool zlib_extract_first_rom(char *zip_path, size_t zip_path_size, const char *valid_exts) +{ + if (!valid_exts) + { + RARCH_ERR("Libretro implementation does not have any valid extensions. Cannot unzip without knowing this.\n"); + return false; + } + + bool ret = true; + struct string_list *list = string_split(valid_exts, "|"); + if (!list) + GOTO_END_ERROR(); + + struct zip_extract_userdata userdata = {0}; + userdata.zip_path = zip_path; + userdata.zip_path_size = zip_path_size; + userdata.ext = list; + + if (!zlib_parse_file(zip_path, zip_extract_cb, &userdata)) + { + RARCH_ERR("Parsing ZIP failed.\n"); + GOTO_END_ERROR(); + } + + if (!userdata.found_rom) + { + RARCH_ERR("Didn't find any ROMS that matched valid extensions for libretro implementation.\n"); + GOTO_END_ERROR(); + } + +end: + if (list) + string_list_free(list); + return ret; +} + +static bool zlib_get_file_list_cb(const char *path, const uint8_t *cdata, unsigned cmode, + uint32_t csize, uint32_t size, + uint32_t crc32, void *userdata) +{ + (void)cdata; + (void)cmode; + (void)csize; + (void)size; + (void)crc32; + struct string_list *list = (struct string_list*)userdata; + union string_list_elem_attr attr; + memset(&attr, 0, sizeof(attr)); + return string_list_append(list, path, attr); +} + +struct string_list *zlib_get_file_list(const char *path) +{ + struct string_list *list = string_list_new(); + if (!list) + return NULL; + + if (!zlib_parse_file(path, zlib_get_file_list_cb, list)) + { + RARCH_ERR("Parsing ZIP failed.\n"); + string_list_free(list); + return NULL; + } + else + return list; +} + diff --git a/file_extract.h b/file_extract.h index 8c07e14604..460d23a14d 100644 --- a/file_extract.h +++ b/file_extract.h @@ -17,9 +17,21 @@ #define FILE_EXTRACT_H__ #include "boolean.h" +#include "file.h" #include +#include +// Returns true when parsing should continue. False to stop. +typedef bool (*zlib_file_cb)(const char *name, + const uint8_t *cdata, unsigned cmode, uint32_t csize, uint32_t size, + uint32_t crc32, void *userdata); + +// Low-level file parsing. Enumerates over all files and calls file_cb with userdata. +bool zlib_parse_file(const char *file, zlib_file_cb file_cb, void *userdata); + +// Built with zlib_parse_file. bool zlib_extract_first_rom(char *zip_path, size_t zip_path_size, const char *valid_exts); +struct string_list *zlib_get_file_list(const char *path); #endif diff --git a/file_path.c b/file_path.c index 35c1e5d142..a535cc21ae 100644 --- a/file_path.c +++ b/file_path.c @@ -79,7 +79,7 @@ static bool string_list_capacity(struct string_list *list, size_t cap) return true; } -static struct string_list *string_list_new(void) +struct string_list *string_list_new(void) { struct string_list *list = (struct string_list*)calloc(1, sizeof(*list)); if (!list) @@ -94,7 +94,7 @@ static struct string_list *string_list_new(void) return list; } -static bool string_list_append(struct string_list *list, const char *elem, union string_list_elem_attr attr) +bool string_list_append(struct string_list *list, const char *elem, union string_list_elem_attr attr) { if (list->size >= list->cap && !string_list_capacity(list, list->cap * 2)) @@ -180,7 +180,7 @@ bool string_list_find_elem_prefix(const struct string_list *list, const char *pr const char *path_get_extension(const char *path) { - const char *ext = strrchr(path, '.'); + const char *ext = strrchr(path_basename(path), '.'); if (ext) return ext + 1; else @@ -189,7 +189,7 @@ const char *path_get_extension(const char *path) char *path_remove_extension(char *path) { - char *last = strrchr(path, '.'); + char *last = strrchr(path_basename(path), '.'); if (*last) *last = '\0'; return last; @@ -417,7 +417,7 @@ void fill_pathname(char *out_path, const char *in_path, const char *replace, siz char tmp_path[PATH_MAX]; rarch_assert(strlcpy(tmp_path, in_path, sizeof(tmp_path)) < sizeof(tmp_path)); - char *tok = strrchr(tmp_path, '.'); + char *tok = strrchr(path_basename(tmp_path), '.'); if (tok) *tok = '\0'; diff --git a/frontend/menu/menu_common.c b/frontend/menu/menu_common.c index 42e111b689..f3930c33c2 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); } @@ -931,3 +937,37 @@ void menu_key_event(bool down, unsigned keycode, uint32_t character, uint16_t ke (void)key_modifiers; } +void menu_resolve_libretro_names(rgui_list_t *list, const char *dir) +{ + for (size_t i = 0; i < list->size; i++) + { + const char *path; + unsigned type = 0; + rgui_list_get_at_offset(list, i, &path, &type); + if (type != RGUI_FILE_PLAIN) + continue; + + char core_path[PATH_MAX]; + fill_pathname_join(core_path, dir, path, sizeof(core_path)); + + char display_name[256]; + if (rgui->core_info && + core_info_list_get_display_name(rgui->core_info, + core_path, display_name, sizeof(display_name))) + rgui_list_set_alt_at_offset(list, i, display_name); + } +} + +void menu_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); + } +} + + diff --git a/frontend/menu/menu_common.h b/frontend/menu/menu_common.h index f0aaaf9621..45ca989554 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 @@ -99,8 +100,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, @@ -290,6 +293,10 @@ typedef struct bool msg_force; bool push_start_screen; + 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]; @@ -346,6 +353,9 @@ void shader_manager_set_preset(struct gfx_shader *shader, void menu_ticker_line(char *buf, size_t len, unsigned tick, const char *str, bool selected); +void menu_resolve_libretro_names(rgui_list_t *list, const char *dir); +void menu_resolve_supported_cores(rgui_handle_t *rgui); + void load_menu_game_prepare(void); bool load_menu_game(void); void load_menu_game_history(unsigned game_index); diff --git a/frontend/menu/menu_settings.c b/frontend/menu/menu_settings.c index 0c2d39fbc1..c291068e89 100644 --- a/frontend/menu/menu_settings.c +++ b/frontend/menu/menu_settings.c @@ -565,7 +565,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 4048fd1118..fd82ee5617 100644 --- a/frontend/menu/rgui.c +++ b/frontend/menu/rgui.c @@ -203,32 +203,6 @@ static int rgui_core_setting_toggle(unsigned setting, rgui_action_t action) return 0; } -static void rgui_resolve_libretro_names(rgui_list_t *list, const char *dir) -{ - for (size_t i = 0; i < list->size; i++) - { - const char *path; - unsigned type = 0; - rgui_list_get_at_offset(list, i, &path, &type); - if (type != RGUI_FILE_PLAIN) - continue; - - char core_path[PATH_MAX]; - fill_pathname_join(core_path, dir, path, sizeof(core_path)); - char info_path[PATH_MAX]; - fill_pathname(info_path, core_path, ".info", sizeof(info_path)); - - config_file_t *conf = config_file_new(info_path); - if (!conf) - continue; - - char display_name[256]; - if (config_get_array(conf, "display_name", display_name, sizeof(display_name))) - rgui_list_set_alt_at_offset(list, i, display_name); - config_file_free(conf); - } -} - static int rgui_settings_toggle_setting(rgui_handle_t *rgui, unsigned setting, rgui_action_t action, unsigned menu_type) { #ifdef HAVE_SHADER_MANAGER @@ -279,7 +253,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->core_info->count) + rgui_list_push(rgui->selection_buf, "Load Game (Detect 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); @@ -866,8 +851,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; @@ -1140,6 +1127,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; @@ -1314,7 +1303,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)); @@ -1422,6 +1427,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) @@ -1442,12 +1451,48 @@ 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, rgui->selection_ptr); + rgui->selection_ptr = 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; @@ -1477,6 +1522,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 || @@ -1488,11 +1534,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); + menu_resolve_libretro_names(rgui->selection_buf, dir); + else if (menu_type == RGUI_SETTINGS_DEFERRED_CORE) + menu_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 bf375151f6..e25b499c4c 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) @@ -292,13 +294,17 @@ static void render_text(rgui_handle_t *rgui) snprintf(title, sizeof(title), "SYSTEM DIR %s", dir); else { - const char *core_name = rgui->info.library_name; - if (!core_name) - core_name = g_extern.system.info.library_name; - if (!core_name) - core_name = "No Core"; - - snprintf(title, sizeof(title), "GAME (%s) %s", core_name, dir); + if (rgui->defer_core) + snprintf(title, sizeof(title), "GAME %s", dir); + else + { + const char *core_name = rgui->info.library_name; + if (!core_name) + core_name = g_extern.system.info.library_name; + if (!core_name) + core_name = "No Core"; + snprintf(title, sizeof(title), "GAME (%s) %s", core_name, dir); + } } char title_buf[256]; @@ -361,9 +367,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) { @@ -378,10 +383,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 @@ -569,6 +571,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: