diff --git a/config.def.h b/config.def.h index fcecd91b55..49f9035842 100644 --- a/config.def.h +++ b/config.def.h @@ -243,6 +243,15 @@ #endif #define DEFAULT_CHECK_FIRMWARE_BEFORE_LOADING false +/* Specifies whether to cache core info + * into a single (compressed) file for improved + * load times on platforms with slow IO */ +#if defined(_3DS) +#define DEFAULT_CORE_INFO_CACHE_ENABLE true +#else +#define DEFAULT_CORE_INFO_CACHE_ENABLE false +#endif + /* Specifies whether to 'reload' (fork and quit) * RetroArch when launching content with the * currently loaded core diff --git a/configuration.c b/configuration.c index 0ef261d057..ef622c9c9c 100644 --- a/configuration.c +++ b/configuration.c @@ -1485,6 +1485,7 @@ static struct config_bool_setting *populate_settings_bool( SETTING_BOOL("input_descriptor_hide_unbound", &settings->bools.input_descriptor_hide_unbound, true, input_descriptor_hide_unbound, false); SETTING_BOOL("load_dummy_on_core_shutdown", &settings->bools.load_dummy_on_core_shutdown, true, DEFAULT_LOAD_DUMMY_ON_CORE_SHUTDOWN, false); SETTING_BOOL("check_firmware_before_loading", &settings->bools.check_firmware_before_loading, true, DEFAULT_CHECK_FIRMWARE_BEFORE_LOADING, false); + SETTING_BOOL("core_info_cache_enable", &settings->bools.core_info_cache_enable, true, DEFAULT_CORE_INFO_CACHE_ENABLE, false); #ifndef HAVE_DYNAMIC SETTING_BOOL("always_reload_core_on_run_content", &settings->bools.always_reload_core_on_run_content, true, DEFAULT_ALWAYS_RELOAD_CORE_ON_RUN_CONTENT, false); #endif diff --git a/configuration.h b/configuration.h index c5b715b705..3dcdf9a8ee 100644 --- a/configuration.h +++ b/configuration.h @@ -777,6 +777,7 @@ typedef struct settings bool network_remote_enable_user[MAX_USERS]; bool load_dummy_on_core_shutdown; bool check_firmware_before_loading; + bool core_info_cache_enable; #ifndef HAVE_DYNAMIC bool always_reload_core_on_run_content; #endif diff --git a/core_info.c b/core_info.c index f40fd037fa..721c759aa8 100644 --- a/core_info.c +++ b/core_info.c @@ -15,11 +15,14 @@ * If not, see . */ +#include #include #include #include #include #include +#include +#include #include #include @@ -28,6 +31,7 @@ #endif #include "retroarch.h" +#include "verbosity.h" #include "core_info.h" #include "file_path_special.h" @@ -40,23 +44,1108 @@ #include "play_feature_delivery/play_feature_delivery.h" #endif -#define CORE_INFO_CACHE_FILE_NAME ".cache" +/*************************/ +/* Core Info Cache START */ +/*************************/ + +#define CORE_INFO_CACHE_DEFAULT_CAPACITY 8 typedef struct { + core_info_t *items; + size_t length; + size_t capacity; + bool refresh; +} core_info_cache_list_t; + +typedef struct +{ + core_info_t *core_info; + core_info_cache_list_t *core_info_cache_list; char **current_string_val; struct string_list **current_string_list_val; uint32_t *current_entry_uint_val; bool *current_entry_bool_val; - bool to_core_file_id; - bool to_firmware; - core_info_t *core_info; - core_info_cache_list_t *core_info_cache_list; - unsigned array_depth; unsigned object_depth; + bool to_core_file_id; + bool to_firmware; } CCJSONContext; +/* Forward declarations */ +static void core_info_free(core_info_t* info); +static uint32_t core_info_hash_string(const char *str); +static core_info_cache_list_t *core_info_cache_list_new(void); +static void core_info_cache_add(core_info_cache_list_t *list, core_info_t *info, + bool transfer); + +/* JSON Handlers START */ + +static bool CCJSONObjectMemberHandler(void *context, const char *pValue, size_t length) +{ + CCJSONContext *pCtx = (CCJSONContext *)context; + + if ((pCtx->object_depth == 2) && (pCtx->array_depth == 1) && length) + { + pCtx->current_string_val = NULL; + pCtx->current_string_list_val = NULL; + pCtx->current_entry_uint_val = NULL; + pCtx->current_entry_bool_val = NULL; + pCtx->to_core_file_id = false; + pCtx->to_firmware = false; + + switch (pValue[0]) + { + case 'a': + if (string_is_equal(pValue, "authors")) + { + pCtx->current_string_val = &pCtx->core_info->authors; + pCtx->current_string_list_val = &pCtx->core_info->authors_list; + } + break; + case 'c': + if (string_is_equal(pValue, "categories")) + { + pCtx->current_string_val = &pCtx->core_info->categories; + pCtx->current_string_list_val = &pCtx->core_info->categories_list; + } + else if (string_is_equal(pValue, "core_name")) + pCtx->current_string_val = &pCtx->core_info->core_name; + else if (string_is_equal(pValue, "core_file_id")) + pCtx->to_core_file_id = true; + break; + case 'd': + if (string_is_equal(pValue, "display_name")) + pCtx->current_string_val = &pCtx->core_info->display_name; + else if (string_is_equal(pValue, "display_version")) + pCtx->current_string_val = &pCtx->core_info->display_version; + else if (string_is_equal(pValue, "databases")) + { + pCtx->current_string_val = &pCtx->core_info->databases; + pCtx->current_string_list_val = &pCtx->core_info->databases_list; + } + else if (string_is_equal(pValue, "description")) + pCtx->current_string_val = &pCtx->core_info->description; + else if (string_is_equal(pValue, "database_match_archive_member")) + pCtx->current_entry_bool_val = &pCtx->core_info->database_match_archive_member; + break; + case 'f': + if (string_is_equal(pValue, "firmware")) + pCtx->to_firmware = true; + break; + case 'h': + if (string_is_equal(pValue, "has_info")) + pCtx->current_entry_bool_val = &pCtx->core_info->has_info; + break; + case 'l': + if (string_is_equal(pValue, "licenses")) + { + pCtx->current_string_val = &pCtx->core_info->licenses; + pCtx->current_string_list_val = &pCtx->core_info->licenses_list; + } + else if (string_is_equal(pValue, "is_experimental")) + pCtx->current_entry_bool_val = &pCtx->core_info->is_experimental; + break; + case 'n': + if (string_is_equal(pValue, "notes")) + { + pCtx->current_string_val = &pCtx->core_info->notes; + pCtx->current_string_list_val = &pCtx->core_info->note_list; + } + break; + case 'p': + if (string_is_equal(pValue, "path")) + pCtx->current_string_val = &pCtx->core_info->path; + else if (string_is_equal(pValue, "permissions")) + { + pCtx->current_string_val = &pCtx->core_info->permissions; + pCtx->current_string_list_val = &pCtx->core_info->permissions_list; + } + break; + case 'r': + if (string_is_equal(pValue, "required_hw_api")) + { + pCtx->current_string_val = &pCtx->core_info->required_hw_api; + pCtx->current_string_list_val = &pCtx->core_info->required_hw_api_list; + } + break; + case 's': + if (string_is_equal(pValue, "system_manufacturer")) + pCtx->current_string_val = &pCtx->core_info->system_manufacturer; + else if (string_is_equal(pValue, "systemname")) + pCtx->current_string_val = &pCtx->core_info->systemname; + else if (string_is_equal(pValue, "system_id")) + pCtx->current_string_val = &pCtx->core_info->system_id; + else if (string_is_equal(pValue, "supported_extensions")) + { + pCtx->current_string_val = &pCtx->core_info->supported_extensions; + pCtx->current_string_list_val = &pCtx->core_info->supported_extensions_list; + } + else if (string_is_equal(pValue, "supports_no_game")) + pCtx->current_entry_bool_val = &pCtx->core_info->supports_no_game; + break; + } + } + else if ((pCtx->object_depth == 3) && (pCtx->array_depth == 1) && length) + { + pCtx->current_string_val = NULL; + pCtx->current_entry_uint_val = NULL; + + if (pCtx->to_core_file_id) + { + if (string_is_equal(pValue, "str")) + pCtx->current_string_val = &pCtx->core_info->core_file_id.str; + else if (string_is_equal(pValue, "hash")) + pCtx->current_entry_uint_val = &pCtx->core_info->core_file_id.hash; + } + } + else if ((pCtx->object_depth == 3) && (pCtx->array_depth == 2) && length) + { + pCtx->current_string_val = NULL; + pCtx->current_entry_bool_val = NULL; + + if (pCtx->to_firmware && (pCtx->core_info->firmware_count > 0)) + { + size_t firmware_idx = pCtx->core_info->firmware_count - 1; + + if (string_is_equal(pValue, "path")) + pCtx->current_string_val = &pCtx->core_info->firmware[firmware_idx].path; + else if (string_is_equal(pValue, "desc")) + pCtx->current_string_val = &pCtx->core_info->firmware[firmware_idx].desc; + else if (string_is_equal(pValue, "optional")) + pCtx->current_entry_bool_val = &pCtx->core_info->firmware[firmware_idx].optional; + } + } + + return true; +} + +static bool CCJSONStringHandler(void *context, const char *pValue, size_t length) +{ + CCJSONContext *pCtx = (CCJSONContext*)context; + + if (pCtx->current_string_val && length && !string_is_empty(pValue)) + { + if (*pCtx->current_string_val) + free(*pCtx->current_string_val); + *pCtx->current_string_val = strdup(pValue); + + if (pCtx->current_string_list_val) + { + if (*pCtx->current_string_list_val) + string_list_free(*pCtx->current_string_list_val); + *pCtx->current_string_list_val = string_split(*pCtx->current_string_val, "|"); + } + } + + pCtx->current_string_val = NULL; + pCtx->current_string_list_val = NULL; + + return true; +} + +static bool CCJSONNumberHandler(void *context, const char *pValue, size_t length) +{ + CCJSONContext *pCtx = (CCJSONContext*)context; + + if (pCtx->current_entry_uint_val) + *pCtx->current_entry_uint_val = string_to_unsigned(pValue); + + pCtx->current_entry_uint_val = NULL; + + return true; +} + +static bool CCJSONBoolHandler(void *context, bool value) +{ + CCJSONContext *pCtx = (CCJSONContext *)context; + + if (pCtx->current_entry_bool_val) + *pCtx->current_entry_bool_val = value; + + pCtx->current_entry_bool_val = NULL; + + return true; +} + +static bool CCJSONStartObjectHandler(void *context) +{ + CCJSONContext *pCtx = (CCJSONContext*)context; + + pCtx->object_depth++; + + if ((pCtx->object_depth == 1) && (pCtx->array_depth == 0)) + { + if (pCtx->core_info_cache_list) + return false; + + pCtx->core_info_cache_list = core_info_cache_list_new(); + if (!pCtx->core_info_cache_list) + return false; + } + else if ((pCtx->object_depth == 2) && (pCtx->array_depth == 1)) + { + if (pCtx->core_info) + { + core_info_free(pCtx->core_info); + free(pCtx->core_info); + pCtx->core_info = NULL; + } + + pCtx->core_info = (core_info_t*)calloc(1, sizeof(core_info_t)); + if (!pCtx->core_info) + return false; + } + else if ((pCtx->object_depth == 3) && (pCtx->array_depth == 2)) + { + if (pCtx->to_firmware) + { + size_t new_idx = pCtx->core_info->firmware_count; + core_info_firmware_t *firmware_tmp = (core_info_firmware_t*) + realloc(pCtx->core_info->firmware, + (pCtx->core_info->firmware_count + 1) * sizeof(core_info_firmware_t)); + + if (!firmware_tmp) + return false; + + firmware_tmp[new_idx].path = NULL; + firmware_tmp[new_idx].desc = NULL; + firmware_tmp[new_idx].missing = false; + firmware_tmp[new_idx].optional = false; + + pCtx->core_info->firmware = firmware_tmp; + pCtx->core_info->firmware_count++; + } + } + + return true; +} + +static bool CCJSONEndObjectHandler(void *context) +{ + CCJSONContext *pCtx = (CCJSONContext*)context; + + if ((pCtx->object_depth == 2) && (pCtx->array_depth == 1) && pCtx->core_info) + { + core_info_cache_add(pCtx->core_info_cache_list, pCtx->core_info, true); + free(pCtx->core_info); + pCtx->core_info = NULL; + } + else if ((pCtx->object_depth == 3) && (pCtx->array_depth == 1)) + pCtx->to_core_file_id = false; + + retro_assert(pCtx->object_depth > 0); + pCtx->object_depth--; + + return true; +} + +static bool CCJSONStartArrayHandler(void *context) +{ + CCJSONContext *pCtx = (CCJSONContext*)context; + pCtx->array_depth++; + return true; +} + +static bool CCJSONEndArrayHandler(void *context) +{ + CCJSONContext *pCtx = (CCJSONContext*)context; + + if ((pCtx->object_depth == 2) && (pCtx->array_depth == 2)) + pCtx->to_firmware = false; + + retro_assert(pCtx->array_depth > 0); + pCtx->array_depth--; + + return true; +} + +/* JSON Handlers END */ + +/* Note: 'dst' must be zero initialised, or memory + * leaks will occur */ +static void core_info_copy(core_info_t *src, core_info_t *dst) +{ + dst->path = src->path ? strdup(src->path) : NULL; + dst->display_name = src->display_name ? strdup(src->display_name) : NULL; + dst->display_version = src->display_version ? strdup(src->display_version) : NULL; + dst->core_name = src->core_name ? strdup(src->core_name) : NULL; + dst->system_manufacturer = src->system_manufacturer ? strdup(src->system_manufacturer) : NULL; + dst->systemname = src->systemname ? strdup(src->systemname) : NULL; + dst->system_id = src->system_id ? strdup(src->system_id) : NULL; + dst->supported_extensions = src->supported_extensions ? strdup(src->supported_extensions) : NULL; + dst->authors = src->authors ? strdup(src->authors) : NULL; + dst->permissions = src->permissions ? strdup(src->permissions) : NULL; + dst->licenses = src->licenses ? strdup(src->licenses) : NULL; + dst->categories = src->categories ? strdup(src->categories) : NULL; + dst->databases = src->databases ? strdup(src->databases) : NULL; + dst->notes = src->notes ? strdup(src->notes) : NULL; + dst->required_hw_api = src->required_hw_api ? strdup(src->required_hw_api) : NULL; + dst->description = src->description ? strdup(src->description) : NULL; + + dst->categories_list = src->categories_list ? string_list_clone(src->categories_list) : NULL; + dst->databases_list = src->databases_list ? string_list_clone(src->databases_list) : NULL; + dst->note_list = src->note_list ? string_list_clone(src->note_list) : NULL; + dst->supported_extensions_list = src->supported_extensions_list ? string_list_clone(src->supported_extensions_list) : NULL; + dst->authors_list = src->authors_list ? string_list_clone(src->authors_list) : NULL; + dst->permissions_list = src->permissions_list ? string_list_clone(src->permissions_list) : NULL; + dst->licenses_list = src->licenses_list ? string_list_clone(src->licenses_list) : NULL; + dst->required_hw_api_list = src->required_hw_api_list ? string_list_clone(src->required_hw_api_list) : NULL; + + if (src->firmware_count > 0) + { + dst->firmware = (core_info_firmware_t*)calloc(src->firmware_count, + sizeof(core_info_firmware_t)); + + if (dst->firmware) + { + size_t i; + + dst->firmware_count = src->firmware_count; + + for (i = 0; i < src->firmware_count; i++) + { + dst->firmware[i].path = src->firmware[i].path ? strdup(src->firmware[i].path) : NULL; + dst->firmware[i].desc = src->firmware[i].desc ? strdup(src->firmware[i].desc) : NULL; + dst->firmware[i].missing = src->firmware[i].missing; + dst->firmware[i].optional = src->firmware[i].optional; + } + } + else + dst->firmware_count = 0; + } + + dst->core_file_id.str = src->core_file_id.str ? strdup(src->core_file_id.str) : NULL; + dst->core_file_id.hash = src->core_file_id.hash; + + dst->has_info = src->has_info; + dst->supports_no_game = src->supports_no_game; + dst->database_match_archive_member = src->database_match_archive_member; + dst->is_experimental = src->is_experimental; + dst->is_locked = src->is_locked; + dst->is_installed = src->is_installed; +} + +/* Like core_info_copy, but transfers 'ownership' + * of internal objects/data structures from 'src' + * to 'dst' */ +static void core_info_transfer(core_info_t *src, core_info_t *dst) +{ + dst->path = src->path; + src->path = NULL; + + dst->display_name = src->display_name; + src->display_name = NULL; + + dst->display_version = src->display_version; + src->display_version = NULL; + + dst->core_name = src->core_name; + src->core_name = NULL; + + dst->system_manufacturer = src->system_manufacturer; + src->system_manufacturer = NULL; + + dst->systemname = src->systemname; + src->systemname = NULL; + + dst->system_id = src->system_id; + src->system_id = NULL; + + dst->supported_extensions = src->supported_extensions; + src->supported_extensions = NULL; + + dst->authors = src->authors; + src->authors = NULL; + + dst->permissions = src->permissions; + src->permissions = NULL; + + dst->licenses = src->licenses; + src->licenses = NULL; + + dst->categories = src->categories; + src->categories = NULL; + + dst->databases = src->databases; + src->databases = NULL; + + dst->notes = src->notes; + src->notes = NULL; + + dst->required_hw_api = src->required_hw_api; + src->required_hw_api = NULL; + + dst->description = src->description; + src->description = NULL; + + dst->categories_list = src->categories_list; + src->categories_list = NULL; + + dst->databases_list = src->databases_list; + src->databases_list = NULL; + + dst->note_list = src->note_list; + src->note_list = NULL; + + dst->supported_extensions_list = src->supported_extensions_list; + src->supported_extensions_list = NULL; + + dst->authors_list = src->authors_list; + src->authors_list = NULL; + + dst->permissions_list = src->permissions_list; + src->permissions_list = NULL; + + dst->licenses_list = src->licenses_list; + src->licenses_list = NULL; + + dst->required_hw_api_list = src->required_hw_api_list; + src->required_hw_api_list = NULL; + + dst->firmware = src->firmware; + dst->firmware_count = src->firmware_count; + src->firmware = NULL; + src->firmware_count = 0; + + dst->core_file_id.str = src->core_file_id.str; + src->core_file_id.str = NULL; + dst->core_file_id.hash = src->core_file_id.hash; + + dst->has_info = src->has_info; + dst->supports_no_game = src->supports_no_game; + dst->database_match_archive_member = src->database_match_archive_member; + dst->is_experimental = src->is_experimental; + dst->is_locked = src->is_locked; + dst->is_installed = src->is_installed; +} + +static void core_info_cache_list_free(core_info_cache_list_t *core_info_cache_list) +{ + size_t i; + + if (!core_info_cache_list) + return; + + for (i = 0; i < core_info_cache_list->length; i++) + { + core_info_t* info = (core_info_t*)&core_info_cache_list->items[i]; + core_info_free(info); + } + + free(core_info_cache_list->items); + free(core_info_cache_list); +} + +static core_info_cache_list_t *core_info_cache_list_new(void) +{ + core_info_cache_list_t *core_info_cache_list = NULL; + + core_info_cache_list = (core_info_cache_list_t *)malloc(sizeof(*core_info_cache_list)); + if (!core_info_cache_list) + return NULL; + + core_info_cache_list->length = 0; + core_info_cache_list->items = (core_info_t *)calloc(CORE_INFO_CACHE_DEFAULT_CAPACITY, + sizeof(core_info_t)); + + if (!core_info_cache_list->items) + { + core_info_cache_list_free(core_info_cache_list); + return NULL; + } + + core_info_cache_list->capacity = CORE_INFO_CACHE_DEFAULT_CAPACITY; + core_info_cache_list->refresh = false; + + return core_info_cache_list; +} + +static core_info_t *core_info_cache_find(core_info_cache_list_t *list, char *core_file_id) +{ + uint32_t hash; + size_t i; + + if (!list || + string_is_empty(core_file_id)) + return NULL; + + hash = core_info_hash_string(core_file_id); + + for (i = 0; i < list->length; i++) + { + core_info_t *info = (core_info_t*)&list->items[i]; + + if (!info) + continue; + + if ((info->core_file_id.hash == hash) && + string_is_equal(info->core_file_id.str, core_file_id)) + { + info->is_installed = true; + return info; + } + } + + return NULL; +} + +static void core_info_cache_add(core_info_cache_list_t *list, core_info_t *info, + bool transfer) +{ + core_info_t *info_cache = NULL; + + if (!list || + !info || + (info->core_file_id.hash == 0) || + string_is_empty(info->core_file_id.str)) + return; + + if (list->length >= list->capacity) + { + size_t prev_capacity = list->capacity; + core_info_t *items_tmp = (core_info_t*)realloc(list->items, + (list->capacity << 1) * sizeof(core_info_t)); + + if (!items_tmp) + return; + + list->capacity = list->capacity << 1; + list->items = items_tmp; + + memset(&list->items[prev_capacity], 0, + (list->capacity - prev_capacity) * sizeof(core_info_t)); + } + + info_cache = (core_info_t*)&list->items[list->length]; + + if (transfer) + core_info_transfer(info, info_cache); + else + core_info_copy(info, info_cache); + + list->length++; +} + +static core_info_cache_list_t *core_info_cache_read(const char *info_dir) +{ + intfstream_t *file = NULL; + rjson_t *parser = NULL; + CCJSONContext context = {0}; + core_info_cache_list_t *core_info_cache_list = NULL; + char file_path[PATH_MAX_LENGTH]; + + /* Check whether a 'force refresh' file + * is present */ + file_path[0] = '\0'; + + if (string_is_empty(info_dir)) + strlcpy(file_path, FILE_PATH_CORE_INFO_CACHE_REFRESH, sizeof(file_path)); + else + fill_pathname_join(file_path, info_dir, FILE_PATH_CORE_INFO_CACHE_REFRESH, + sizeof(file_path)); + + if (path_is_valid(file_path)) + return core_info_cache_list_new(); + + /* Open info cache file */ + file_path[0] = '\0'; + + if (string_is_empty(info_dir)) + strlcpy(file_path, FILE_PATH_CORE_INFO_CACHE, sizeof(file_path)); + else + fill_pathname_join(file_path, info_dir, FILE_PATH_CORE_INFO_CACHE, + sizeof(file_path)); + +#if defined(HAVE_ZLIB) + file = intfstream_open_rzip_file(file_path, + RETRO_VFS_FILE_ACCESS_READ); +#else + file = intfstream_open_file(file_path, + RETRO_VFS_FILE_ACCESS_READ, + RETRO_VFS_FILE_ACCESS_HINT_NONE); +#endif + + if (!file) + return core_info_cache_list_new(); + + /* Parse info cache file */ + parser = rjson_open_stream(file); + if (!parser) + { + RARCH_ERR("[Core Info] Failed to create JSON parser\n"); + goto end; + } + + rjson_set_options(parser, + RJSON_OPTION_ALLOW_UTF8BOM + | RJSON_OPTION_ALLOW_COMMENTS + | RJSON_OPTION_ALLOW_UNESCAPED_CONTROL_CHARACTERS + | RJSON_OPTION_REPLACE_INVALID_ENCODING); + + if (rjson_parse(parser, &context, + CCJSONObjectMemberHandler, + CCJSONStringHandler, + CCJSONNumberHandler, + CCJSONStartObjectHandler, + CCJSONEndObjectHandler, + CCJSONStartArrayHandler, + CCJSONEndArrayHandler, + CCJSONBoolHandler, + NULL) /* Unused null handler */ + != RJSON_DONE) + { + RARCH_WARN("[Core Info] Error parsing chunk:\n---snip---\n%.*s\n---snip---\n", + rjson_get_source_context_len(parser), + rjson_get_source_context_buf(parser)); + RARCH_WARN("[Core Info] Error: Invalid JSON at line %d, column %d - %s.\n", + (int)rjson_get_source_line(parser), + (int)rjson_get_source_column(parser), + (*rjson_get_error(parser) ? rjson_get_error(parser) : "format error")); + + /* Info cache is corrupt - discard it */ + core_info_cache_list_free(context.core_info_cache_list); + core_info_cache_list = core_info_cache_list_new(); + } + else + core_info_cache_list = context.core_info_cache_list; + + rjson_free(parser); + + /* Clean up leftovers in the event of + * a parsing error */ + if (context.core_info) + { + core_info_free(context.core_info); + free(context.core_info); + } + +end: + intfstream_close(file); + free(file); + + return core_info_cache_list; +} + +static void core_info_cache_write(core_info_cache_list_t *list, const char *info_dir) +{ + intfstream_t *file = NULL; + rjsonwriter_t *writer = NULL; + char file_path[PATH_MAX_LENGTH]; + size_t i, j; + + file_path[0] = '\0'; + + if (!list) + return; + + /* Open info cache file */ + if (string_is_empty(info_dir)) + strlcpy(file_path, FILE_PATH_CORE_INFO_CACHE, sizeof(file_path)); + else + fill_pathname_join(file_path, info_dir, FILE_PATH_CORE_INFO_CACHE, + sizeof(file_path)); + + /* TODO/FIXME: Apparently rzip compression is an issue on UWP */ +#if defined(HAVE_ZLIB) && !(defined(__WINRT__) || defined(WINAPI_FAMILY) && WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP) + file = intfstream_open_rzip_file(file_path, + RETRO_VFS_FILE_ACCESS_WRITE); +#else + file = intfstream_open_file(file_path, + RETRO_VFS_FILE_ACCESS_WRITE, + RETRO_VFS_FILE_ACCESS_HINT_NONE); +#endif + + if (!file) + { + RARCH_ERR("[Core Info] Failed to write to core info cache file: %s\n", file_path); + return; + } + + /* Write info cache */ + writer = rjsonwriter_open_stream(file); + if (!writer) + { + RARCH_ERR("[Core Info] Failed to create JSON writer\n"); + goto end; + } + + rjsonwriter_add_start_object(writer); + rjsonwriter_add_newline(writer); + rjsonwriter_add_spaces(writer, 2); + rjsonwriter_add_string(writer, "version"); + rjsonwriter_add_colon(writer); + rjsonwriter_add_space(writer); + rjsonwriter_add_string(writer, "1.0"); + rjsonwriter_add_comma(writer); + rjsonwriter_add_newline(writer); + rjsonwriter_add_spaces(writer, 2); + rjsonwriter_add_string(writer, "items"); + rjsonwriter_add_colon(writer); + rjsonwriter_add_space(writer); + rjsonwriter_add_start_array(writer); + rjsonwriter_add_newline(writer); + + for (i = 0; i < list->length; i++) + { + core_info_t* info = &list->items[i]; + + if (!info || !info->is_installed) + continue; + + if (i > 0) + { + rjsonwriter_add_comma(writer); + rjsonwriter_add_newline(writer); + } + + rjsonwriter_add_spaces(writer, 4); + rjsonwriter_add_start_object(writer); + rjsonwriter_add_newline(writer); + + rjsonwriter_add_spaces(writer, 6); + rjsonwriter_add_string(writer, "path"); + rjsonwriter_add_colon(writer); + rjsonwriter_add_space(writer); + rjsonwriter_add_string(writer, info->path); + rjsonwriter_add_comma(writer); + rjsonwriter_add_newline(writer); + + rjsonwriter_add_spaces(writer, 6); + rjsonwriter_add_string(writer, "display_name"); + rjsonwriter_add_colon(writer); + rjsonwriter_add_space(writer); + rjsonwriter_add_string(writer, info->display_name); + rjsonwriter_add_comma(writer); + rjsonwriter_add_newline(writer); + + rjsonwriter_add_spaces(writer, 6); + rjsonwriter_add_string(writer, "display_version"); + rjsonwriter_add_colon(writer); + rjsonwriter_add_space(writer); + rjsonwriter_add_string(writer, info->display_version); + rjsonwriter_add_comma(writer); + rjsonwriter_add_newline(writer); + + rjsonwriter_add_spaces(writer, 6); + rjsonwriter_add_string(writer, "core_name"); + rjsonwriter_add_colon(writer); + rjsonwriter_add_space(writer); + rjsonwriter_add_string(writer, info->core_name); + rjsonwriter_add_comma(writer); + rjsonwriter_add_newline(writer); + + rjsonwriter_add_spaces(writer, 6); + rjsonwriter_add_string(writer, "system_manufacturer"); + rjsonwriter_add_colon(writer); + rjsonwriter_add_space(writer); + rjsonwriter_add_string(writer, info->system_manufacturer); + rjsonwriter_add_comma(writer); + rjsonwriter_add_newline(writer); + + rjsonwriter_add_spaces(writer, 6); + rjsonwriter_add_string(writer, "systemname"); + rjsonwriter_add_colon(writer); + rjsonwriter_add_space(writer); + rjsonwriter_add_string(writer, info->systemname); + rjsonwriter_add_comma(writer); + rjsonwriter_add_newline(writer); + + rjsonwriter_add_spaces(writer, 6); + rjsonwriter_add_string(writer, "system_id"); + rjsonwriter_add_colon(writer); + rjsonwriter_add_space(writer); + rjsonwriter_add_string(writer, info->system_id); + rjsonwriter_add_comma(writer); + rjsonwriter_add_newline(writer); + + rjsonwriter_add_spaces(writer, 6); + rjsonwriter_add_string(writer, "supported_extensions"); + rjsonwriter_add_colon(writer); + rjsonwriter_add_space(writer); + rjsonwriter_add_string(writer, info->supported_extensions); + rjsonwriter_add_comma(writer); + rjsonwriter_add_newline(writer); + + rjsonwriter_add_spaces(writer, 6); + rjsonwriter_add_string(writer, "authors"); + rjsonwriter_add_colon(writer); + rjsonwriter_add_space(writer); + rjsonwriter_add_string(writer, info->authors); + rjsonwriter_add_comma(writer); + rjsonwriter_add_newline(writer); + + rjsonwriter_add_spaces(writer, 6); + rjsonwriter_add_string(writer, "permissions"); + rjsonwriter_add_colon(writer); + rjsonwriter_add_space(writer); + rjsonwriter_add_string(writer, info->permissions); + rjsonwriter_add_comma(writer); + rjsonwriter_add_newline(writer); + + rjsonwriter_add_spaces(writer, 6); + rjsonwriter_add_string(writer, "licenses"); + rjsonwriter_add_colon(writer); + rjsonwriter_add_space(writer); + rjsonwriter_add_string(writer, info->licenses); + rjsonwriter_add_comma(writer); + rjsonwriter_add_newline(writer); + + rjsonwriter_add_spaces(writer, 6); + rjsonwriter_add_string(writer, "categories"); + rjsonwriter_add_colon(writer); + rjsonwriter_add_space(writer); + rjsonwriter_add_string(writer, info->categories); + rjsonwriter_add_comma(writer); + rjsonwriter_add_newline(writer); + + rjsonwriter_add_spaces(writer, 6); + rjsonwriter_add_string(writer, "databases"); + rjsonwriter_add_colon(writer); + rjsonwriter_add_space(writer); + rjsonwriter_add_string(writer, info->databases); + rjsonwriter_add_comma(writer); + rjsonwriter_add_newline(writer); + + rjsonwriter_add_spaces(writer, 6); + rjsonwriter_add_string(writer, "notes"); + rjsonwriter_add_colon(writer); + rjsonwriter_add_space(writer); + rjsonwriter_add_string(writer, info->notes); + rjsonwriter_add_comma(writer); + rjsonwriter_add_newline(writer); + + rjsonwriter_add_spaces(writer, 6); + rjsonwriter_add_string(writer, "required_hw_api"); + rjsonwriter_add_colon(writer); + rjsonwriter_add_space(writer); + rjsonwriter_add_string(writer, info->required_hw_api); + rjsonwriter_add_comma(writer); + rjsonwriter_add_newline(writer); + + rjsonwriter_add_spaces(writer, 6); + rjsonwriter_add_string(writer, "description"); + rjsonwriter_add_colon(writer); + rjsonwriter_add_space(writer); + rjsonwriter_add_string(writer, info->description); + rjsonwriter_add_comma(writer); + rjsonwriter_add_newline(writer); + + if (info->firmware_count > 0) + { + rjsonwriter_add_spaces(writer, 6); + rjsonwriter_add_string(writer, "firmware"); + rjsonwriter_add_colon(writer); + rjsonwriter_add_space(writer); + rjsonwriter_add_start_array(writer); + rjsonwriter_add_newline(writer); + + for (j = 0; j < info->firmware_count; j++) + { + rjsonwriter_add_spaces(writer, 8); + rjsonwriter_add_start_object(writer); + rjsonwriter_add_newline(writer); + rjsonwriter_add_spaces(writer, 10); + rjsonwriter_add_string(writer, "path"); + rjsonwriter_add_colon(writer); + rjsonwriter_add_space(writer); + rjsonwriter_add_string(writer, info->firmware[j].path); + rjsonwriter_add_comma(writer); + rjsonwriter_add_newline(writer); + rjsonwriter_add_spaces(writer, 10); + rjsonwriter_add_string(writer, "desc"); + rjsonwriter_add_colon(writer); + rjsonwriter_add_space(writer); + rjsonwriter_add_string(writer, info->firmware[j].desc); + rjsonwriter_add_comma(writer); + rjsonwriter_add_newline(writer); + rjsonwriter_add_spaces(writer, 10); + rjsonwriter_add_string(writer, "optional"); + rjsonwriter_add_colon(writer); + rjsonwriter_add_space(writer); + rjsonwriter_add_bool(writer, info->firmware[j].optional); + rjsonwriter_add_newline(writer); + rjsonwriter_add_spaces(writer, 8); + rjsonwriter_add_end_object(writer); + + if (j < info->firmware_count - 1) + rjsonwriter_add_comma(writer); + + rjsonwriter_add_newline(writer); + } + + rjsonwriter_add_spaces(writer, 6); + rjsonwriter_add_end_array(writer); + rjsonwriter_add_comma(writer); + rjsonwriter_add_newline(writer); + } + + rjsonwriter_add_spaces(writer, 6); + rjsonwriter_add_string(writer, "core_file_id"); + rjsonwriter_add_colon(writer); + rjsonwriter_add_newline(writer); + rjsonwriter_add_spaces(writer, 6); + rjsonwriter_add_start_object(writer); + rjsonwriter_add_newline(writer); + rjsonwriter_add_spaces(writer, 8); + rjsonwriter_add_string(writer, "str"); + rjsonwriter_add_colon(writer); + rjsonwriter_add_space(writer); + rjsonwriter_add_string(writer, info->core_file_id.str); + rjsonwriter_add_comma(writer); + rjsonwriter_add_newline(writer); + rjsonwriter_add_spaces(writer, 8); + rjsonwriter_add_string(writer, "hash"); + rjsonwriter_add_colon(writer); + rjsonwriter_add_space(writer); + rjsonwriter_add_unsigned(writer, info->core_file_id.hash); + rjsonwriter_add_newline(writer); + rjsonwriter_add_spaces(writer, 6); + rjsonwriter_add_end_object(writer); + rjsonwriter_add_comma(writer); + rjsonwriter_add_newline(writer); + + rjsonwriter_add_spaces(writer, 6); + rjsonwriter_add_string(writer, "firmware_count"); + rjsonwriter_add_colon(writer); + rjsonwriter_add_space(writer); + rjsonwriter_add_unsigned(writer, info->firmware_count); + rjsonwriter_add_comma(writer); + rjsonwriter_add_newline(writer); + + rjsonwriter_add_spaces(writer, 6); + rjsonwriter_add_string(writer, "has_info"); + rjsonwriter_add_colon(writer); + rjsonwriter_add_space(writer); + rjsonwriter_add_bool(writer, info->has_info); + rjsonwriter_add_comma(writer); + rjsonwriter_add_newline(writer); + + rjsonwriter_add_spaces(writer, 6); + rjsonwriter_add_string(writer, "supports_no_game"); + rjsonwriter_add_colon(writer); + rjsonwriter_add_space(writer); + rjsonwriter_add_bool(writer, info->supports_no_game); + rjsonwriter_add_comma(writer); + rjsonwriter_add_newline(writer); + + rjsonwriter_add_spaces(writer, 6); + rjsonwriter_add_string(writer, "database_match_archive_member"); + rjsonwriter_add_colon(writer); + rjsonwriter_add_space(writer); + rjsonwriter_add_bool(writer, info->database_match_archive_member); + rjsonwriter_add_comma(writer); + rjsonwriter_add_newline(writer); + + rjsonwriter_add_spaces(writer, 6); + rjsonwriter_add_string(writer, "is_experimental"); + rjsonwriter_add_colon(writer); + rjsonwriter_add_space(writer); + rjsonwriter_add_bool(writer, info->is_experimental); + rjsonwriter_add_newline(writer); + + rjsonwriter_add_spaces(writer, 4); + rjsonwriter_add_end_object(writer); + } + + rjsonwriter_add_newline(writer); + rjsonwriter_add_spaces(writer, 2); + rjsonwriter_add_end_array(writer); + rjsonwriter_add_newline(writer); + rjsonwriter_add_end_object(writer); + rjsonwriter_add_newline(writer); + rjsonwriter_free(writer); + + RARCH_LOG("[Core Info] Wrote to cache file: %s\n", file_path); + + /* Remove 'force refresh' file, if required */ + file_path[0] = '\0'; + + if (string_is_empty(info_dir)) + strlcpy(file_path, FILE_PATH_CORE_INFO_CACHE_REFRESH, sizeof(file_path)); + else + fill_pathname_join(file_path, info_dir, FILE_PATH_CORE_INFO_CACHE_REFRESH, + sizeof(file_path)); + + if (path_is_valid(file_path)) + filestream_delete(file_path); + +end: + intfstream_close(file); + free(file); + + list->refresh = false; +} + +static void core_info_check_uninstalled(core_info_cache_list_t *list) +{ + size_t i; + + if (!list) + return; + + for (i = 0; i < list->length; i++) + { + core_info_t *info = (core_info_t *)&list->items[i]; + + if (!info) + continue; + + if (!info->is_installed) + { + list->refresh = true; + return; + } + } +} + +/* When called, generates a temporary file + * that will force an info cache refresh the + * next time that core info is initialised with + * caching enabled */ +bool core_info_cache_force_refresh(const char *path_info) +{ + char file_path[PATH_MAX_LENGTH]; + + file_path[0] = '\0'; + + /* Get 'force refresh' file path */ + if (string_is_empty(path_info)) + strlcpy(file_path, FILE_PATH_CORE_INFO_CACHE_REFRESH, sizeof(file_path)); + else + fill_pathname_join(file_path, path_info, FILE_PATH_CORE_INFO_CACHE_REFRESH, + sizeof(file_path)); + + /* Generate a new, empty 'force refresh' file, + * if required */ + if (!path_is_valid(file_path)) + { + RFILE *refresh_file = filestream_open( + file_path, + RETRO_VFS_FILE_ACCESS_WRITE, + RETRO_VFS_FILE_ACCESS_HINT_NONE); + + if (!refresh_file) + return false; + + /* We have to write something - just output + * a single character */ + if (filestream_putc(refresh_file, 0) != 0) + { + filestream_close(refresh_file); + return false; + } + + filestream_close(refresh_file); + } + + return true; +} + +/***********************/ +/* Core Info Cache END */ +/***********************/ + enum compare_op { COMPARE_OP_EQUAL = 0, @@ -67,6 +1156,37 @@ enum compare_op COMPARE_OP_GREATER_EQUAL }; +typedef struct +{ + const char *path; + const char *filename; +} core_file_path_t; + +typedef struct +{ + core_file_path_t *list; + size_t size; +} core_file_path_list_t; + +typedef struct +{ + const char *filename; + uint32_t hash; +} core_lock_file_path_t; + +typedef struct +{ + core_lock_file_path_t *list; + size_t size; +} core_lock_file_path_list_t; + +typedef struct +{ + struct string_list *dir_list; + core_file_path_list_t *core_list; + core_lock_file_path_list_t *lock_list; +} core_path_list_t; + static uint32_t core_info_hash_string(const char *str) { unsigned char c; @@ -76,6 +1196,165 @@ static uint32_t core_info_hash_string(const char *str) return (hash ? hash : 1); } +static void core_info_path_list_free(core_path_list_t *path_list) +{ + if (!path_list) + return; + + if (path_list->core_list) + { + if (path_list->core_list->list) + free(path_list->core_list->list); + free(path_list->core_list); + } + + if (path_list->lock_list) + { + if (path_list->lock_list->list) + free(path_list->lock_list->list); + free(path_list->lock_list); + } + + if (path_list->dir_list) + string_list_free(path_list->dir_list); + + free(path_list); +} + +static core_path_list_t *core_info_path_list_new(const char *core_dir, + const char *core_ext, bool show_hidden_files) +{ + core_path_list_t *path_list = (core_path_list_t*)calloc(1, sizeof(*path_list)); + bool dir_list_ok = false; + char exts[32]; + size_t i; + + exts[0] = '\0'; + + if (string_is_empty(core_ext) || + !path_list) + goto error; + + /* Allocate list containers */ + path_list->dir_list = string_list_new(); + path_list->core_list = (core_file_path_list_t*)calloc(1, + sizeof(*path_list->core_list)); + path_list->lock_list = (core_lock_file_path_list_t*)calloc(1, + sizeof(*path_list->lock_list)); + + if (!path_list->dir_list || + !path_list->core_list || + !path_list->lock_list) + goto error; + + /* Get list of file extensions to include + * (core + lock file) */ + fill_pathname_join_delim(exts, core_ext, FILE_PATH_LOCK_EXTENSION_NO_DOT, + '|', sizeof(exts)); + + /* Fetch core directory listing */ + dir_list_ok = dir_list_append(path_list->dir_list, + core_dir, exts, false, show_hidden_files, + false, false); + +#if defined(__WINRT__) || defined(WINAPI_FAMILY) && WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP + { + /* UWP: browse the optional packages for additional cores */ + struct string_list core_packages = {0}; + + if (string_list_initialize(&core_packages)) + { + uwp_fill_installed_core_packages(&core_packages); + for (i = 0; i < core_packages.size; i++) + dir_list_append(path_list->dir_list, + core_packages.elems[i].data, exts, false, + show_hidden_files, false, false); + string_list_deinitialize(&core_packages); + } + } +#else + /* Keep the old 'directory not found' behaviour */ + if (!dir_list_ok) + goto error; +#endif + + /* Allocate sub lists */ + path_list->core_list->list = (core_file_path_t*) + malloc(path_list->dir_list->size * + sizeof(*path_list->core_list->list)); + path_list->lock_list->list = (core_lock_file_path_t*) + malloc(path_list->dir_list->size * + sizeof(*path_list->lock_list->list)); + + if (!path_list->core_list->list || + !path_list->lock_list->list) + goto error; + + /* Parse directory listing */ + for (i = 0; i < path_list->dir_list->size; i++) + { + const char *file_path = path_list->dir_list->elems[i].data; + const char *filename = NULL; + const char *file_ext = NULL; + + if (string_is_empty(file_path) || + !(filename = path_basename_nocompression(file_path)) || + !(file_ext = path_get_extension(filename))) + continue; + + /* Check whether this is a core or lock file */ + if (string_is_equal(file_ext, core_ext)) + { + path_list->core_list->list[ + path_list->core_list->size].path = file_path; + path_list->core_list->list[ + path_list->core_list->size].filename = filename; + path_list->core_list->size++; + } + else if (string_is_equal(file_ext, FILE_PATH_LOCK_EXTENSION_NO_DOT)) + { + path_list->lock_list->list[ + path_list->lock_list->size].filename = filename; + path_list->lock_list->list[ + path_list->lock_list->size].hash = core_info_hash_string(filename); + path_list->lock_list->size++; + } + } + + return path_list; + +error: + core_info_path_list_free(path_list); + return NULL; +} + +static bool core_info_path_is_locked(core_lock_file_path_list_t *lock_list, + const char *core_file_name) +{ + size_t i; + uint32_t hash; + char lock_filename[256]; + + if (lock_list->size < 1) + return false; + + strlcpy(lock_filename, core_file_name, sizeof(lock_filename)); + strlcat(lock_filename, FILE_PATH_LOCK_EXTENSION, sizeof(lock_filename)); + + hash = core_info_hash_string(lock_filename); + + for (i = 0; i < lock_list->size; i++) + { + core_lock_file_path_t *lock_file = &lock_list->list[i]; + + if ((lock_file->hash == hash) && + string_is_equal(lock_file->filename, lock_filename)) + return true; + } + + return false; +} + static bool core_info_get_file_id(const char *core_filename, char *core_file_id, size_t len) { @@ -471,60 +1750,23 @@ static void core_info_list_free(core_info_list_t *core_info_list) free(core_info_list); } -static void core_info_cache_list_free(core_info_cache_list_t *core_info_cache_list) -{ - size_t i; - - if (!core_info_cache_list) - return; - - for (i = 0; i < core_info_cache_list->length; i++) - { - core_info_t* info = (core_info_t*)&core_info_cache_list->items[i]; - core_info_free(info); - } - - free(core_info_cache_list->items); - free(core_info_cache_list); -} - static core_info_list_t *core_info_list_new(const char *path, const char *libretro_info_dir, const char *exts, - bool dir_show_hidden_files) + bool dir_show_hidden_files, + bool enable_cache) { size_t i; - struct string_list contents = {0}; + core_path_list_t *path_list = NULL; core_info_t *core_info = NULL; core_info_list_t *core_info_list = NULL; core_info_cache_list_t *core_info_cache_list = NULL; const char *info_dir = libretro_info_dir; - bool ok = false; - string_list_initialize(&contents); - - ok = dir_list_append(&contents, path, exts, - false, dir_show_hidden_files, false, false); - -#if defined(__WINRT__) || defined(WINAPI_FAMILY) && WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP - { - /* UWP: browse the optional packages for additional cores */ - struct string_list core_packages = {0}; - - if (string_list_initialize(&core_packages)) - { - uwp_fill_installed_core_packages(&core_packages); - for (i = 0; i < core_packages.size; i++) - dir_list_append(&contents, core_packages.elems[i].data, exts, - false, dir_show_hidden_files, false, false); - string_list_deinitialize(&core_packages); - } - } -#else - /* Keep the old 'directory not found' behaviour */ - if (!ok) + path_list = core_info_path_list_new(path, exts, + dir_show_hidden_files); + if (!path_list) goto error; -#endif core_info_list = (core_info_list_t*)malloc(sizeof(*core_info_list)); if (!core_info_list) @@ -535,7 +1777,8 @@ static core_info_list_t *core_info_list_new(const char *path, core_info_list->info_count = 0; core_info_list->all_ext = NULL; - core_info = (core_info_t*)calloc(contents.size, sizeof(*core_info)); + core_info = (core_info_t*)calloc(path_list->core_list->size, + sizeof(*core_info)); if (!core_info) { @@ -544,43 +1787,55 @@ static core_info_list_t *core_info_list_new(const char *path, } core_info_list->list = core_info; - core_info_list->count = contents.size; + core_info_list->count = path_list->core_list->size; -#ifdef _3DS - core_info_cache_list = core_info_read_cache_file(libretro_info_dir); - if (!core_info_cache_list) - goto error; -#endif - - for (i = 0; i < contents.size; i++) + /* Read core info cache, if enabled */ + if (enable_cache) { - core_info_t *info = &core_info[i]; - core_info_t *info_cache = NULL; - const char *base_path = contents.elems[i].data; - const char *core_filename = NULL; - config_file_t *conf = NULL; + core_info_cache_list = core_info_cache_read(info_dir); + if (!core_info_cache_list) + goto error; + } + + for (i = 0; i < path_list->core_list->size; i++) + { + core_info_t *info = &core_info[i]; + core_file_path_t *core_file = &path_list->core_list->list[i]; + const char *base_path = core_file->path; + const char *core_filename = core_file->filename; + config_file_t *conf = NULL; char core_file_id[256]; core_file_id[0] = '\0'; - if (string_is_empty(base_path) || - !(core_filename = path_basename_nocompression(base_path)) || - !core_info_get_file_id(core_filename, core_file_id, + if (!core_info_get_file_id(core_filename, core_file_id, sizeof(core_file_id))) continue; - info_cache = core_info_get_cache(core_info_cache_list, core_file_id); - if (info_cache) + /* If info cache is available, search for + * current core */ + if (core_info_cache_list) { - core_info_copy(info_cache, info); - continue; + core_info_t *info_cache = core_info_cache_find(core_info_cache_list, + core_file_id); + + if (info_cache) + { + core_info_copy(info_cache, info); + /* Core lock status is 'dynamic', and + * cannot be cached */ + info->is_locked = core_info_path_is_locked(path_list->lock_list, + core_filename); + continue; + } } /* Cache core path */ info->path = strdup(base_path); /* Get core lock status */ - info->is_locked = core_info_get_core_lock(info->path, false); + info->is_locked = core_info_path_is_locked(path_list->lock_list, + core_filename); /* Cache core file 'id' */ info->core_file_id.str = strdup(core_file_id); @@ -600,27 +1855,41 @@ static core_info_list_t *core_info_list_new(const char *path, info->display_name = strdup(core_filename); info->is_installed = true; -#ifdef _3DS - core_info_add_cache(core_info_cache_list, info); - core_info_cache_list->refresh = true; -#endif + + /* If info cache is enabled and we reach this + * point, current core is uncached + * > Add it to the list, and trigger a cache + * refresh */ + if (core_info_cache_list) + { + core_info_cache_add(core_info_cache_list, info, false); + core_info_cache_list->refresh = true; + } } -#ifdef _3DS - core_info_check_uninstalled(core_info_cache_list); - if (core_info_cache_list->refresh) { - core_info_write_cache_file(core_info_cache_list, libretro_info_dir); - } - - core_info_cache_list_free(core_info_cache_list); -#endif core_info_list_resolve_all_extensions(core_info_list); - string_list_deinitialize(&contents); + /* If info cache is enabled + * > Check whether any cached cores have been + * uninstalled since the last run (triggers + * a refresh) + * > Write new cache to disk if updates are + * required */ + if (core_info_cache_list) + { + core_info_check_uninstalled(core_info_cache_list); + + if (core_info_cache_list->refresh) + core_info_cache_write(core_info_cache_list, info_dir); + + core_info_cache_list_free(core_info_cache_list); + } + + core_info_path_list_free(path_list); return core_info_list; error: - string_list_deinitialize(&contents); + core_info_path_list_free(path_list); return NULL; } @@ -804,13 +2073,14 @@ void core_info_deinit_list(void) } bool core_info_init_list(const char *path_info, const char *dir_cores, - const char *exts, bool dir_show_hidden_files) + const char *exts, bool dir_show_hidden_files, bool enable_cache) { core_info_state_t *p_coreinfo = coreinfo_get_ptr(); if (!(p_coreinfo->curr_list = core_info_list_new(dir_cores, !string_is_empty(path_info) ? path_info : dir_cores, exts, - dir_show_hidden_files))) + dir_show_hidden_files, + enable_cache))) return false; return true; } @@ -1649,805 +2919,3 @@ bool core_info_get_core_lock(const char *core_path, bool validate_path) return is_locked; } - -core_info_t *core_info_get_cache(core_info_cache_list_t *list, char *core_file_id) -{ - size_t i; - core_info_t *info = NULL; - - if (!list | !core_file_id) - return NULL; - - for (i = 0; i < list->length; i++) - { - core_info_t *temp = (core_info_t*)&list->items[i]; - if (!temp) - continue; - - if (string_is_equal(temp->core_file_id.str, core_file_id)) - { - temp->is_installed = true; - info = temp; - break; - } - } - - return info; -} - -void core_info_add_cache(core_info_cache_list_t *list, core_info_t *info) -{ - if (!info->core_file_id.str) - return; - - if (list->length >= list->capacity) - { - size_t prev_capacity = list->capacity; - list->capacity = list->capacity * 2; - list->items = (core_info_t*)realloc(list->items, list->capacity * sizeof(core_info_t)); - memset(&list->items[prev_capacity], 0, (list->capacity - prev_capacity) * sizeof(core_info_t)); - } - - core_info_t *cache = &list->items[list->length]; - core_info_copy(info, cache); - list->length++; -} - -static bool CCJSONObjectMemberHandler(void *context, const char *pValue, size_t length) -{ - CCJSONContext *pCtx = (CCJSONContext *)context; - - if ((pCtx->object_depth == 2) && (pCtx->array_depth == 1) && length) - { - pCtx->current_string_val = NULL; - pCtx->current_string_list_val = NULL; - pCtx->current_entry_uint_val = NULL; - pCtx->current_entry_bool_val = NULL; - pCtx->to_core_file_id = false; - pCtx->to_firmware = false; - - switch (pValue[0]) - { - case 'a': - if (string_is_equal(pValue, "authors")) - { - pCtx->current_string_val = &pCtx->core_info->authors; - pCtx->current_string_list_val = &pCtx->core_info->authors_list; - } - break; - case 'c': - if (string_is_equal(pValue, "categories")) - { - pCtx->current_string_val = &pCtx->core_info->categories; - pCtx->current_string_list_val = &pCtx->core_info->categories_list; - } - else if (string_is_equal(pValue, "core_name")) - pCtx->current_string_val = &pCtx->core_info->core_name; - else if (string_is_equal(pValue, "core_file_id")) - pCtx->to_core_file_id = true; - break; - case 'd': - if (string_is_equal(pValue, "display_name")) - pCtx->current_string_val = &pCtx->core_info->display_name; - else if (string_is_equal(pValue, "display_version")) - pCtx->current_string_val = &pCtx->core_info->display_version; - else if (string_is_equal(pValue, "databases")) - { - pCtx->current_string_val = &pCtx->core_info->databases; - pCtx->current_string_list_val = &pCtx->core_info->databases_list; - } - else if (string_is_equal(pValue, "description")) - pCtx->current_string_val = &pCtx->core_info->description; - else if (string_is_equal(pValue, "database_match_archive_member")) - pCtx->current_entry_bool_val = &pCtx->core_info->database_match_archive_member; - break; - case 'f': - if (string_is_equal(pValue, "firmware")) - pCtx->to_firmware = true; - break; - case 'h': - if (string_is_equal(pValue, "has_info")) - pCtx->current_entry_bool_val = &pCtx->core_info->has_info; - break; - case 'l': - if (string_is_equal(pValue, "licenses")) - { - pCtx->current_string_val = &pCtx->core_info->licenses; - pCtx->current_string_list_val = &pCtx->core_info->licenses_list; - } - else if (string_is_equal(pValue, "is_experimental")) - pCtx->current_entry_bool_val = &pCtx->core_info->is_experimental; - else if (string_is_equal(pValue, "is_locked")) - pCtx->current_entry_bool_val = &pCtx->core_info->is_locked; - break; - case 'n': - if (string_is_equal(pValue, "notes")) - { - pCtx->current_string_val = &pCtx->core_info->notes; - pCtx->current_string_list_val = &pCtx->core_info->note_list; - } - break; - case 'p': - if (string_is_equal(pValue, "path")) - pCtx->current_string_val = &pCtx->core_info->path; - else if (string_is_equal(pValue, "permissions")) - { - pCtx->current_string_val = &pCtx->core_info->permissions; - pCtx->current_string_list_val = &pCtx->core_info->permissions_list; - } - break; - case 'r': - if (string_is_equal(pValue, "required_hw_api")) - { - pCtx->current_string_val = &pCtx->core_info->required_hw_api; - pCtx->current_string_list_val = &pCtx->core_info->required_hw_api_list; - } - break; - case 's': - if (string_is_equal(pValue, "system_manufacturer")) - pCtx->current_string_val = &pCtx->core_info->system_manufacturer; - else if (string_is_equal(pValue, "systemname")) - pCtx->current_string_val = &pCtx->core_info->systemname; - else if (string_is_equal(pValue, "system_id")) - pCtx->current_string_val = &pCtx->core_info->system_id; - else if (string_is_equal(pValue, "supported_extensions")) - { - pCtx->current_string_val = &pCtx->core_info->supported_extensions; - pCtx->current_string_list_val = &pCtx->core_info->supported_extensions_list; - } - else if (string_is_equal(pValue, "supports_no_game")) - pCtx->current_entry_bool_val = &pCtx->core_info->supports_no_game; - break; - } - } - else if ((pCtx->object_depth == 3) && (pCtx->array_depth == 1) && length) - { - if (pCtx->to_core_file_id) - { - if (string_is_equal(pValue, "str")) - pCtx->current_string_val = &pCtx->core_info->core_file_id.str; - else if (string_is_equal(pValue, "hash")) - pCtx->current_entry_uint_val = &pCtx->core_info->core_file_id.hash; - } - } - else if ((pCtx->object_depth == 3) && (pCtx->array_depth == 2) && length) - { - if (pCtx->to_firmware) - { - if (string_is_equal(pValue, "path")) - pCtx->current_string_val = &pCtx->core_info->firmware[pCtx->core_info->firmware_count - 1].path; - else if (string_is_equal(pValue, "desc")) - pCtx->current_string_val = &pCtx->core_info->firmware[pCtx->core_info->firmware_count - 1].desc; - else if (string_is_equal(pValue, "missing")) - pCtx->current_entry_bool_val = &pCtx->core_info->firmware[pCtx->core_info->firmware_count - 1].missing; - else if (string_is_equal(pValue, "optional")) - pCtx->current_entry_bool_val = &pCtx->core_info->firmware[pCtx->core_info->firmware_count - 1].optional; - } - } - - return true; -} - -static bool CCJSONStringHandler(void *context, const char *pValue, size_t length) -{ - CCJSONContext *pCtx = (CCJSONContext*)context; - - if (pCtx->current_string_val) - { - *pCtx->current_string_val = strdup(pValue); - - if (pCtx->current_string_list_val) - { - *pCtx->current_string_list_val = string_split(*pCtx->current_string_val, "|"); - } - } - - return true; -} - -static bool CCJSONNumberHandler(void *context, const char *pValue, size_t length) -{ - CCJSONContext *pCtx = (CCJSONContext*)context; - - if (pCtx->current_entry_uint_val) - *pCtx->current_entry_uint_val = (unsigned)strtoul(pValue, NULL, 10); - - return true; -} - -static bool CCJSONBoolHandler(void *context, bool value) -{ - CCJSONContext *pCtx = (CCJSONContext *)context; - - if (pCtx->current_entry_bool_val) - *pCtx->current_entry_bool_val = value; - - return true; -} - -static bool CCJSONStartObjectHandler(void *context) -{ - CCJSONContext *pCtx = (CCJSONContext*)context; - - pCtx->object_depth++; - - if ((pCtx->object_depth == 1) && (pCtx->array_depth == 0)) - { - pCtx->core_info_cache_list = new_core_info_cache_list(); - if (!pCtx->core_info_cache_list) - return false; - } - else if ((pCtx->object_depth == 2) && (pCtx->array_depth == 1)) - { - pCtx->core_info = (core_info_t *)calloc(1, sizeof(core_info_t)); - if (!pCtx->core_info) - return false; - } - else if ((pCtx->object_depth == 3) && (pCtx->array_depth == 2)) - { - if (pCtx->to_firmware) - { - pCtx->core_info->firmware_count++; - pCtx->core_info->firmware = (core_info_firmware_t *)realloc(pCtx->core_info->firmware, pCtx->core_info->firmware_count * sizeof(core_info_firmware_t)); - } - } - - return true; -} - -static bool CCJSONEndObjectHandler(void *context) -{ - CCJSONContext *pCtx = (CCJSONContext*)context; - - if ((pCtx->object_depth == 2) && (pCtx->array_depth == 1)) - { - core_info_add_cache(pCtx->core_info_cache_list, pCtx->core_info); - core_info_free(pCtx->core_info); - } - else if ((pCtx->object_depth == 3) && (pCtx->array_depth == 1)) - { - if (pCtx->to_core_file_id) - pCtx->to_core_file_id = false; - } - - retro_assert(pCtx->object_depth > 0); - pCtx->object_depth--; - - return true; -} - -static bool CCJSONStartArrayHandler(void *context) -{ - CCJSONContext *pCtx = (CCJSONContext*)context; - - pCtx->array_depth++; - - return true; -} - -static bool CCJSONEndArrayHandler(void *context) -{ - CCJSONContext *pCtx = (CCJSONContext*)context; - - if ((pCtx->object_depth == 2) && (pCtx->array_depth == 2)) - { - if (pCtx->to_firmware) - pCtx->to_firmware = false; - } - - retro_assert(pCtx->array_depth > 0); - pCtx->array_depth--; - - return true; -} - -core_info_cache_list_t *core_info_read_cache_file(const char *info_dir) -{ - core_info_cache_list_t *core_info_cache_list; - char file_path[PATH_MAX_LENGTH]; - - core_info_cache_list = NULL; - - file_path[0] = '\0'; - fill_pathname_join(file_path, info_dir, CORE_INFO_CACHE_FILE_NAME, sizeof(file_path)); - -#if defined(HAVE_ZLIB) - /* Always use RZIP interface when reading playlists - * > this will automatically handle uncompressed - * data */ - intfstream_t *file = intfstream_open_rzip_file( - file_path, - RETRO_VFS_FILE_ACCESS_READ); -#else - intfstream_t *file = intfstream_open_file( - playlist->config.path, - RETRO_VFS_FILE_ACCESS_READ, - RETRO_VFS_FILE_ACCESS_HINT_NONE); -#endif - - if (file) { - rjson_t *parser; - CCJSONContext context = { 0 }; - - parser = rjson_open_stream(file); - if (!parser) - { - RARCH_ERR("Failed to create JSON parser\n"); - goto end; - } - - rjson_set_options(parser, - RJSON_OPTION_ALLOW_UTF8BOM - | RJSON_OPTION_ALLOW_COMMENTS - | RJSON_OPTION_ALLOW_UNESCAPED_CONTROL_CHARACTERS - | RJSON_OPTION_REPLACE_INVALID_ENCODING); - - if (rjson_parse(parser, &context, - CCJSONObjectMemberHandler, - CCJSONStringHandler, - CCJSONNumberHandler, - CCJSONStartObjectHandler, - CCJSONEndObjectHandler, - CCJSONStartArrayHandler, - CCJSONEndArrayHandler, - CCJSONBoolHandler, - NULL) // null - != RJSON_DONE) - { - RARCH_WARN("Error parsing chunk:\n---snip---\n%.*s\n---snip---\n", - rjson_get_source_context_len(parser), - rjson_get_source_context_buf(parser)); - RARCH_WARN("Error: Invalid JSON at line %d, column %d - %s.\n", - (int)rjson_get_source_line(parser), - (int)rjson_get_source_column(parser), - (*rjson_get_error(parser) ? rjson_get_error(parser) : "format error")); - } - core_info_cache_list = context.core_info_cache_list; - rjson_free(parser); - } - else - { - core_info_cache_list = new_core_info_cache_list(); - } - -end: - intfstream_close(file); - free(file); - - return core_info_cache_list; -} - -core_info_cache_list_t *new_core_info_cache_list(void) -{ - const int default_cache_capacity = 8; - - core_info_cache_list_t *core_info_cache_list; - - core_info_cache_list = (core_info_cache_list_t *)malloc(sizeof(*core_info_cache_list)); - if (!core_info_cache_list) - return NULL; - - core_info_cache_list->items = (core_info_t *)calloc(default_cache_capacity, sizeof(core_info_t)); - if (!core_info_cache_list->items) - { - core_info_cache_list_free(core_info_cache_list); - return NULL; - } - - core_info_cache_list->length = 0; - core_info_cache_list->capacity = default_cache_capacity; - core_info_cache_list->refresh = false; - - return core_info_cache_list; -} - -void core_info_write_cache_file(core_info_cache_list_t *list, const char *info_dir) -{ - size_t i, j; - - intfstream_t *file = NULL; - rjsonwriter_t *writer; - char file_path[PATH_MAX_LENGTH]; - - file_path[0] = '\0'; - - fill_pathname_join(file_path, info_dir, CORE_INFO_CACHE_FILE_NAME, sizeof(file_path)); - - file = intfstream_open_file(file_path, RETRO_VFS_FILE_ACCESS_WRITE, RETRO_VFS_FILE_ACCESS_HINT_NONE); - if (!file) - { - RARCH_ERR("Failed to write to core info cache file: %s\n", file_path); - return; - } - - writer = rjsonwriter_open_stream(file); - if (!writer) - { - RARCH_ERR("Failed to create JSON writer\n"); - goto end; - } - - rjsonwriter_add_start_object(writer); - rjsonwriter_add_newline(writer); - rjsonwriter_add_spaces(writer, 2); - rjsonwriter_add_string(writer, "version"); - rjsonwriter_add_colon(writer); - rjsonwriter_add_space(writer); - rjsonwriter_add_string(writer, "1.0"); - rjsonwriter_add_comma(writer); - rjsonwriter_add_newline(writer); - rjsonwriter_add_spaces(writer, 2); - rjsonwriter_add_string(writer, "items"); - rjsonwriter_add_colon(writer); - rjsonwriter_add_space(writer); - rjsonwriter_add_start_array(writer); - rjsonwriter_add_newline(writer); - - for (i = 0; i < list->length; i++) - { - core_info_t* info = &list->items[i]; - if (!info->is_installed) - continue; - - if (i > 0) - { - rjsonwriter_add_comma(writer); - rjsonwriter_add_newline(writer); - } - - rjsonwriter_add_spaces(writer, 4); - rjsonwriter_add_start_object(writer); - rjsonwriter_add_newline(writer); - - rjsonwriter_add_spaces(writer, 6); - rjsonwriter_add_string(writer, "path"); - rjsonwriter_add_colon(writer); - rjsonwriter_add_space(writer); - rjsonwriter_add_string(writer, info->path); - rjsonwriter_add_comma(writer); - rjsonwriter_add_newline(writer); - - rjsonwriter_add_spaces(writer, 6); - rjsonwriter_add_string(writer, "display_name"); - rjsonwriter_add_colon(writer); - rjsonwriter_add_space(writer); - rjsonwriter_add_string(writer, info->display_name); - rjsonwriter_add_comma(writer); - rjsonwriter_add_newline(writer); - - rjsonwriter_add_spaces(writer, 6); - rjsonwriter_add_string(writer, "display_version"); - rjsonwriter_add_colon(writer); - rjsonwriter_add_space(writer); - rjsonwriter_add_string(writer, info->display_version); - rjsonwriter_add_comma(writer); - rjsonwriter_add_newline(writer); - - rjsonwriter_add_spaces(writer, 6); - rjsonwriter_add_string(writer, "core_name"); - rjsonwriter_add_colon(writer); - rjsonwriter_add_space(writer); - rjsonwriter_add_string(writer, info->core_name); - rjsonwriter_add_comma(writer); - rjsonwriter_add_newline(writer); - - rjsonwriter_add_spaces(writer, 6); - rjsonwriter_add_string(writer, "system_manufacturer"); - rjsonwriter_add_colon(writer); - rjsonwriter_add_space(writer); - rjsonwriter_add_string(writer, info->system_manufacturer); - rjsonwriter_add_comma(writer); - rjsonwriter_add_newline(writer); - - rjsonwriter_add_spaces(writer, 6); - rjsonwriter_add_string(writer, "systemname"); - rjsonwriter_add_colon(writer); - rjsonwriter_add_space(writer); - rjsonwriter_add_string(writer, info->systemname); - rjsonwriter_add_comma(writer); - rjsonwriter_add_newline(writer); - - rjsonwriter_add_spaces(writer, 6); - rjsonwriter_add_string(writer, "system_id"); - rjsonwriter_add_colon(writer); - rjsonwriter_add_space(writer); - rjsonwriter_add_string(writer, info->system_id); - rjsonwriter_add_comma(writer); - rjsonwriter_add_newline(writer); - - rjsonwriter_add_spaces(writer, 6); - rjsonwriter_add_string(writer, "supported_extensions"); - rjsonwriter_add_colon(writer); - rjsonwriter_add_space(writer); - rjsonwriter_add_string(writer, info->supported_extensions); - rjsonwriter_add_comma(writer); - rjsonwriter_add_newline(writer); - - rjsonwriter_add_spaces(writer, 6); - rjsonwriter_add_string(writer, "authors"); - rjsonwriter_add_colon(writer); - rjsonwriter_add_space(writer); - rjsonwriter_add_string(writer, info->authors); - rjsonwriter_add_comma(writer); - rjsonwriter_add_newline(writer); - - rjsonwriter_add_spaces(writer, 6); - rjsonwriter_add_string(writer, "permissions"); - rjsonwriter_add_colon(writer); - rjsonwriter_add_space(writer); - rjsonwriter_add_string(writer, info->permissions); - rjsonwriter_add_comma(writer); - rjsonwriter_add_newline(writer); - - rjsonwriter_add_spaces(writer, 6); - rjsonwriter_add_string(writer, "licenses"); - rjsonwriter_add_colon(writer); - rjsonwriter_add_space(writer); - rjsonwriter_add_string(writer, info->licenses); - rjsonwriter_add_comma(writer); - rjsonwriter_add_newline(writer); - - rjsonwriter_add_spaces(writer, 6); - rjsonwriter_add_string(writer, "categories"); - rjsonwriter_add_colon(writer); - rjsonwriter_add_space(writer); - rjsonwriter_add_string(writer, info->categories); - rjsonwriter_add_comma(writer); - rjsonwriter_add_newline(writer); - - rjsonwriter_add_spaces(writer, 6); - rjsonwriter_add_string(writer, "databases"); - rjsonwriter_add_colon(writer); - rjsonwriter_add_space(writer); - rjsonwriter_add_string(writer, info->databases); - rjsonwriter_add_comma(writer); - rjsonwriter_add_newline(writer); - - rjsonwriter_add_spaces(writer, 6); - rjsonwriter_add_string(writer, "notes"); - rjsonwriter_add_colon(writer); - rjsonwriter_add_space(writer); - rjsonwriter_add_string(writer, info->notes); - rjsonwriter_add_comma(writer); - rjsonwriter_add_newline(writer); - - rjsonwriter_add_spaces(writer, 6); - rjsonwriter_add_string(writer, "required_hw_api"); - rjsonwriter_add_colon(writer); - rjsonwriter_add_space(writer); - rjsonwriter_add_string(writer, info->required_hw_api); - rjsonwriter_add_comma(writer); - rjsonwriter_add_newline(writer); - - rjsonwriter_add_spaces(writer, 6); - rjsonwriter_add_string(writer, "description"); - rjsonwriter_add_colon(writer); - rjsonwriter_add_space(writer); - rjsonwriter_add_string(writer, info->description); - rjsonwriter_add_comma(writer); - rjsonwriter_add_newline(writer); - - if (info->firmware_count) - { - rjsonwriter_add_spaces(writer, 6); - rjsonwriter_add_string(writer, "firmware"); - rjsonwriter_add_colon(writer); - rjsonwriter_add_space(writer); - rjsonwriter_add_start_array(writer); - rjsonwriter_add_newline(writer); - - for (j = 0; j < info->firmware_count; j++) - { - rjsonwriter_add_spaces(writer, 8); - rjsonwriter_add_start_object(writer); - rjsonwriter_add_newline(writer); - rjsonwriter_add_spaces(writer, 10); - rjsonwriter_add_string(writer, "path"); - rjsonwriter_add_colon(writer); - rjsonwriter_add_space(writer); - rjsonwriter_add_string(writer, info->firmware[j].path); - rjsonwriter_add_comma(writer); - rjsonwriter_add_newline(writer); - rjsonwriter_add_spaces(writer, 10); - rjsonwriter_add_string(writer, "desc"); - rjsonwriter_add_colon(writer); - rjsonwriter_add_space(writer); - rjsonwriter_add_string(writer, info->firmware[j].desc); - rjsonwriter_add_comma(writer); - rjsonwriter_add_newline(writer); - rjsonwriter_add_spaces(writer, 10); - rjsonwriter_add_string(writer, "missing"); - rjsonwriter_add_colon(writer); - rjsonwriter_add_space(writer); - rjsonwriter_add_bool(writer, info->firmware[j].missing); - rjsonwriter_add_comma(writer); - rjsonwriter_add_newline(writer); - rjsonwriter_add_spaces(writer, 10); - rjsonwriter_add_string(writer, "optional"); - rjsonwriter_add_colon(writer); - rjsonwriter_add_space(writer); - rjsonwriter_add_bool(writer, info->firmware[j].optional); - rjsonwriter_add_newline(writer); - rjsonwriter_add_spaces(writer, 8); - rjsonwriter_add_end_object(writer); - - if (j < info->firmware_count - 1) - rjsonwriter_add_comma(writer); - - rjsonwriter_add_newline(writer); - } - - rjsonwriter_add_spaces(writer, 6); - rjsonwriter_add_end_array(writer); - rjsonwriter_add_comma(writer); - rjsonwriter_add_newline(writer); - } - - rjsonwriter_add_spaces(writer, 6); - rjsonwriter_add_string(writer, "core_file_id"); - rjsonwriter_add_colon(writer); - rjsonwriter_add_newline(writer); - rjsonwriter_add_spaces(writer, 6); - rjsonwriter_add_start_object(writer); - rjsonwriter_add_newline(writer); - rjsonwriter_add_spaces(writer, 8); - rjsonwriter_add_string(writer, "str"); - rjsonwriter_add_colon(writer); - rjsonwriter_add_space(writer); - rjsonwriter_add_string(writer, info->core_file_id.str); - rjsonwriter_add_comma(writer); - rjsonwriter_add_newline(writer); - rjsonwriter_add_spaces(writer, 8); - rjsonwriter_add_string(writer, "hash"); - rjsonwriter_add_colon(writer); - rjsonwriter_add_space(writer); - rjsonwriter_add_unsigned(writer, info->core_file_id.hash); - rjsonwriter_add_newline(writer); - rjsonwriter_add_spaces(writer, 6); - rjsonwriter_add_end_object(writer); - rjsonwriter_add_comma(writer); - rjsonwriter_add_newline(writer); - - rjsonwriter_add_spaces(writer, 6); - rjsonwriter_add_string(writer, "firmware_count"); - rjsonwriter_add_colon(writer); - rjsonwriter_add_space(writer); - rjsonwriter_add_unsigned(writer, info->firmware_count); - rjsonwriter_add_comma(writer); - rjsonwriter_add_newline(writer); - - rjsonwriter_add_spaces(writer, 6); - rjsonwriter_add_string(writer, "has_info"); - rjsonwriter_add_colon(writer); - rjsonwriter_add_space(writer); - rjsonwriter_add_bool(writer, info->has_info); - rjsonwriter_add_comma(writer); - rjsonwriter_add_newline(writer); - - rjsonwriter_add_spaces(writer, 6); - rjsonwriter_add_string(writer, "supports_no_game"); - rjsonwriter_add_colon(writer); - rjsonwriter_add_space(writer); - rjsonwriter_add_bool(writer, info->supports_no_game); - rjsonwriter_add_comma(writer); - rjsonwriter_add_newline(writer); - - rjsonwriter_add_spaces(writer, 6); - rjsonwriter_add_string(writer, "database_match_archive_member"); - rjsonwriter_add_colon(writer); - rjsonwriter_add_space(writer); - rjsonwriter_add_bool(writer, info->database_match_archive_member); - rjsonwriter_add_comma(writer); - rjsonwriter_add_newline(writer); - - rjsonwriter_add_spaces(writer, 6); - rjsonwriter_add_string(writer, "is_experimental"); - rjsonwriter_add_colon(writer); - rjsonwriter_add_space(writer); - rjsonwriter_add_bool(writer, info->is_experimental); - rjsonwriter_add_comma(writer); - rjsonwriter_add_newline(writer); - - rjsonwriter_add_spaces(writer, 6); - rjsonwriter_add_string(writer, "is_locked"); - rjsonwriter_add_colon(writer); - rjsonwriter_add_space(writer); - rjsonwriter_add_bool(writer, info->is_locked); - rjsonwriter_add_newline(writer); - - rjsonwriter_add_spaces(writer, 4); - rjsonwriter_add_end_object(writer); - } - - rjsonwriter_add_newline(writer); - rjsonwriter_add_spaces(writer, 2); - rjsonwriter_add_end_array(writer); - rjsonwriter_add_newline(writer); - rjsonwriter_add_end_object(writer); - rjsonwriter_add_newline(writer); - rjsonwriter_free(writer); - - RARCH_LOG("[CoreInfo]: Written to cache file: %s\n", file_path); -end: - intfstream_close(file); - free(file); - - list->refresh = false; -} - -void core_info_copy(core_info_t *src, core_info_t *dst) -{ - size_t i; - - dst->path = src->path ? strdup(src->path) : NULL; - dst->display_name = src->display_name ? strdup(src->display_name) : NULL; - dst->display_version = src->display_version ? strdup(src->display_version) : NULL; - dst->core_name = src->core_name ? strdup(src->core_name) : NULL; - dst->system_manufacturer = src->system_manufacturer ? strdup(src->system_manufacturer) : NULL; - dst->systemname = src->systemname ? strdup(src->systemname) : NULL; - dst->system_id = src->system_id ? strdup(src->system_id) : NULL; - dst->supported_extensions = src->supported_extensions ? strdup(src->supported_extensions) : NULL; - dst->authors = src->authors ? strdup(src->authors) : NULL; - dst->permissions = src->permissions ? strdup(src->permissions) : NULL; - dst->licenses = src->licenses ? strdup(src->licenses) : NULL; - dst->categories = src->categories ? strdup(src->categories) : NULL; - dst->databases = src->databases ? strdup(src->databases) : NULL; - dst->notes = src->notes ? strdup(src->notes) : NULL; - dst->required_hw_api = src->required_hw_api ? strdup(src->required_hw_api) : NULL; - dst->description = src->description ? strdup(src->description) : NULL; - dst->categories_list = src->categories_list ? string_list_clone(src->categories_list) : NULL; - dst->databases_list = src->databases_list ? string_list_clone(src->databases_list) : NULL; - dst->note_list = src->note_list ? string_list_clone(src->note_list) : NULL; - dst->supported_extensions_list = src->supported_extensions_list ? string_list_clone(src->supported_extensions_list) : NULL; - dst->authors_list = src->authors_list ? string_list_clone(src->authors_list) : NULL; - dst->permissions_list = src->permissions_list ? string_list_clone(src->permissions_list) : NULL; - dst->licenses_list = src->licenses_list ? string_list_clone(src->licenses_list) : NULL; - dst->required_hw_api_list = src->required_hw_api_list ? string_list_clone(src->required_hw_api_list) : NULL; - - if (src->firmware_count) - { - dst->firmware = (core_info_firmware_t*)calloc(src->firmware_count, sizeof(core_info_firmware_t)); - if (!dst->firmware) - return; - - for (i = 0; i < src->firmware_count; i++) - { - dst->firmware[i].path = src->firmware[i].path ? strdup(src->firmware[i].path) : NULL; - dst->firmware[i].desc = src->firmware[i].desc ? strdup(src->firmware[i].desc) : NULL; - dst->firmware[i].missing = src->firmware[i].missing; - dst->firmware[i].optional = src->firmware[i].optional; - } - } - - dst->core_file_id.str = src->core_file_id.str ? strdup(src->core_file_id.str) : NULL; - dst->core_file_id.hash = src->core_file_id.hash; - - dst->firmware_count = src->firmware_count; - dst->has_info = src->has_info; - dst->supports_no_game = src->supports_no_game; - dst->database_match_archive_member = src->database_match_archive_member; - dst->is_experimental = src->is_experimental; - dst->is_locked = src->is_locked; - dst->is_installed = src->is_installed; -} - -void core_info_check_uninstalled(core_info_cache_list_t *list) -{ - size_t i; - - if (!list) - return; - - for (i = 0; i < list->length; i++) - { - core_info_t *info = (core_info_t *)&list->items[i]; - if (!info) - continue; - - if (!info->is_installed) - { - list->refresh = true; - return; - } - } -} diff --git a/core_info.h b/core_info.h index f264e6b2fb..7cb286abff 100644 --- a/core_info.h +++ b/core_info.h @@ -110,14 +110,6 @@ typedef struct size_t info_count; } core_info_list_t; -typedef struct -{ - core_info_t *items; - size_t length; - size_t capacity; - bool refresh; -} core_info_cache_list_t; - typedef struct core_info_ctx_firmware { const char *path; @@ -165,7 +157,7 @@ bool core_info_get_current_core(core_info_t **core); void core_info_deinit_list(void); bool core_info_init_list(const char *path_info, const char *dir_cores, - const char *exts, bool show_hidden_files); + const char *exts, bool show_hidden_files, bool enable_cache); bool core_info_get_list(core_info_list_t **core); @@ -212,13 +204,11 @@ 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); -core_info_t *core_info_get_cache(core_info_cache_list_t *list, char *core_file_id); -void core_info_add_cache(core_info_cache_list_t *list, core_info_t *info); -void core_info_copy(core_info_t *src, core_info_t *dst); -void core_info_write_cache_file(core_info_cache_list_t *list, const char *info_dir); -core_info_cache_list_t *core_info_read_cache_file(const char *info_dir); -core_info_cache_list_t *new_core_info_cache_list(void); -void core_info_check_uninstalled(core_info_cache_list_t *list); +/* When called, generates a temporary file + * that will force an info cache refresh the + * next time that core info is initialised with + * caching enabled */ +bool core_info_cache_force_refresh(const char *path_info); RETRO_END_DECLS diff --git a/file_path_special.h b/file_path_special.h index 8ce54e96dd..cb72bba45c 100644 --- a/file_path_special.h +++ b/file_path_special.h @@ -106,10 +106,13 @@ RETRO_BEGIN_DECLS #define FILE_PATH_CORE_BACKUP_EXTENSION ".lcbk" #define FILE_PATH_CORE_BACKUP_EXTENSION_NO_DOT "lcbk" #define FILE_PATH_LOCK_EXTENSION ".lck" +#define FILE_PATH_LOCK_EXTENSION_NO_DOT "lck" #define FILE_PATH_BACKUP_EXTENSION ".bak" #if defined(RARCH_MOBILE) #define FILE_PATH_DEFAULT_OVERLAY "gamepads/neo-retropad/neo-retropad.cfg" #endif +#define FILE_PATH_CORE_INFO_CACHE "core_info.cache" +#define FILE_PATH_CORE_INFO_CACHE_REFRESH "core_info.refresh" enum application_special_type { diff --git a/intl/msg_hash_lbl.h b/intl/msg_hash_lbl.h index abe0714846..1812b4c699 100644 --- a/intl/msg_hash_lbl.h +++ b/intl/msg_hash_lbl.h @@ -1172,6 +1172,10 @@ MSG_HASH( MENU_ENUM_LABEL_CHECK_FOR_MISSING_FIRMWARE, "check_for_missing_firmware" ) +MSG_HASH( + MENU_ENUM_LABEL_CORE_INFO_CACHE_ENABLE, + "core_info_cache_enable" + ) MSG_HASH( MENU_ENUM_LABEL_DUMMY_ON_CORE_SHUTDOWN, "dummy_on_core_shutdown" diff --git a/intl/msg_hash_us.h b/intl/msg_hash_us.h index 57eabb8bff..92381cb3ef 100644 --- a/intl/msg_hash_us.h +++ b/intl/msg_hash_us.h @@ -2886,6 +2886,14 @@ MSG_HASH( MENU_ENUM_SUBLABEL_CHECK_FOR_MISSING_FIRMWARE, "Check if all the required firmware is present before attempting to load content." ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_CORE_INFO_CACHE_ENABLE, + "Cache Core Info Files" + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_CORE_INFO_CACHE_ENABLE, + "Maintain a persistent local cache of installed core information. Greatly reduces loading times on platforms with slow disk access." + ) #ifndef HAVE_DYNAMIC MSG_HASH( MENU_ENUM_LABEL_VALUE_ALWAYS_RELOAD_CORE_ON_RUN_CONTENT, diff --git a/menu/cbs/menu_cbs_sublabel.c b/menu/cbs/menu_cbs_sublabel.c index d97c5611f6..cb0404e252 100644 --- a/menu/cbs/menu_cbs_sublabel.c +++ b/menu/cbs/menu_cbs_sublabel.c @@ -357,6 +357,7 @@ DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_video_adaptive_vsync, MENU_ DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_core_allow_rotate, MENU_ENUM_SUBLABEL_VIDEO_ALLOW_ROTATE) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_dummy_on_core_shutdown, MENU_ENUM_SUBLABEL_DUMMY_ON_CORE_SHUTDOWN) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_dummy_check_missing_firmware, MENU_ENUM_SUBLABEL_CHECK_FOR_MISSING_FIRMWARE) +DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_core_info_cache_enable, MENU_ENUM_SUBLABEL_CORE_INFO_CACHE_ENABLE) #ifndef HAVE_DYNAMIC DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_always_reload_core_on_run_content, MENU_ENUM_SUBLABEL_ALWAYS_RELOAD_CORE_ON_RUN_CONTENT) #endif @@ -3403,6 +3404,9 @@ int menu_cbs_init_bind_sublabel(menu_file_list_cbs_t *cbs, case MENU_ENUM_LABEL_CHECK_FOR_MISSING_FIRMWARE: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_dummy_check_missing_firmware); break; + case MENU_ENUM_LABEL_CORE_INFO_CACHE_ENABLE: + BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_core_info_cache_enable); + break; #ifndef HAVE_DYNAMIC case MENU_ENUM_LABEL_ALWAYS_RELOAD_CORE_ON_RUN_CONTENT: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_always_reload_core_on_run_content); diff --git a/menu/menu_displaylist.c b/menu/menu_displaylist.c index cc14b36a23..02df4ca3cf 100644 --- a/menu/menu_displaylist.c +++ b/menu/menu_displaylist.c @@ -8618,11 +8618,12 @@ unsigned menu_displaylist_build_list( case DISPLAYLIST_CORE_SETTINGS_LIST: { menu_displaylist_build_info_t build_list[] = { - {MENU_ENUM_LABEL_VIDEO_SHARED_CONTEXT, PARSE_ONLY_BOOL}, - {MENU_ENUM_LABEL_DRIVER_SWITCH_ENABLE, PARSE_ONLY_BOOL}, - {MENU_ENUM_LABEL_DUMMY_ON_CORE_SHUTDOWN, PARSE_ONLY_BOOL}, - {MENU_ENUM_LABEL_CHECK_FOR_MISSING_FIRMWARE, PARSE_ONLY_BOOL}, - {MENU_ENUM_LABEL_VIDEO_ALLOW_ROTATE, PARSE_ONLY_BOOL}, + {MENU_ENUM_LABEL_VIDEO_SHARED_CONTEXT, PARSE_ONLY_BOOL}, + {MENU_ENUM_LABEL_DRIVER_SWITCH_ENABLE, PARSE_ONLY_BOOL}, + {MENU_ENUM_LABEL_DUMMY_ON_CORE_SHUTDOWN, PARSE_ONLY_BOOL}, + {MENU_ENUM_LABEL_CHECK_FOR_MISSING_FIRMWARE, PARSE_ONLY_BOOL}, + {MENU_ENUM_LABEL_VIDEO_ALLOW_ROTATE, PARSE_ONLY_BOOL}, + {MENU_ENUM_LABEL_CORE_INFO_CACHE_ENABLE, PARSE_ONLY_BOOL}, #ifndef HAVE_DYNAMIC {MENU_ENUM_LABEL_ALWAYS_RELOAD_CORE_ON_RUN_CONTENT, PARSE_ONLY_BOOL}, #endif diff --git a/menu/menu_setting.c b/menu/menu_setting.c index b6fd84cab3..ad305b9490 100644 --- a/menu/menu_setting.c +++ b/menu/menu_setting.c @@ -7809,6 +7809,20 @@ static void general_write_handler(rarch_setting_t *setting) task_push_wifi_disable(NULL); #endif break; + case MENU_ENUM_LABEL_CORE_INFO_CACHE_ENABLE: + { + settings_t *settings = config_get_ptr(); + const char *dir_libretro = settings->paths.directory_libretro; + const char *path_libretro_info = settings->paths.path_libretro_info; + + /* When enabling the core info cache, + * force a cache refresh on the next + * core info initialisation */ + if (*setting->value.target.boolean) + core_info_cache_force_refresh(!string_is_empty(path_libretro_info) ? + path_libretro_info : dir_libretro); + } + break; default: break; } @@ -9327,9 +9341,9 @@ static bool setting_append_list( { unsigned i, listing = 0; #ifndef HAVE_DYNAMIC - struct bool_entry bool_entries[7]; + struct bool_entry bool_entries[8]; #else - struct bool_entry bool_entries[6]; + struct bool_entry bool_entries[7]; #endif START_GROUP(list, list_info, &group_info, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_CORE_SETTINGS), parent_group); @@ -9384,6 +9398,13 @@ static bool setting_append_list( bool_entries[listing].flags = SD_FLAG_ADVANCED; listing++; + bool_entries[listing].target = &settings->bools.core_info_cache_enable; + bool_entries[listing].name_enum_idx = MENU_ENUM_LABEL_CORE_INFO_CACHE_ENABLE; + bool_entries[listing].SHORT_enum_idx = MENU_ENUM_LABEL_VALUE_CORE_INFO_CACHE_ENABLE; + bool_entries[listing].default_value = DEFAULT_CORE_INFO_CACHE_ENABLE; + bool_entries[listing].flags = SD_FLAG_NONE; + listing++; + #ifndef HAVE_DYNAMIC bool_entries[listing].target = &settings->bools.always_reload_core_on_run_content; bool_entries[listing].name_enum_idx = MENU_ENUM_LABEL_ALWAYS_RELOAD_CORE_ON_RUN_CONTENT; diff --git a/msg_hash.h b/msg_hash.h index f04332229f..a0433b8cf5 100644 --- a/msg_hash.h +++ b/msg_hash.h @@ -2121,6 +2121,7 @@ enum msg_hash_enums MENU_LABEL(DUMMY_ON_CORE_SHUTDOWN), MENU_LABEL(CHECK_FOR_MISSING_FIRMWARE), + MENU_LABEL(CORE_INFO_CACHE_ENABLE), #ifndef HAVE_DYNAMIC MENU_LABEL(ALWAYS_RELOAD_CORE_ON_RUN_CONTENT), #endif diff --git a/retroarch.c b/retroarch.c index ad9faf0c36..b82cc1c540 100644 --- a/retroarch.c +++ b/retroarch.c @@ -14131,6 +14131,7 @@ bool command_event(enum event_command cmd, void *data) const char *dir_libretro = settings->paths.directory_libretro; const char *path_libretro_info = settings->paths.path_libretro_info; bool show_hidden_files = settings->bools.show_hidden_files; + bool core_info_cache_enable = settings->bools.core_info_cache_enable; ext_name[0] = '\0'; @@ -14143,7 +14144,8 @@ bool command_event(enum event_command cmd, void *data) core_info_init_list(path_libretro_info, dir_libretro, ext_name, - show_hidden_files + show_hidden_files, + core_info_cache_enable ); } break; diff --git a/samples/tasks/database/main.c b/samples/tasks/database/main.c index a26f05236c..3756a5db6f 100644 --- a/samples/tasks/database/main.c +++ b/samples/tasks/database/main.c @@ -66,7 +66,7 @@ int main(int argc, char *argv[]) #else task_queue_init(false /* threaded enable */, main_msg_queue_push); #endif - core_info_init_list(core_info_dir, core_dir, exts, true); + core_info_init_list(core_info_dir, core_dir, exts, true, false); task_push_dbscan(playlist_dir, db_dir, input_dir, true, true, main_db_cb);