From c37f540b72e435a689f5c1b4fd8fdca4eb96cccd Mon Sep 17 00:00:00 2001 From: jdgleaver Date: Thu, 6 May 2021 11:58:45 +0100 Subject: [PATCH] Core Info Cache Improvements - Core info cache can now be enabled/disabled on all platforms via a new `Settings > Core > Cache Core Info Files` option - Core info cache file has been renamed from `.cache` to `core_info.cache` (i.e. it is no longer a 'hidden' file on Unix platforms, so can be deleted easily) - The core info cache file is now compressed (rzip) to further reduce disk IO - The presence of a `core_info.refresh` file in the core info directory will force a one-time refresh of the info cache. This file is generated automatically when toggling on the `Cache Core Info Files` option, and we will also add it to core info file packaging such that updating info files (either manually or via the online updater) will force a refresh - The core info cache no longer contains 'core is locked' and 'firmware missing' data fields; these are 'dynamic' properties that must be determined at runtime - The 'core is locked' status is now determined on core info intialisation by parsing the core directory listing, rather than by performing indivdual 'lock file exists' checks. This minimises file IO, and greatly improves performance on devices with slow storage - While parsing the core info cache file, we now avoid unnecessary `strdup()`s when adding entries to the resultant cache list - Memory leaks (potential and real) have been fixed, and safety checks added - Build errors have been fixed --- config.def.h | 9 + configuration.c | 1 + configuration.h | 1 + core_info.c | 2246 ++++++++++++++++++++------------- core_info.h | 22 +- file_path_special.h | 3 + intl/msg_hash_lbl.h | 4 + intl/msg_hash_us.h | 8 + menu/cbs/menu_cbs_sublabel.c | 4 + menu/menu_displaylist.c | 11 +- menu/menu_setting.c | 25 +- msg_hash.h | 1 + retroarch.c | 4 +- samples/tasks/database/main.c | 2 +- 14 files changed, 1427 insertions(+), 914 deletions(-) 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);