diff --git a/Makefile.common b/Makefile.common index 5b9947dd28..6d3aa84f8f 100644 --- a/Makefile.common +++ b/Makefile.common @@ -277,6 +277,7 @@ OBJ += \ OBJ += \ $(LIBRETRO_COMM_DIR)/lists/linked_list.o \ + $(LIBRETRO_COMM_DIR)/lists/nested_list.o \ $(LIBRETRO_COMM_DIR)/queues/generic_queue.o ifneq ($(findstring Linux,$(OS)),) @@ -336,6 +337,7 @@ endif OBJ += \ core_info.o \ core_backup.o \ + core_option_manager.o \ $(LIBRETRO_COMM_DIR)/file/config_file.o \ $(LIBRETRO_COMM_DIR)/file/config_file_userdata.o \ runtime_file.o \ diff --git a/core_option_manager.c b/core_option_manager.c new file mode 100644 index 0000000000..8e8f1750a9 --- /dev/null +++ b/core_option_manager.c @@ -0,0 +1,1752 @@ +/* RetroArch - A frontend for libretro. + * Copyright (C) 2010-2014 - Hans-Kristian Arntzen + * Copyright (C) 2011-2017 - Daniel De Matteis + * + * RetroArch is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with RetroArch. + * If not, see . + */ + +#include + +#ifdef HAVE_CHEEVOS +#include "cheevos/cheevos.h" +#endif + +#include "core_option_manager.h" + +#define CORE_OPTION_MANAGER_MAP_TAG "#" +#define CORE_OPTION_MANAGER_MAP_DELIM ":" + +/*********************/ +/* Option Conversion */ +/*********************/ + +/** + * core_option_manager_convert_v1: + * + * @options_v1 : an array of retro_core_option_definition + * structs + * + * Converts an array of core option v1 definitions into + * a v2 core options struct. Returned pointer must be + * freed using core_option_manager_free_converted(). + * + * Returns: Valid pointer to a new v2 core options struct + * if successful, otherwise NULL. + **/ +struct retro_core_options_v2 *core_option_manager_convert_v1( + const struct retro_core_option_definition *options_v1) +{ + size_t i; + size_t num_options = 0; + struct retro_core_options_v2 *options_v2 = NULL; + struct retro_core_option_v2_definition *option_v2_defs = NULL; + + if (!options_v1) + return NULL; + + /* Determine number of options */ + for (;;) + { + if (string_is_empty(options_v1[num_options].key)) + break; + num_options++; + } + + if (num_options < 1) + return NULL; + + /* Allocate output retro_core_options_v2 struct */ + options_v2 = (struct retro_core_options_v2 *) + malloc(sizeof(*options_v2)); + if (!options_v2) + return NULL; + + /* Note: v1 options have no concept of + * categories, so this field will be left + * as NULL */ + options_v2->categories = NULL; + options_v2->definitions = NULL; + + /* Allocate output option_v2_defs array + * > One extra entry required for terminating NULL entry + * > Note that calloc() sets terminating NULL entry and + * correctly 'nullifies' each values array */ + option_v2_defs = (struct retro_core_option_v2_definition *) + calloc(num_options + 1, sizeof(*option_v2_defs)); + + if (!option_v2_defs) + { + free(options_v2); + return NULL; + } + + options_v2->definitions = option_v2_defs; + + /* Loop through options... */ + for (i = 0; i < num_options; i++) + { + size_t j; + size_t num_values = 0; + + /* Set key */ + option_v2_defs[i].key = options_v1[i].key; + + /* Set default value */ + option_v2_defs[i].default_value = options_v1[i].default_value; + + /* Set desc and info strings */ + option_v2_defs[i].desc = options_v1[i].desc; + option_v2_defs[i].info = options_v1[i].info; + + /* v1 options have no concept of categories + * (Note: These are already nullified by + * the preceding calloc(), but we do it + * explicitly here for code clarity) */ + option_v2_defs[i].desc_categorized = NULL; + option_v2_defs[i].info_categorized = NULL; + option_v2_defs[i].category_key = NULL; + + /* Determine number of values */ + for (;;) + { + if (string_is_empty(options_v1[i].values[num_values].value)) + break; + num_values++; + } + + /* Copy values */ + for (j = 0; j < num_values; j++) + { + /* Set value string */ + option_v2_defs[i].values[j].value = options_v1[i].values[j].value; + + /* Set value label string */ + option_v2_defs[i].values[j].label = options_v1[i].values[j].label; + } + } + + return options_v2; +} + +/** + * core_option_manager_convert_v1_intl: + * + * @options_v1_intl : pointer to a retro_core_options_intl + * struct + * + * Converts a v1 'international' core options definition + * struct into a v2 core options struct. Returned pointer + * must be freed using core_option_manager_free_converted(). + * + * Returns: Valid pointer to a new v2 core options struct + * if successful, otherwise NULL. + **/ +struct retro_core_options_v2 *core_option_manager_convert_v1_intl( + const struct retro_core_options_intl *options_v1_intl) +{ + size_t i; + size_t num_options = 0; + struct retro_core_option_definition *option_defs_us = NULL; + struct retro_core_option_definition *option_defs_local = NULL; + struct retro_core_options_v2 *options_v2 = NULL; + struct retro_core_option_v2_definition *option_v2_defs = NULL; + + if (!options_v1_intl) + return NULL; + + option_defs_us = options_v1_intl->us; + option_defs_local = options_v1_intl->local; + + if (!option_defs_us) + return NULL; + + /* Determine number of options */ + for (;;) + { + if (string_is_empty(option_defs_us[num_options].key)) + break; + num_options++; + } + + if (num_options < 1) + return NULL; + + /* Allocate output retro_core_options_v2 struct */ + options_v2 = (struct retro_core_options_v2 *) + malloc(sizeof(*options_v2)); + if (!options_v2) + return NULL; + + /* Note: v1 options have no concept of + * categories, so this field will be left + * as NULL */ + options_v2->categories = NULL; + options_v2->definitions = NULL; + + /* Allocate output option_v2_defs array + * > One extra entry required for terminating NULL entry + * > Note that calloc() sets terminating NULL entry and + * correctly 'nullifies' each values array */ + option_v2_defs = (struct retro_core_option_v2_definition *) + calloc(num_options + 1, sizeof(*option_v2_defs)); + + if (!option_v2_defs) + { + core_option_manager_free_converted(options_v2); + return NULL; + } + + options_v2->definitions = option_v2_defs; + + /* Loop through options... */ + for (i = 0; i < num_options; i++) + { + size_t j; + size_t num_values = 0; + const char *key = option_defs_us[i].key; + const char *local_desc = NULL; + const char *local_info = NULL; + struct retro_core_option_value *local_values = NULL; + + /* Key is always taken from us english defs */ + option_v2_defs[i].key = key; + + /* Default value is always taken from us english defs */ + option_v2_defs[i].default_value = option_defs_us[i].default_value; + + /* Try to find corresponding entry in local defs array */ + if (option_defs_local) + { + size_t index = 0; + + for (;;) + { + const char *local_key = option_defs_local[index].key; + + if (string_is_empty(local_key)) + break; + + if (string_is_equal(key, local_key)) + { + local_desc = option_defs_local[index].desc; + local_info = option_defs_local[index].info; + local_values = option_defs_local[index].values; + break; + } + + index++; + } + } + + /* Set desc and info strings */ + option_v2_defs[i].desc = string_is_empty(local_desc) ? + option_defs_us[i].desc : local_desc; + option_v2_defs[i].info = string_is_empty(local_info) ? + option_defs_us[i].info : local_info; + + /* v1 options have no concept of categories + * (Note: These are already nullified by + * the preceding calloc(), but we do it + * explicitly here for code clarity) */ + option_v2_defs[i].desc_categorized = NULL; + option_v2_defs[i].info_categorized = NULL; + option_v2_defs[i].category_key = NULL; + + /* Determine number of values + * (always taken from us english defs) */ + for (;;) + { + if (string_is_empty(option_defs_us[i].values[num_values].value)) + break; + num_values++; + } + + /* Copy values */ + for (j = 0; j < num_values; j++) + { + const char *value = option_defs_us[i].values[j].value; + const char *local_label = NULL; + + /* Value string is always taken from us english defs */ + option_v2_defs[i].values[j].value = value; + + /* Try to find corresponding entry in local defs values array */ + if (local_values) + { + size_t value_index = 0; + + for (;;) + { + const char *local_value = local_values[value_index].value; + + if (string_is_empty(local_value)) + break; + + if (string_is_equal(value, local_value)) + { + local_label = local_values[value_index].label; + break; + } + + value_index++; + } + } + + /* Set value label string */ + option_v2_defs[i].values[j].label = string_is_empty(local_label) ? + option_defs_us[i].values[j].label : local_label; + } + } + + return options_v2; +} + +/** + * core_option_manager_convert_v2_intl: + * + * @options_v2_intl : pointer to a retro_core_options_v2_intl + * struct + * + * Converts a v2 'international' core options struct + * into a regular v2 core options struct. Returned pointer + * must be freed using core_option_manager_free_converted(). + * + * Returns: Valid pointer to a new v2 core options struct + * if successful, otherwise NULL. + **/ +struct retro_core_options_v2 *core_option_manager_convert_v2_intl( + const struct retro_core_options_v2_intl *options_v2_intl) +{ + size_t i; + size_t num_categories = 0; + size_t num_options = 0; + struct retro_core_options_v2 *options_v2_us = NULL; + struct retro_core_options_v2 *options_v2_local = NULL; + struct retro_core_options_v2 *options_v2 = NULL; + struct retro_core_option_v2_category *option_v2_cats = NULL; + struct retro_core_option_v2_definition *option_v2_defs = NULL; + + if (!options_v2_intl) + return NULL; + + options_v2_us = options_v2_intl->us; + options_v2_local = options_v2_intl->local; + + if (!options_v2_us || + !options_v2_us->definitions) + return NULL; + + /* Determine number of categories + * (Note: zero categories are permitted) */ + if (options_v2_us->categories) + { + for (;;) + { + if (string_is_empty(options_v2_us->categories[num_categories].key)) + break; + num_categories++; + } + } + + /* Determine number of options */ + for (;;) + { + if (string_is_empty(options_v2_us->definitions[num_options].key)) + break; + num_options++; + } + + if (num_options < 1) + return NULL; + + /* Allocate output retro_core_options_v2 struct */ + options_v2 = (struct retro_core_options_v2 *) + malloc(sizeof(*options_v2)); + if (!options_v2) + return NULL; + + options_v2->categories = NULL; + options_v2->definitions = NULL; + + /* Allocate output option_v2_cats array + * > One extra entry required for terminating NULL entry + * > Note that calloc() sets terminating NULL entry */ + if (num_categories > 0) + { + option_v2_cats = (struct retro_core_option_v2_category *) + calloc(num_categories + 1, sizeof(*option_v2_cats)); + + if (!option_v2_cats) + { + core_option_manager_free_converted(options_v2); + return NULL; + } + } + + options_v2->categories = option_v2_cats; + + /* Allocate output option_v2_defs array + * > One extra entry required for terminating NULL entry + * > Note that calloc() sets terminating NULL entry and + * correctly 'nullifies' each values array */ + option_v2_defs = (struct retro_core_option_v2_definition *) + calloc(num_options + 1, sizeof(*option_v2_defs)); + + if (!option_v2_defs) + { + core_option_manager_free_converted(options_v2); + return NULL; + } + + options_v2->definitions = option_v2_defs; + + /* Loop through categories... + * (Note: This loop will not execute if + * options_v2_us->categories is NULL) */ + for (i = 0; i < num_categories; i++) + { + const char *key = options_v2_us->categories[i].key; + const char *local_desc = NULL; + const char *local_info = NULL; + + /* Key is always taken from us english + * categories */ + option_v2_cats[i].key = key; + + /* Try to find corresponding entry in local + * categories array */ + if (options_v2_local && + options_v2_local->categories) + { + size_t index = 0; + + for (;;) + { + const char *local_key = options_v2_local->categories[index].key; + + if (string_is_empty(local_key)) + break; + + if (string_is_equal(key, local_key)) + { + local_desc = options_v2_local->categories[index].desc; + local_info = options_v2_local->categories[index].info; + break; + } + + index++; + } + } + + /* Set desc and info strings */ + option_v2_cats[i].desc = string_is_empty(local_desc) ? + options_v2_us->categories[i].desc : local_desc; + option_v2_cats[i].info = string_is_empty(local_info) ? + options_v2_us->categories[i].info : local_info; + + } + + /* Loop through options... */ + for (i = 0; i < num_options; i++) + { + size_t j; + size_t num_values = 0; + const char *key = options_v2_us->definitions[i].key; + const char *local_desc = NULL; + const char *local_desc_categorized = NULL; + const char *local_info = NULL; + const char *local_info_categorized = NULL; + struct retro_core_option_value *local_values = NULL; + + /* Key is always taken from us english defs */ + option_v2_defs[i].key = key; + + /* Default value is always taken from us english defs */ + option_v2_defs[i].default_value = options_v2_us->definitions[i].default_value; + + /* Try to find corresponding entry in local defs array */ + if (options_v2_local && + options_v2_local->definitions) + { + size_t index = 0; + + for (;;) + { + const char *local_key = options_v2_local->definitions[index].key; + + if (string_is_empty(local_key)) + break; + + if (string_is_equal(key, local_key)) + { + local_desc = options_v2_local->definitions[index].desc; + local_desc_categorized = options_v2_local->definitions[index].desc_categorized; + local_info = options_v2_local->definitions[index].info; + local_info_categorized = options_v2_local->definitions[index].info_categorized; + local_values = options_v2_local->definitions[index].values; + break; + } + + index++; + } + } + + /* Set desc and info strings */ + option_v2_defs[i].desc = string_is_empty(local_desc) ? + options_v2_us->definitions[i].desc : local_desc; + option_v2_defs[i].desc_categorized = string_is_empty(local_desc_categorized) ? + options_v2_us->definitions[i].desc_categorized : local_desc_categorized; + option_v2_defs[i].info = string_is_empty(local_info) ? + options_v2_us->definitions[i].info : local_info; + option_v2_defs[i].info_categorized = string_is_empty(local_info_categorized) ? + options_v2_us->definitions[i].info_categorized : local_info_categorized; + + /* Category key is always taken from us english defs */ + option_v2_defs[i].category_key = options_v2_us->definitions[i].category_key; + + /* Determine number of values + * (always taken from us english defs) */ + for (;;) + { + if (string_is_empty( + options_v2_us->definitions[i].values[num_values].value)) + break; + num_values++; + } + + /* Copy values */ + for (j = 0; j < num_values; j++) + { + const char *value = options_v2_us->definitions[i].values[j].value; + const char *local_label = NULL; + + /* Value string is always taken from us english defs */ + option_v2_defs[i].values[j].value = value; + + /* Try to find corresponding entry in local defs values array */ + if (local_values) + { + size_t value_index = 0; + + for (;;) + { + const char *local_value = local_values[value_index].value; + + if (string_is_empty(local_value)) + break; + + if (string_is_equal(value, local_value)) + { + local_label = local_values[value_index].label; + break; + } + + value_index++; + } + } + + /* Set value label string */ + option_v2_defs[i].values[j].label = string_is_empty(local_label) ? + options_v2_us->definitions[i].values[j].label : local_label; + } + } + + return options_v2; +} + +/** + * core_option_manager_convert_v2_intl: + * + * @options_v2 : pointer to a retro_core_options_v2 + * struct + * + * Frees the pointer returned by any + * core_option_manager_convert_*() function. + **/ +void core_option_manager_free_converted( + struct retro_core_options_v2 *options_v2) +{ + if (!options_v2) + return; + + if (options_v2->categories) + { + free(options_v2->categories); + options_v2->categories = NULL; + } + + if (options_v2->definitions) + { + free(options_v2->definitions); + options_v2->definitions = NULL; + } + + free(options_v2); +} + +/**************************************/ +/* Initialisation / De-Initialisation */ +/**************************************/ + +/* Sanitises a core option value label, handling the case + * where an explicit label is not provided and performing + * conversion of various true/false identifiers to + * ON/OFF strings */ +static const char *core_option_manager_parse_value_label( + const char *value, const char *value_label) +{ + /* 'value_label' may be NULL */ + const char *label = string_is_empty(value_label) ? + value : value_label; + + if (string_is_empty(label)) + return NULL; + + /* Any label starting with a digit (or +/-) + * cannot be a boolean string, and requires + * no further processing */ + if (ISDIGIT((unsigned char)*label) || + (*label == '+') || + (*label == '-')) + return label; + + /* Core devs have a habit of using arbitrary + * strings to label boolean values (i.e. enabled, + * Enabled, on, On, ON, true, True, TRUE, disabled, + * Disabled, off, Off, OFF, false, False, FALSE). + * These should all be converted to standard ON/OFF + * strings + * > Note: We require some duplication here + * (e.g. MENU_ENUM_LABEL_ENABLED *and* + * MENU_ENUM_LABEL_VALUE_ENABLED) in order + * to match both localised and non-localised + * strings. This function is not performance + * critical, so these extra comparisons do + * no harm */ + if (string_is_equal_noncase(label, msg_hash_to_str(MENU_ENUM_LABEL_ENABLED)) || + string_is_equal_noncase(label, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_ENABLED)) || + string_is_equal_noncase(label, "enable") || + string_is_equal_noncase(label, "on") || + string_is_equal_noncase(label, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_ON)) || + string_is_equal_noncase(label, "true") || + string_is_equal_noncase(label, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_TRUE))) + label = msg_hash_to_str(MENU_ENUM_LABEL_VALUE_ON); + else if (string_is_equal_noncase(label, msg_hash_to_str(MENU_ENUM_LABEL_DISABLED)) || + string_is_equal_noncase(label, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_DISABLED)) || + string_is_equal_noncase(label, "disable") || + string_is_equal_noncase(label, "off") || + string_is_equal_noncase(label, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_OFF)) || + string_is_equal_noncase(label, "false") || + string_is_equal_noncase(label, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_FALSE))) + label = msg_hash_to_str(MENU_ENUM_LABEL_VALUE_OFF); + + return label; +} + +/* Parses a single legacy core options interface + * variable, extracting all present core_option + * information */ +static bool core_option_manager_parse_variable( + core_option_manager_t *opt, size_t idx, + const struct retro_variable *var, + config_file_t *config_src) +{ + size_t i; + union string_list_elem_attr attr; + const char *val_start = NULL; + char *value = NULL; + char *desc_end = NULL; + struct core_option *option = (struct core_option*)&opt->opts[idx]; + struct config_entry_list + *entry = NULL; + + /* Record option index (required to facilitate + * option map handling) */ + option->opt_idx = idx; + + /* All options are visible by default */ + option->visible = true; + + if (!string_is_empty(var->key)) + option->key = strdup(var->key); + if (!string_is_empty(var->value)) + value = strdup(var->value); + + if (!string_is_empty(value)) + desc_end = strstr(value, "; "); + + if (!desc_end) + goto error; + + *desc_end = '\0'; + + if (!string_is_empty(value)) + option->desc = strdup(value); + + val_start = desc_end + 2; + option->vals = string_split(val_start, "|"); + + if (!option->vals) + goto error; + + /* Legacy core option interface has no concept + * of value labels + * > Use actual values for display purposes */ + attr.i = 0; + option->val_labels = string_list_new(); + + if (!option->val_labels) + goto error; + + /* > Loop over values and 'extract' labels */ + for (i = 0; i < option->vals->size; i++) + { + const char *value = option->vals->elems[i].data; + const char *value_label = core_option_manager_parse_value_label( + value, NULL); + + /* Redundant safely check... */ + value_label = string_is_empty(value_label) ? + value : value_label; + + /* Append value label string */ + string_list_append(option->val_labels, value_label, attr); + } + + /* Legacy core option interface always uses first + * defined value as the default */ + option->default_index = 0; + option->index = 0; + + if (config_src) + entry = config_get_entry(config_src, option->key); + else + entry = config_get_entry(opt->conf, option->key); + + /* Set current config value */ + if (entry && !string_is_empty(entry->value)) + { + for (i = 0; i < option->vals->size; i++) + { + if (string_is_equal(option->vals->elems[i].data, entry->value)) + { + option->index = i; + break; + } + } + } + + /* Legacy core option interface has no concept + * of categories */ + option->desc_categorized = NULL; + option->info_categorized = NULL; + option->category_key = NULL; + + free(value); + return true; + +error: + free(value); + return false; +} + +/** + * core_option_manager_new_vars: + * + * @conf_path : Filesystem path to write core option + * config file to + * @src_conf_path : Filesystem path from which to load + * initial config settings. + * @vars : Pointer to core option variable array + * handle + * + * Legacy version of core_option_manager_new(). + * Creates and initializes a core manager handle. + * + * Returns: handle to new core manager handle if successful, + * otherwise NULL. + **/ +core_option_manager_t *core_option_manager_new_vars( + const char *conf_path, const char *src_conf_path, + const struct retro_variable *vars) +{ + const struct retro_variable *var = NULL; + size_t size = 0; + config_file_t *config_src = NULL; + core_option_manager_t *opt = NULL; + + if (!vars) + return NULL; + + opt = (core_option_manager_t*)malloc(sizeof(*opt)); + + if (!opt) + return NULL; + + opt->conf = NULL; + opt->conf_path[0] = '\0'; + /* Legacy core option interface has no concept + * of categories, so leave opt->cats as NULL + * and opt->cats_size as zero */ + opt->cats = NULL; + opt->cats_size = 0; + opt->opts = NULL; + opt->size = 0; + opt->option_map = nested_list_init(); + opt->updated = false; + + if (!opt->option_map) + goto error; + + /* Open 'output' config file */ + if (!string_is_empty(conf_path)) + if (!(opt->conf = config_file_new_from_path_to_string(conf_path))) + if (!(opt->conf = config_file_new_alloc())) + goto error; + + strlcpy(opt->conf_path, conf_path, sizeof(opt->conf_path)); + + /* Load source config file, if required */ + if (!string_is_empty(src_conf_path)) + config_src = config_file_new_from_path_to_string(src_conf_path); + + /* Get number of variables */ + for (var = vars; var->key && var->value; var++) + size++; + + if (size == 0) + goto error; + + /* Create options array */ + opt->opts = (struct core_option*)calloc(size, sizeof(*opt->opts)); + if (!opt->opts) + goto error; + + opt->size = size; + size = 0; + + /* Parse each variable */ + for (var = vars; var->key && var->value; size++, var++) + { + if (core_option_manager_parse_variable(opt, size, var, config_src)) + { + /* If variable is read correctly, add it to + * the map */ + char address[256]; + + address[0] = '\0'; + + /* Address string is normally: + * + * ...where is prepended to the option + * key in order to avoid category/option key + * collisions. Legacy options have no categories, + * so we could just set the address to + * - but for consistency with + * 'modern' options, we apply the tag regardless */ + snprintf(address, sizeof(address), + CORE_OPTION_MANAGER_MAP_TAG "%s", var->key); + + if (!nested_list_add_item(opt->option_map, + address, NULL, (const void*)&opt->opts[size])) + goto error; + } + else + goto error; + } + + if (config_src) + config_file_free(config_src); + + return opt; + +error: + if (config_src) + config_file_free(config_src); + core_option_manager_free(opt); + return NULL; +} + +/* Parses a single v2 core options interface + * option, extracting all present core_option + * information */ +static bool core_option_manager_parse_option( + core_option_manager_t *opt, size_t idx, + const struct retro_core_option_v2_definition *option_def, + config_file_t *config_src) +{ + size_t i; + union string_list_elem_attr attr; + struct config_entry_list + *entry = NULL; + size_t num_vals = 0; + struct core_option *option = (struct core_option*)&opt->opts[idx]; + const char *key = option_def->key; + const char *category_key = option_def->category_key; + const struct retro_core_option_value + *values = option_def->values; + + /* Record option index (required to facilitate + * option map handling) */ + option->opt_idx = idx; + + /* All options are visible by default */ + option->visible = true; + + if (!string_is_empty(option_def->desc)) + option->desc = strdup(option_def->desc); + + if (!string_is_empty(option_def->info)) + option->info = strdup(option_def->info); + + /* Set category-related parameters + * > Ignore if specified category key does not + * match an entry in the categories array + * > Category key cannot contain a map delimiter + * character */ + if (opt->cats && + !string_is_empty(category_key) && + !strstr(category_key, CORE_OPTION_MANAGER_MAP_DELIM)) + { + for (i = 0; i < opt->cats_size; i++) + { + const char *search_key = opt->cats[i].key; + + if (string_is_empty(search_key)) + break; + + if (string_is_equal(search_key, category_key)) + { + option->category_key = strdup(category_key); + + if (!string_is_empty(option_def->desc_categorized)) + option->desc_categorized = strdup(option_def->desc_categorized); + + if (!string_is_empty(option_def->info_categorized)) + option->info_categorized = strdup(option_def->info_categorized); + + break; + } + } + } + + /* Have to set key *after* checking for option + * categories */ + if (!string_is_empty(option_def->key)) + { + /* If option has a category, option key + * cannot contain a map delimiter character */ + if (!string_is_empty(option->category_key) && + strstr(key, CORE_OPTION_MANAGER_MAP_DELIM)) + return false; + + option->key = strdup(key); + } + + /* Get number of values */ + for (;;) + { + if (string_is_empty(values[num_vals].value)) + break; + num_vals++; + } + + if (num_vals < 1) + return false; + + /* Initialise string lists */ + attr.i = 0; + option->vals = string_list_new(); + option->val_labels = string_list_new(); + + if (!option->vals || !option->val_labels) + return false; + + /* Initialise default value */ + option->default_index = 0; + option->index = 0; + + /* Extract value/label pairs */ + for (i = 0; i < num_vals; i++) + { + const char *value = values[i].value; + const char *value_label = values[i].label; + + /* Append value string + * > We know that 'value' is always valid */ + string_list_append(option->vals, value, attr); + + /* Value label requires additional processing */ + value_label = core_option_manager_parse_value_label( + value, value_label); + + /* > Redundant safely check... */ + value_label = string_is_empty(value_label) ? + value : value_label; + + /* Append value label string */ + string_list_append(option->val_labels, value_label, attr); + + /* Check whether this value is the default setting */ + if (!string_is_empty(option_def->default_value)) + { + if (string_is_equal(option_def->default_value, value)) + { + option->default_index = i; + option->index = i; + } + } + } + + if (config_src) + entry = config_get_entry(config_src, option->key); + else + entry = config_get_entry(opt->conf, option->key); + + /* Set current config value */ + if (entry && !string_is_empty(entry->value)) + { + for (i = 0; i < option->vals->size; i++) + { + if (string_is_equal(option->vals->elems[i].data, entry->value)) + { + option->index = i; + break; + } + } + } + + return true; +} + +/** + * core_option_manager_new: + * + * @conf_path : Filesystem path to write core option + * config file to + * @src_conf_path : Filesystem path from which to load + * initial config settings. + * @options_v2 : Pointer to retro_core_options_v2 struct + * + * Creates and initializes a core manager handle. Parses + * information from a retro_core_options_v2 struct. + * + * Returns: handle to new core manager handle if successful, + * otherwise NULL. + **/ +core_option_manager_t *core_option_manager_new( + const char *conf_path, const char *src_conf_path, + const struct retro_core_options_v2 *options_v2) +{ + const struct retro_core_option_v2_category *option_cat = NULL; + const struct retro_core_option_v2_definition *option_def = NULL; + struct retro_core_option_v2_category *option_cats = NULL; + struct retro_core_option_v2_definition *option_defs = NULL; + size_t cats_size = 0; + size_t size = 0; + config_file_t *config_src = NULL; + core_option_manager_t *opt = NULL; + + if (!options_v2 || + !options_v2->definitions) + return NULL; + + option_cats = options_v2->categories; + option_defs = options_v2->definitions; + + opt = (core_option_manager_t*)malloc(sizeof(*opt)); + + if (!opt) + return NULL; + + opt->conf = NULL; + opt->conf_path[0] = '\0'; + opt->cats = NULL; + opt->cats_size = 0; + opt->opts = NULL; + opt->size = 0; + opt->option_map = nested_list_init(); + opt->updated = false; + + if (!opt->option_map) + goto error; + + /* Open 'output' config file */ + if (!string_is_empty(conf_path)) + if (!(opt->conf = config_file_new_from_path_to_string(conf_path))) + if (!(opt->conf = config_file_new_alloc())) + goto error; + + strlcpy(opt->conf_path, conf_path, sizeof(opt->conf_path)); + + /* Load source config file, if required */ + if (!string_is_empty(src_conf_path)) + config_src = config_file_new_from_path_to_string(src_conf_path); + + /* Get number of categories + * > Note: 'option_cat->info == NULL' is valid */ + if (option_cats) + { + for (option_cat = option_cats; + !string_is_empty(option_cat->key) && + !string_is_empty(option_cat->desc); + option_cat++) + cats_size++; + } + + /* Get number of options + * > Note: 'option_def->info == NULL' is valid */ + for (option_def = option_defs; + option_def->key && option_def->desc && option_def->values[0].value; + option_def++) + size++; + + if (size == 0) + goto error; + + /* Create categories array */ + if (cats_size > 0) + { + opt->cats = (struct core_catagory*)calloc(size, sizeof(*opt->cats)); + if (!opt->cats) + goto error; + + opt->cats_size = cats_size; + cats_size = 0; + + /* Parse each category + * > Note: 'option_cat->info == NULL' is valid */ + for (option_cat = option_cats; + !string_is_empty(option_cat->key) && + !string_is_empty(option_cat->desc); + cats_size++, option_cat++) + { + opt->cats[cats_size].key = strdup(option_cat->key); + opt->cats[cats_size].desc = strdup(option_cat->desc); + + if (!string_is_empty(option_cat->info)) + opt->cats[cats_size].info = strdup(option_cat->info); + } + } + + /* Create options array */ + opt->opts = (struct core_option*)calloc(size, sizeof(*opt->opts)); + if (!opt->opts) + goto error; + + opt->size = size; + size = 0; + + /* Parse each option + * > Note: 'option_def->info == NULL' is valid */ + for (option_def = option_defs; + option_def->key && option_def->desc && option_def->values[0].value; + size++, option_def++) + { + if (core_option_manager_parse_option(opt, size, option_def, config_src)) + { + /* If option is read correctly, add it to + * the map */ + const char *category_key = opt->opts[size].category_key; + char address[256]; + + address[0] = '\0'; + + /* Address string is nominally: + * + * ...where is prepended to the option + * key in order to avoid category/option key + * collisions */ + if (string_is_empty(category_key)) + snprintf(address, sizeof(address), + CORE_OPTION_MANAGER_MAP_TAG "%s", option_def->key); + else + snprintf(address, sizeof(address), + "%s" CORE_OPTION_MANAGER_MAP_DELIM CORE_OPTION_MANAGER_MAP_TAG "%s", + category_key, option_def->key); + + if (!nested_list_add_item(opt->option_map, + address, CORE_OPTION_MANAGER_MAP_DELIM, + (const void*)&opt->opts[size])) + goto error; + } + else + goto error; + } + + if (config_src) + config_file_free(config_src); + + return opt; + +error: + if (config_src) + config_file_free(config_src); + core_option_manager_free(opt); + return NULL; +} + +/** + * core_option_manager_free: + * + * @opt : options manager handle + * + * Frees specified core options manager handle. + **/ +void core_option_manager_free(core_option_manager_t *opt) +{ + size_t i; + + if (!opt) + return; + + for (i = 0; i < opt->cats_size; i++) + { + if (opt->cats[i].key) + free(opt->cats[i].key); + if (opt->cats[i].desc) + free(opt->cats[i].desc); + if (opt->cats[i].info) + free(opt->cats[i].info); + + opt->cats[i].key = NULL; + opt->cats[i].desc = NULL; + opt->cats[i].info = NULL; + } + + for (i = 0; i < opt->size; i++) + { + if (opt->opts[i].desc) + free(opt->opts[i].desc); + if (opt->opts[i].desc_categorized) + free(opt->opts[i].desc_categorized); + if (opt->opts[i].info) + free(opt->opts[i].info); + if (opt->opts[i].info_categorized) + free(opt->opts[i].info_categorized); + if (opt->opts[i].key) + free(opt->opts[i].key); + if (opt->opts[i].category_key) + free(opt->opts[i].category_key); + + if (opt->opts[i].vals) + string_list_free(opt->opts[i].vals); + if (opt->opts[i].val_labels) + string_list_free(opt->opts[i].val_labels); + + opt->opts[i].desc = NULL; + opt->opts[i].desc_categorized = NULL; + opt->opts[i].info = NULL; + opt->opts[i].info_categorized = NULL; + opt->opts[i].key = NULL; + opt->opts[i].category_key = NULL; + opt->opts[i].vals = NULL; + } + + if (opt->option_map) + nested_list_free(opt->option_map); + + if (opt->conf) + config_file_free(opt->conf); + + free(opt->cats); + free(opt->opts); + free(opt); +} + +/********************/ +/* Category Getters */ +/********************/ + +/** + * core_option_manager_get_category_desc: + * + * @opt : options manager handle + * @key : core option category id string + * + * Fetches the 'description' text of the core option + * category identified by @key (used as the + * category label in the menu). + * + * Returns: description string (menu label) of the + * specified option category if successful, + * otherwise NULL. + **/ +const char *core_option_manager_get_category_desc(core_option_manager_t *opt, + const char *key) +{ + size_t i; + + if (!opt || + string_is_empty(key)) + return NULL; + + for (i = 0; i < opt->cats_size; i++) + { + const char *cat_key = opt->cats[i].key; + + if (string_is_empty(cat_key)) + continue; + + if (string_is_equal(key, cat_key)) + { + return opt->cats[i].desc; + } + } + + return NULL; +} + +/** + * core_option_manager_get_category_info: + * + * @opt : options manager handle + * @key : core option category id string + * + * Fetches the 'info' text of the core option + * category identified by @key (used as the category + * sublabel in the menu). + * + * Returns: information string (menu sublabel) of + * the specified option category if successful, + * otherwise NULL. + **/ +const char *core_option_manager_get_category_info(core_option_manager_t *opt, + const char *key) +{ + size_t i; + + if (!opt || + string_is_empty(key)) + return NULL; + + for (i = 0; i < opt->cats_size; i++) + { + const char *cat_key = opt->cats[i].key; + + if (string_is_empty(cat_key)) + continue; + + if (string_is_equal(key, cat_key)) + { + return opt->cats[i].info; + } + } + + return NULL; +} + +/** + * core_option_manager_get_category_visible: + * + * @opt : options manager handle + * @key : core option category id string + * + * Queries whether the core option category + * identified by @key should be displayed in + * the frontend menu. (A category is deemed to + * be visible if at least one of the options + * in the category is visible) + * + * Returns: true if option category should be + * displayed by the frontend, otherwise false. + **/ +bool core_option_manager_get_category_visible(core_option_manager_t *opt, + const char *key) +{ + nested_list_item_t *category_item = NULL; + nested_list_t *option_list = NULL; + nested_list_item_t *option_item = NULL; + const struct core_option *option = NULL; + size_t i; + + if (!opt || + string_is_empty(key)) + return false; + + /* Fetch category item from map */ + category_item = nested_list_get_item(opt->option_map, + key, NULL); + + if (!category_item) + return false; + + /* Get child options of specified category */ + option_list = nested_list_item_get_children(category_item); + + if (!option_list) + return false; + + /* Loop over child options */ + for (i = 0; i < nested_list_get_size(option_list); i++) + { + option_item = nested_list_get_item_idx(option_list, i); + option = (const struct core_option *) + nested_list_item_get_value(option_item); + + /* Check if current option is visible */ + if (option && option->visible) + return true; + } + + return false; +} + +/******************/ +/* Option Getters */ +/******************/ + +/** + * core_option_manager_get_idx: + * + * @opt : options manager handle + * @key : core option key string (variable to query + * in RETRO_ENVIRONMENT_GET_VARIABLE) + * @idx : index of core option corresponding + * to @key + * + * Fetches the index of the core option identified + * by the specified @key. + * + * Returns: true if option matching the specified + * key was found, otherwise false. + **/ +bool core_option_manager_get_idx(core_option_manager_t *opt, + const char *key, size_t *idx) +{ + size_t i; + + if (!opt || + string_is_empty(key) || + !idx) + return false; + + for (i = 0; i < opt->size; i++) + { + const char *opt_key = opt->opts[i].key; + + if (string_is_empty(opt_key)) + continue; + + if (string_is_equal(key, opt_key)) + { + *idx = i; + return true; + } + } + + return false; +} + +/** + * core_option_manager_get_desc: + * + * @opt : options manager handle + * @idx : core option index + * @categorized : flag specifying whether to + * fetch the categorised description + * or the legacy fallback + * + * Fetches the 'description' of the core option at + * index @idx (used as the option label in the menu). + * If menu has option category support, @categorized + * should be true. (At present, only the Qt interface + * requires @categorized to be false) + * + * Returns: description string (menu label) of the + * specified option if successful, otherwise NULL. + **/ +const char *core_option_manager_get_desc(core_option_manager_t *opt, + size_t idx, bool categorized) +{ + const char *desc = NULL; + + if (!opt || + (idx >= opt->size)) + return NULL; + + /* Try categorised description first, + * if requested */ + if (categorized) + desc = opt->opts[idx].desc_categorized; + + /* Fall back to legacy description, if + * required */ + if (string_is_empty(desc)) + desc = opt->opts[idx].desc; + + return desc; +} + +/** + * core_option_manager_get_info: + * + * @opt : options manager handle + * @idx : core option index + * @categorized : flag specifying whether to + * fetch the categorised information + * or the legacy fallback + * + * Fetches the 'info' text of the core option at + * index @idx (used as the option sublabel in the + * menu). If menu has option category support, + * @categorized should be true. (At present, only + * the Qt interface requires @categorized to be false) + * + * Returns: information string (menu sublabel) of the + * specified option if successful, otherwise NULL. + **/ +const char *core_option_manager_get_info(core_option_manager_t *opt, + size_t idx, bool categorized) +{ + const char *info = NULL; + + if (!opt || + (idx >= opt->size)) + return NULL; + + /* Try categorised information first, + * if requested */ + if (categorized) + info = opt->opts[idx].info_categorized; + + /* Fall back to legacy information, if + * required */ + if (string_is_empty(info)) + info = opt->opts[idx].info; + + return info; +} + +/** + * core_option_manager_get_val: + * + * @opt : options manager handle + * @idx : core option index + * + * Fetches the string representation of the current + * value of the core option at index @idx. + * + * Returns: core option value string if successful, + * otherwise NULL. + **/ +const char *core_option_manager_get_val(core_option_manager_t *opt, + size_t idx) +{ + struct core_option *option = NULL; + + if (!opt || + (idx >= opt->size)) + return NULL; + + option = (struct core_option*)&opt->opts[idx]; + + return option->vals->elems[option->index].data; +} + +/** + * core_option_manager_get_val_label: + * + * @opt : options manager handle + * @idx : core option index + * + * Fetches the 'label' text (used for display purposes + * in the menu) for the current value of the core + * option at index @idx. + * + * Returns: core option value label string if + * successful, otherwise NULL. + **/ +const char *core_option_manager_get_val_label(core_option_manager_t *opt, + size_t idx) +{ + struct core_option *option = NULL; + + if (!opt || + (idx >= opt->size)) + return NULL; + + option = (struct core_option*)&opt->opts[idx]; + + return option->val_labels->elems[option->index].data; +} + +/** + * core_option_manager_get_visible: + * + * @opt : options manager handle + * @idx : core option index + * + * Queries whether the core option at index @idx + * should be displayed in the frontend menu. + * + * Returns: true if option should be displayed by + * the frontend, otherwise false. + **/ +bool core_option_manager_get_visible(core_option_manager_t *opt, + size_t idx) +{ + if (!opt || + (idx >= opt->size)) + return false; + + return opt->opts[idx].visible; +} + +/******************/ +/* Option Setters */ +/******************/ + +/** + * core_option_manager_set_val: + * + * @opt : options manager handle + * @idx : core option index + * @val_idx : index of the value to set + * + * Sets the core option at index @idx to the + * option value corresponding to @val_idx. + **/ +void core_option_manager_set_val(core_option_manager_t *opt, + size_t idx, size_t val_idx) +{ + struct core_option *option = NULL; + + if (!opt || + (idx >= opt->size)) + return; + + option = (struct core_option*)&opt->opts[idx]; + option->index = val_idx % option->vals->size; + opt->updated = true; + +#ifdef HAVE_CHEEVOS + rcheevos_validate_config_settings(); +#endif +} + +/** + * core_option_manager_adjust_val: + * + * @opt : options manager handle + * @idx : core option index + * @adjustment : offset to apply from current + * value index + * + * Modifies the value of the core option at index + * @idx by incrementing the current option value index + * by @adjustment. + **/ +void core_option_manager_adjust_val(core_option_manager_t* opt, + size_t idx, int adjustment) +{ + struct core_option* option = NULL; + + if (!opt || + (idx >= opt->size)) + return; + + option = (struct core_option*)&opt->opts[idx]; + option->index = (option->index + option->vals->size + adjustment) % option->vals->size; + opt->updated = true; + +#ifdef HAVE_CHEEVOS + rcheevos_validate_config_settings(); +#endif +} + +/** + * core_option_manager_set_default: + * + * @opt : options manager handle + * @idx : core option index + * + * Resets the core option at index @idx to its + * default value. + **/ +void core_option_manager_set_default(core_option_manager_t *opt, size_t idx) +{ + if (!opt || + (idx >= opt->size)) + return; + + opt->opts[idx].index = opt->opts[idx].default_index; + opt->updated = true; + +#ifdef HAVE_CHEEVOS + rcheevos_validate_config_settings(); +#endif +} + +/** + * core_option_manager_set_visible: + * + * @opt : options manager handle + * @key : core option key string (variable to query + * in RETRO_ENVIRONMENT_GET_VARIABLE) + * @visible : flag specifying whether option should + * be shown in the menu + * + * Sets the in-menu visibility of the core option + * identified by the specified @key. + **/ +void core_option_manager_set_visible(core_option_manager_t *opt, + const char *key, bool visible) +{ + size_t i; + + if (!opt || string_is_empty(key)) + return; + + for (i = 0; i < opt->size; i++) + { + const char *opt_key = opt->opts[i].key; + + if (string_is_empty(opt_key)) + continue; + + if (string_is_equal(opt_key, key)) + { + opt->opts[i].visible = visible; + return; + } + } +} + +/**********************/ +/* Configuration File */ +/**********************/ + +/** + * core_option_manager_flush: + * + * @opt : options manager handle + * @conf : configuration file handle + * + * Writes all core option key-pair values from the + * specified core option manager handle to the + * specified configuration file struct. + **/ +void core_option_manager_flush(core_option_manager_t *opt, + config_file_t *conf) +{ + size_t i; + + for (i = 0; i < opt->size; i++) + { + struct core_option *option = (struct core_option*)&opt->opts[i]; + + if (option) + config_set_string(conf, option->key, + opt->opts[i].vals->elems[opt->opts[i].index].data); + } +} diff --git a/core_option_manager.h b/core_option_manager.h index d8a54ba15d..bec1e0db8d 100644 --- a/core_option_manager.h +++ b/core_option_manager.h @@ -22,6 +22,7 @@ #include #include #include +#include #include "retroarch.h" @@ -30,100 +31,408 @@ RETRO_BEGIN_DECLS struct core_option { char *desc; + char *desc_categorized; char *info; + char *info_categorized; char *key; + char *category_key; struct string_list *vals; struct string_list *val_labels; + /* opt_idx: option index, used for internal + * bookkeeping */ + size_t opt_idx; + /* default_index, index: correspond to + * option *value* indices */ size_t default_index; size_t index; bool visible; }; +struct core_catagory +{ + char *key; + char *desc; + char *info; +}; + +/* TODO/FIXME: This struct should be made + * 'private', with restricted access to its + * members via interface functions. This + * requires significant refactoring... */ struct core_option_manager { config_file_t *conf; char conf_path[PATH_MAX_LENGTH]; + struct core_catagory *cats; struct core_option *opts; + nested_list_t *option_map; + + size_t cats_size; size_t size; + bool updated; }; typedef struct core_option_manager core_option_manager_t; +/*********************/ +/* Option Conversion */ +/*********************/ + /** - * core_option_manager_set_default: - * @opt : pointer to core option manager object. - * @idx : index of core option to be reset to defaults. + * core_option_manager_convert_v1: * - * Reset core option specified by @idx and sets default value for option. + * @options_v1 : an array of retro_core_option_definition + * structs + * + * Converts an array of core option v1 definitions into + * a v2 core options struct. Returned pointer must be + * freed using core_option_manager_free_converted(). + * + * Returns: Valid pointer to a new v2 core options struct + * if successful, otherwise NULL. **/ -void core_option_manager_set_default(core_option_manager_t *opt, size_t idx); +struct retro_core_options_v2 *core_option_manager_convert_v1( + const struct retro_core_option_definition *options_v1); + +/** + * core_option_manager_convert_v1_intl: + * + * @options_v1_intl : pointer to a retro_core_options_intl + * struct + * + * Converts a v1 'international' core options definition + * struct into a v2 core options struct. Returned pointer + * must be freed using core_option_manager_free_converted(). + * + * Returns: Valid pointer to a new v2 core options struct + * if successful, otherwise NULL. + **/ +struct retro_core_options_v2 *core_option_manager_convert_v1_intl( + const struct retro_core_options_intl *options_v1_intl); + +/** + * core_option_manager_convert_v2_intl: + * + * @options_v2_intl : pointer to a retro_core_options_v2_intl + * struct + * + * Converts a v2 'international' core options struct + * into a regular v2 core options struct. Returned pointer + * must be freed using core_option_manager_free_converted(). + * + * Returns: Valid pointer to a new v2 core options struct + * if successful, otherwise NULL. + **/ +struct retro_core_options_v2 *core_option_manager_convert_v2_intl( + const struct retro_core_options_v2_intl *options_v2_intl); + +/** + * core_option_manager_free_converted: + * + * @options_v2 : pointer to a retro_core_options_v2 + * struct + * + * Frees the pointer returned by any + * core_option_manager_convert_*() function. + **/ +void core_option_manager_free_converted( + struct retro_core_options_v2 *options_v2); + +/**************************************/ +/* Initialisation / De-Initialisation */ +/**************************************/ + +/** + * core_option_manager_new_vars: + * + * @conf_path : Filesystem path to write core option + * config file to + * @src_conf_path : Filesystem path from which to load + * initial config settings. + * @vars : Pointer to core option variable array + * handle + * + * Legacy version of core_option_manager_new(). + * Creates and initializes a core manager handle. + * + * Returns: handle to new core manager handle if successful, + * otherwise NULL. + **/ +core_option_manager_t *core_option_manager_new_vars( + const char *conf_path, const char *src_conf_path, + const struct retro_variable *vars); + +/** + * core_option_manager_new: + * + * @conf_path : Filesystem path to write core option + * config file to + * @src_conf_path : Filesystem path from which to load + * initial config settings. + * @options_v2 : Pointer to retro_core_options_v2 struct + * + * Creates and initializes a core manager handle. Parses + * information from a retro_core_options_v2 struct. + * + * Returns: handle to new core manager handle if successful, + * otherwise NULL. + **/ +core_option_manager_t *core_option_manager_new( + const char *conf_path, const char *src_conf_path, + const struct retro_core_options_v2 *options_v2); + +/** + * core_option_manager_free: + * + * @opt : options manager handle + * + * Frees specified core options manager handle. + **/ +void core_option_manager_free(core_option_manager_t *opt); + +/********************/ +/* Category Getters */ +/********************/ + +/** + * core_option_manager_get_category_desc: + * + * @opt : options manager handle + * @key : core option category id string + * + * Fetches the 'description' text of the core option + * category identified by @key (used as the + * category label in the menu). + * + * Returns: description string (menu label) of the + * specified option category if successful, + * otherwise NULL. + **/ +const char *core_option_manager_get_category_desc(core_option_manager_t *opt, + const char *key); + +/** + * core_option_manager_get_category_info: + * + * @opt : options manager handle + * @key : core option category id string + * + * Fetches the 'info' text of the core option + * category identified by @key (used as the category + * sublabel in the menu). + * + * Returns: information string (menu sublabel) of + * the specified option category if successful, + * otherwise NULL. + **/ +const char *core_option_manager_get_category_info(core_option_manager_t *opt, + const char *key); + +/** + * core_option_manager_get_category_visible: + * + * @opt : options manager handle + * @key : core option category id string + * + * Queries whether the core option category + * identified by @key should be displayed in + * the frontend menu. (A category is deemed to + * be visible if at least one of the options + * in the category is visible) + * + * Returns: true if option category should be + * displayed by the frontend, otherwise false. + **/ +bool core_option_manager_get_category_visible(core_option_manager_t *opt, + const char *key); + +/******************/ +/* Option Getters */ +/******************/ + +/** + * core_option_manager_get_idx: + * + * @opt : options manager handle + * @key : core option key string (variable to query + * in RETRO_ENVIRONMENT_GET_VARIABLE) + * @idx : index of core option corresponding + * to @key + * + * Fetches the index of the core option identified + * by the specified @key. + * + * Returns: true if option matching the specified + * key was found, otherwise false. + **/ +bool core_option_manager_get_idx(core_option_manager_t *opt, + const char *key, size_t *idx); /** * core_option_manager_get_desc: - * @opt : options manager handle - * @idx : idx identifier of the option * - * Gets description for an option. + * @opt : options manager handle + * @idx : core option index + * @categorized : flag specifying whether to + * fetch the categorised description + * or the legacy fallback * - * Returns: Description for an option. + * Fetches the 'description' of the core option at + * index @idx (used as the option label in the menu). + * If menu has option category support, @categorized + * should be true. (At present, only the Qt interface + * requires @categorized to be false) + * + * Returns: description string (menu label) of the + * specified option if successful, otherwise NULL. **/ const char *core_option_manager_get_desc(core_option_manager_t *opt, - size_t idx); + size_t idx, bool categorized); /** * core_option_manager_get_info: - * @opt : options manager handle - * @idx : idx identifier of the option * - * Gets information text for an option. + * @opt : options manager handle + * @idx : core option index + * @categorized : flag specifying whether to + * fetch the categorised information + * or the legacy fallback * - * Returns: Information text for an option. + * Fetches the 'info' text of the core option at + * index @idx (used as the option sublabel in the + * menu). If menu has option category support, + * @categorized should be true. (At present, only + * the Qt interface requires @categorized to be false) + * + * Returns: information string (menu sublabel) of the + * specified option if successful, otherwise NULL. **/ const char *core_option_manager_get_info(core_option_manager_t *opt, - size_t idx); + size_t idx, bool categorized); /** * core_option_manager_get_val: - * @opt : options manager handle - * @idx : idx identifier of the option * - * Gets value for an option. + * @opt : options manager handle + * @idx : core option index * - * Returns: Value for an option. + * Fetches the string representation of the current + * value of the core option at index @idx. + * + * Returns: core option value string if successful, + * otherwise NULL. **/ const char *core_option_manager_get_val(core_option_manager_t *opt, size_t idx); /** * core_option_manager_get_val_label: - * @opt : options manager handle - * @idx : idx identifier of the option * - * Gets value label for an option. + * @opt : options manager handle + * @idx : core option index * - * Returns: Value label for an option. + * Fetches the 'label' text (used for display purposes + * in the menu) for the current value of the core + * option at index @idx. + * + * Returns: core option value label string if + * successful, otherwise NULL. **/ const char *core_option_manager_get_val_label(core_option_manager_t *opt, size_t idx); /** * core_option_manager_get_visible: - * @opt : options manager handle - * @idx : idx identifier of the option * - * Gets whether option should be visible when displaying - * core options in the frontend + * @opt : options manager handle + * @idx : core option index * - * Returns: 'true' if option should be displayed by the frontend. + * Queries whether the core option at index @idx + * should be displayed in the frontend menu. + * + * Returns: true if option should be displayed by + * the frontend, otherwise false. **/ bool core_option_manager_get_visible(core_option_manager_t *opt, size_t idx); +/******************/ +/* Option Setters */ +/******************/ + +/** + * core_option_manager_set_val: + * + * @opt : options manager handle + * @idx : core option index + * @val_idx : index of the value to set + * + * Sets the core option at index @idx to the + * option value corresponding to @val_idx. + **/ void core_option_manager_set_val(core_option_manager_t *opt, size_t idx, size_t val_idx); +/** + * core_option_manager_adjust_val: + * + * @opt : options manager handle + * @idx : core option index + * @adjustment : offset to apply from current + * value index + * + * Modifies the value of the core option at index + * @idx by incrementing the current option value index + * by @adjustment. + **/ +void core_option_manager_adjust_val(core_option_manager_t* opt, + size_t idx, int adjustment); + +/** + * core_option_manager_set_default: + * + * @opt : options manager handle + * @idx : core option index + * + * Resets the core option at index @idx to its + * default value. + **/ +void core_option_manager_set_default(core_option_manager_t *opt, size_t idx); + +/** + * core_option_manager_set_visible: + * + * @opt : options manager handle + * @key : core option key string (variable to query + * in RETRO_ENVIRONMENT_GET_VARIABLE) + * @visible : flag specifying whether option should + * be shown in the menu + * + * Sets the in-menu visibility of the core option + * identified by the specified @key. + **/ +void core_option_manager_set_visible(core_option_manager_t *opt, + const char *key, bool visible); + +/**********************/ +/* Configuration File */ +/**********************/ + +/** + * core_option_manager_flush: + * + * @opt : options manager handle + * @conf : configuration file handle + * + * Writes all core option key-pair values from the + * specified core option manager handle to the + * specified configuration file struct. + **/ +void core_option_manager_flush(core_option_manager_t *opt, + config_file_t *conf); + RETRO_END_DECLS #endif diff --git a/griffin/griffin.c b/griffin/griffin.c index 5d295cf2e3..98b5ea2c12 100644 --- a/griffin/griffin.c +++ b/griffin/griffin.c @@ -1081,6 +1081,7 @@ FILE #include "../file_path_special.c" #include "../libretro-common/lists/dir_list.c" #include "../libretro-common/lists/string_list.c" +#include "../libretro-common/lists/nested_list.c" #include "../libretro-common/lists/file_list.c" #include "../libretro-common/file/retro_dirent.c" #include "../libretro-common/streams/file_stream.c" @@ -1183,6 +1184,7 @@ FRONTEND #include "../core_info.c" #include "../core_backup.c" +#include "../core_option_manager.c" #if defined(HAVE_NETWORKING) #include "../core_updater_list.c" diff --git a/libretro-common/include/libretro.h b/libretro-common/include/libretro.h index 32aa15f059..076dd25d97 100644 --- a/libretro-common/include/libretro.h +++ b/libretro-common/include/libretro.h @@ -1131,6 +1131,13 @@ enum retro_mod * retro_core_option_definition structs to RETRO_ENVIRONMENT_SET_CORE_OPTIONS_INTL. * This allows the core to additionally set option sublabel information * and/or provide localisation support. + * + * If version is >= 2, core options may instead be set by passing + * a retro_core_options_v2 struct to RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2, + * or an array of retro_core_options_v2 structs to + * RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2_INTL. This allows the core + * to additionally set optional core option category information + * for frontends with core option category support. */ #define RETRO_ENVIRONMENT_SET_CORE_OPTIONS 53 @@ -1172,7 +1179,7 @@ enum retro_mod * default value is NULL, the first entry in the * retro_core_option_definition::values array is treated as the default. * - * The number of possible options should be very limited, + * The number of possible option values should be very limited, * and must be less than RETRO_NUM_CORE_OPTION_VALUES_MAX. * i.e. it should be feasible to cycle through options * without a keyboard. @@ -1205,6 +1212,7 @@ enum retro_mod * This should only be called if RETRO_ENVIRONMENT_GET_CORE_OPTIONS_VERSION * returns an API version of >= 1. * This should be called instead of RETRO_ENVIRONMENT_SET_VARIABLES. + * This should be called instead of RETRO_ENVIRONMENT_SET_CORE_OPTIONS. * This should be called the first time as early as * possible (ideally in retro_set_environment). * Afterwards it may be called again for the core to communicate @@ -1493,6 +1501,217 @@ enum retro_mod * retro_load_game_special() */ +#define RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2 67 + /* const struct retro_core_options_v2 * -- + * Allows an implementation to signal the environment + * which variables it might want to check for later using + * GET_VARIABLE. + * This allows the frontend to present these variables to + * a user dynamically. + * This should only be called if RETRO_ENVIRONMENT_GET_CORE_OPTIONS_VERSION + * returns an API version of >= 2. + * This should be called instead of RETRO_ENVIRONMENT_SET_VARIABLES. + * This should be called instead of RETRO_ENVIRONMENT_SET_CORE_OPTIONS. + * This should be called the first time as early as + * possible (ideally in retro_set_environment). + * Afterwards it may be called again for the core to communicate + * updated options to the frontend, but the number of core + * options must not change from the number in the initial call. + * If RETRO_ENVIRONMENT_GET_CORE_OPTIONS_VERSION returns an API + * version of >= 2, this callback is guaranteed to succeed + * (i.e. callback return value does not indicate success) + * If callback returns true, frontend has core option category + * support. + * If callback returns false, frontend does not have core option + * category support. + * + * 'data' points to a retro_core_options_v2 struct, containing + * of two pointers: + * - retro_core_options_v2::categories is an array of + * retro_core_option_v2_category structs terminated by a + * { NULL, NULL, NULL } element. If retro_core_options_v2::categories + * is NULL, all core options will have no category and will be shown + * at the top level of the frontend core option interface. If frontend + * does not have core option category support, categories array will + * be ignored. + * - retro_core_options_v2::definitions is an array of + * retro_core_option_v2_definition structs terminated by a + * { NULL, NULL, NULL, NULL, NULL, NULL, {{0}}, NULL } + * element. + * + * >> retro_core_option_v2_category notes: + * + * - retro_core_option_v2_category::key should contain string + * that uniquely identifies the core option category. Valid + * key characters are [a-z, A-Z, 0-9, _, -] + * Namespace collisions with other implementations' category + * keys are permitted. + * - retro_core_option_v2_category::desc should contain a human + * readable description of the category key. + * - retro_core_option_v2_category::info should contain any + * additional human readable information text that a typical + * user may need to understand the nature of the core option + * category. + * + * Example entry: + * { + * "advanced_settings", + * "Advanced", + * "Options affecting low-level emulation performance and accuracy." + * } + * + * >> retro_core_option_v2_definition notes: + * + * - retro_core_option_v2_definition::key should be namespaced to not + * collide with other implementations' keys. e.g. A core called + * 'foo' should use keys named as 'foo_option'. Valid key characters + * are [a-z, A-Z, 0-9, _, -]. + * - retro_core_option_v2_definition::desc should contain a human readable + * description of the key. Will be used when the frontend does not + * have core option category support. Examples: "Aspect Ratio" or + * "Video > Aspect Ratio". + * - retro_core_option_v2_definition::desc_categorized should contain a + * human readable description of the key, which will be used when + * frontend has core option category support. Example: "Aspect Ratio", + * where associated retro_core_option_v2_category::desc is "Video". + * If empty or NULL, the string specified by + * retro_core_option_v2_definition::desc will be used instead. + * retro_core_option_v2_definition::desc_categorized will be ignored + * if retro_core_option_v2_definition::category_key is empty or NULL. + * - retro_core_option_v2_definition::info should contain any additional + * human readable information text that a typical user may need to + * understand the functionality of the option. + * - retro_core_option_v2_definition::info_categorized should contain + * any additional human readable information text that a typical user + * may need to understand the functionality of the option, and will be + * used when frontend has core option category support. This is provided + * to accommodate the case where info text references an option by + * name/desc, and the desc/desc_categorized text for that option differ. + * If empty or NULL, the string specified by + * retro_core_option_v2_definition::info will be used instead. + * retro_core_option_v2_definition::info_categorized will be ignored + * if retro_core_option_v2_definition::category_key is empty or NULL. + * - retro_core_option_v2_definition::category_key should contain a + * category identifier (e.g. "video" or "audio") that will be + * assigned to the core option if frontend has core option category + * support. A categorized option will be shown in a subsection/ + * submenu of the frontend core option interface. If key is empty + * or NULL, or if key does not match one of the + * retro_core_option_v2_category::key values in the associated + * retro_core_option_v2_category array, option will have no category + * and will be shown at the top level of the frontend core option + * interface. + * - retro_core_option_v2_definition::values is an array of + * retro_core_option_value structs terminated by a { NULL, NULL } + * element. + * --> retro_core_option_v2_definition::values[index].value is an + * expected option value. + * --> retro_core_option_v2_definition::values[index].label is a + * human readable label used when displaying the value on screen. + * If NULL, the value itself is used. + * - retro_core_option_v2_definition::default_value is the default + * core option setting. It must match one of the expected option + * values in the retro_core_option_v2_definition::values array. If + * it does not, or the default value is NULL, the first entry in the + * retro_core_option_v2_definition::values array is treated as the + * default. + * + * The number of possible option values should be very limited, + * and must be less than RETRO_NUM_CORE_OPTION_VALUES_MAX. + * i.e. it should be feasible to cycle through options + * without a keyboard. + * + * Example entries: + * + * - Uncategorized: + * + * { + * "foo_option", + * "Speed hack coprocessor X", + * NULL, + * "Provides increased performance at the expense of reduced accuracy.", + * NULL, + * NULL, + * { + * { "false", NULL }, + * { "true", NULL }, + * { "unstable", "Turbo (Unstable)" }, + * { NULL, NULL }, + * }, + * "false" + * } + * + * - Categorized: + * + * { + * "foo_option", + * "Advanced > Speed hack coprocessor X", + * "Speed hack coprocessor X", + * "Setting 'Advanced > Speed hack coprocessor X' to 'true' or 'Turbo' provides increased performance at the expense of reduced accuracy", + * "Setting 'Speed hack coprocessor X' to 'true' or 'Turbo' provides increased performance at the expense of reduced accuracy", + * "advanced_settings", + * { + * { "false", NULL }, + * { "true", NULL }, + * { "unstable", "Turbo (Unstable)" }, + * { NULL, NULL }, + * }, + * "false" + * } + * + * Only strings are operated on. The possible values will + * generally be displayed and stored as-is by the frontend. + */ + +#define RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2_INTL 68 + /* const struct retro_core_options_v2_intl * -- + * Allows an implementation to signal the environment + * which variables it might want to check for later using + * GET_VARIABLE. + * This allows the frontend to present these variables to + * a user dynamically. + * This should only be called if RETRO_ENVIRONMENT_GET_CORE_OPTIONS_VERSION + * returns an API version of >= 2. + * This should be called instead of RETRO_ENVIRONMENT_SET_VARIABLES. + * This should be called instead of RETRO_ENVIRONMENT_SET_CORE_OPTIONS. + * This should be called instead of RETRO_ENVIRONMENT_SET_CORE_OPTIONS_INTL. + * This should be called instead of RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2. + * This should be called the first time as early as + * possible (ideally in retro_set_environment). + * Afterwards it may be called again for the core to communicate + * updated options to the frontend, but the number of core + * options must not change from the number in the initial call. + * If RETRO_ENVIRONMENT_GET_CORE_OPTIONS_VERSION returns an API + * version of >= 2, this callback is guaranteed to succeed + * (i.e. callback return value does not indicate success) + * If callback returns true, frontend has core option category + * support. + * If callback returns false, frontend does not have core option + * category support. + * + * This is fundamentally the same as RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2, + * with the addition of localisation support. The description of the + * RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2 callback should be consulted + * for further details. + * + * 'data' points to a retro_core_options_v2_intl struct. + * + * - retro_core_options_v2_intl::us is a pointer to a + * retro_core_options_v2 struct defining the US English + * core options implementation. It must point to a valid struct. + * + * - retro_core_options_v2_intl::local is a pointer to a + * retro_core_options_v2 struct defining core options for + * the current frontend language. It may be NULL (in which case + * retro_core_options_v2_intl::us is used by the frontend). Any items + * missing from this struct will be read from + * retro_core_options_v2_intl::us instead. + * + * NOTE: Default core option values are always taken from the + * retro_core_options_v2_intl::us struct. Any default values in + * the retro_core_options_v2_intl::local struct will be ignored. + */ + /* VFS functionality */ /* File paths: @@ -3214,6 +3433,124 @@ struct retro_core_options_intl struct retro_core_option_definition *local; }; +struct retro_core_option_v2_category +{ + /* Variable uniquely identifying the + * option category. Valid key characters + * are [a-z, A-Z, 0-9, _, -] */ + const char *key; + + /* Human-readable category description + * > Used as category menu label when + * frontend has core option category + * support */ + const char *desc; + + /* Human-readable category information + * > Used as category menu sublabel when + * frontend has core option category + * support + * > Optional (may be NULL or an empty + * string) */ + const char *info; +}; + +struct retro_core_option_v2_definition +{ + /* Variable to query in RETRO_ENVIRONMENT_GET_VARIABLE. + * Valid key characters are [a-z, A-Z, 0-9, _, -] */ + const char *key; + + /* Human-readable core option description + * > Used as menu label when frontend does + * not have core option category support + * e.g. "Video > Aspect Ratio" */ + const char *desc; + + /* Human-readable core option description + * > Used as menu label when frontend has + * core option category support + * e.g. "Aspect Ratio", where associated + * retro_core_option_v2_category::desc + * is "Video" + * > If empty or NULL, the string specified by + * desc will be used as the menu label + * > Will be ignored (and may be set to NULL) + * if category_key is empty or NULL */ + const char *desc_categorized; + + /* Human-readable core option information + * > Used as menu sublabel */ + const char *info; + + /* Human-readable core option information + * > Used as menu sublabel when frontend + * has core option category support + * (e.g. may be required when info text + * references an option by name/desc, + * and the desc/desc_categorized text + * for that option differ) + * > If empty or NULL, the string specified by + * info will be used as the menu sublabel + * > Will be ignored (and may be set to NULL) + * if category_key is empty or NULL */ + const char *info_categorized; + + /* Variable specifying category (e.g. "video", + * "audio") that will be assigned to the option + * if frontend has core option category support. + * > Categorized options will be displayed in a + * subsection/submenu of the frontend core + * option interface + * > Specified string must match one of the + * retro_core_option_v2_category::key values + * in the associated retro_core_option_v2_category + * array; If no match is not found, specified + * string will be considered as NULL + * > If specified string is empty or NULL, option will + * have no category and will be shown at the top + * level of the frontend core option interface */ + const char *category_key; + + /* Array of retro_core_option_value structs, terminated by NULL */ + struct retro_core_option_value values[RETRO_NUM_CORE_OPTION_VALUES_MAX]; + + /* Default core option value. Must match one of the values + * in the retro_core_option_value array, otherwise will be + * ignored */ + const char *default_value; +}; + +struct retro_core_options_v2 +{ + /* Array of retro_core_option_v2_category structs, + * terminated by NULL + * > If NULL, all entries in definitions array + * will have no category and will be shown at + * the top level of the frontend core option + * interface + * > Will be ignored if frontend does not have + * core option category support */ + struct retro_core_option_v2_category *categories; + + /* Array of retro_core_option_v2_definition structs, + * terminated by NULL */ + struct retro_core_option_v2_definition *definitions; +}; + +struct retro_core_options_v2_intl +{ + /* Pointer to a retro_core_options_v2 struct + * > US English implementation + * > Must point to a valid struct */ + struct retro_core_options_v2 *us; + + /* Pointer to a retro_core_options_v2 struct + * - Implementation for current frontend language + * - May be NULL */ + struct retro_core_options_v2 *local; +}; + struct retro_game_info { const char *path; /* Path to game, UTF-8 encoded. diff --git a/libretro-common/include/lists/nested_list.h b/libretro-common/include/lists/nested_list.h new file mode 100644 index 0000000000..be6ca6807d --- /dev/null +++ b/libretro-common/include/lists/nested_list.h @@ -0,0 +1,242 @@ +/* Copyright (C) 2010-2020 The RetroArch team + * + * --------------------------------------------------------------------------------------- + * The following license statement only applies to this file (nested_list.h). + * --------------------------------------------------------------------------------------- + * + * Permission is hereby granted, free of charge, + * to any person obtaining a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef __LIBRETRO_SDK_NESTED_LIST_H__ +#define __LIBRETRO_SDK_NESTED_LIST_H__ + +#include + +#include +#include +#include + +RETRO_BEGIN_DECLS + +/* Prevent direct access to nested_list_* members */ +typedef struct nested_list_item nested_list_item_t; +typedef struct nested_list nested_list_t; + +/**************************************/ +/* Initialisation / De-Initialisation */ +/**************************************/ + +/** + * nested_list_init: + * + * Creates a new empty nested list. Returned pointer + * must be freed using nested_list_free. + * + * Returns: Valid nested_list_t pointer if successful, + * otherwise NULL. + */ +nested_list_t *nested_list_init(void); + +/** + * nested_list_free: + * + * @list : pointer to nested_list_t object + * + * Frees specified nested list. + */ +void nested_list_free(nested_list_t *list); + +/***********/ +/* Setters */ +/***********/ + +/** + * nested_list_add_item: + * + * @list : pointer to nested_list_t object + * @address : a delimited list of item identifiers, + * corresponding to item 'levels' + * @delim : delimiter to use when splitting @address + * into individual ids + * @value : optional value (user data) associated with + * new list item. This is added to the last + * item specified by @address + * + * Appends a new item to the specified nested list. + * If @delim is NULL, item is added to the top level + * list (@list itself) with id equal to @address. + * Otherwise, @address is split by @delim and each + * id is added as new 'layer'. For example: + * + * > @address = "one:two:three", @delim = ":" will + * produce: + * top_level_list:one + * `- "one" list:two + * `- "two" list:three + * where @value is assigned to the "two" list:three + * item. + * + * Returns: true if successful, otherwise false. Will + * always return false if item specified by @address + * already exists in the nested list. + */ +bool nested_list_add_item(nested_list_t *list, + const char *address, const char *delim, const void *value); + +/***********/ +/* Getters */ +/***********/ + +/** + * nested_list_get_size: + * + * @list : pointer to nested_list_t object + * + * Fetches the current size (number of items) in + * the specified list. + * + * Returns: list size. + */ +size_t nested_list_get_size(nested_list_t *list); + +/** + * nested_list_get_item: + * + * @list : pointer to nested_list_t object + * @address : a delimited list of item identifiers, + * corresponding to item 'levels' + * @delim : delimiter to use when splitting @address + * into individual ids + * + * Searches for (and returns) the list item corresponding + * to @address. If @delim is NULL, the top level list + * (@list itself) is searched for an item with an id + * equal to @address. Otherwise, @address is split by + * @delim and each id is searched for in a subsequent + * list level. + * + * Returns: valid nested_list_item_t pointer if item + * is found, otherwise NULL. + */ +nested_list_item_t *nested_list_get_item(nested_list_t *list, + const char *address, const char *delim); + +/** + * nested_list_get_item_idx: + * + * @list : pointer to nested_list_t object + * @idx : item index + * + * Fetches the item corresponding to index @idx in + * the top level list (@list itself) of the specified + * nested list. + * + * Returns: valid nested_list_item_t pointer if item + * exists, otherwise NULL. + */ +nested_list_item_t *nested_list_get_item_idx(nested_list_t *list, + size_t idx); + +/** + * nested_list_item_get_parent: + * + * @list_item : pointer to nested_list_item_t object + * + * Fetches the parent item of the specified nested + * list item. If returned value is NULL, specified + * nested list item belongs to a top level list. + * + * Returns: valid nested_list_item_t pointer if item + * has a parent, otherwise NULL. + */ +nested_list_item_t *nested_list_item_get_parent(nested_list_item_t *list_item); + +/** + * nested_list_item_get_parent_list: + * + * @list_item : pointer to nested_list_item_t object + * + * Fetches a pointer to the nested list of which the + * specified list item is a direct member. + * + * Returns: valid nested_list_t pointer if successful, + * otherwise NULL. + */ +nested_list_t *nested_list_item_get_parent_list(nested_list_item_t *list_item); + +/** + * nested_list_item_get_children: + * + * @list_item : pointer to nested_list_item_t object + * + * Fetches a pointer to the nested list of child items + * belonging to the specified list item. + * + * Returns: valid nested_list_t pointer if item has + * children, otherwise NULL. + */ +nested_list_t *nested_list_item_get_children(nested_list_item_t *list_item); + +/** + * nested_list_item_get_id: + * + * @list_item : pointer to nested_list_item_t object + * + * Fetches the id string of the specified list item, + * as set by nested_list_add_item(). + * + * Returns: item id if successful, otherwise NULL. + */ +const char *nested_list_item_get_id(nested_list_item_t *list_item); + +/** + * nested_list_item_get_address: + * + * @list_item : pointer to nested_list_item_t object + * @delim : delimiter to use when concatenating + * individual item ids into a an @address + * string + * @address : a delimited list of item identifiers, + * corresponding to item 'levels' + * @len : length of supplied @address char array + + * Fetches a compound @address string corresponding to + * the specified item's 'position' in the top level + * nested list of which it is a member. The resultant + * @address may be used to find the item when calling + * nested_list_get_item() on the top level nested list. + * + * Returns: true if successful, otherwise false. + */ +bool nested_list_item_get_address(nested_list_item_t *list_item, + const char *delim, char *address, size_t len); + +/** + * nested_list_item_get_value: + * + * @list_item : pointer to nested_list_item_t object + * + * Fetches the value (user data) associated with the + * specified list item. + * + * Returns: pointer to user data if set, otherwise + * NULL. + */ +const void *nested_list_item_get_value(nested_list_item_t *list_item); + +RETRO_END_DECLS + +#endif diff --git a/libretro-common/lists/nested_list.c b/libretro-common/lists/nested_list.c new file mode 100644 index 0000000000..b9421742d7 --- /dev/null +++ b/libretro-common/lists/nested_list.c @@ -0,0 +1,604 @@ +/* Copyright (C) 2010-2020 The RetroArch team + * + * --------------------------------------------------------------------------------------- + * The following license statement only applies to this file (nested_list.c). + * --------------------------------------------------------------------------------------- + * + * Permission is hereby granted, free of charge, + * to any person obtaining a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include +#include + +#include + +struct nested_list_item +{ + nested_list_item_t *parent_item; + nested_list_t *parent_list; + nested_list_t *children; + char *id; + const void *value; +}; + +struct nested_list +{ + nested_list_item_t *items; + nested_list_item_t **item_map; +}; + +/**************************************/ +/* Initialisation / De-Initialisation */ +/**************************************/ + +/* Forward declaration - required since + * nested_list_free_list() is recursive */ +static void nested_list_free_list(nested_list_t *list); + +/* Frees contents of a nested list item */ +static void nested_list_free_item(nested_list_item_t *item) +{ + if (!item) + return; + + item->parent_item = NULL; + item->parent_list = NULL; + + if (item->children) + { + nested_list_free_list(item->children); + free(item->children); + item->children = NULL; + } + + if (item->id) + { + free(item->id); + item->id = NULL; + } + + item->value = NULL; +} + +/* Frees contents of a nested list */ +static void nested_list_free_list(nested_list_t *list) +{ + size_t i; + + if (!list) + return; + + for (i = 0; i < RBUF_LEN(list->items); i++) + nested_list_free_item(&list->items[i]); + + RBUF_FREE(list->items); + RHMAP_FREE(list->item_map); +} + +/** + * nested_list_init: + * + * Creates a new empty nested list. Returned pointer + * must be freed using nested_list_free. + * + * Returns: Valid nested_list_t pointer if successful, + * otherwise NULL. + */ +nested_list_t *nested_list_init(void) +{ + /* Create nested list */ + nested_list_t *list = (nested_list_t*)malloc(sizeof(*list)); + + if (!list) + return NULL; + + /* Initialise members */ + list->items = NULL; + list->item_map = NULL; + + return list; +} + +/** + * nested_list_free: + * @list : pointer to nested_list_t object + * + * Frees specified nested list. + */ +void nested_list_free(nested_list_t *list) +{ + nested_list_free_list(list); + free(list); +} + +/***********/ +/* Setters */ +/***********/ + +/* Creates and adds a new item to the specified + * nested list. Returns NULL if item matching 'id' + * already exists */ +static nested_list_item_t *nested_list_add_item_to_list(nested_list_t *list, + nested_list_item_t *parent_item, const char *id, const void *value) +{ + size_t num_items = 0; + nested_list_item_t *new_item = NULL; + nested_list_t *child_list = NULL; + + if (!list || string_is_empty(id)) + goto end; + + num_items = RBUF_LEN(list->items); + + /* Ensure that item does not already exist */ + if (RHMAP_HAS_STR(list->item_map, id)) + goto end; + + /* Create new empty child list */ + child_list = nested_list_init(); + if (!child_list) + goto end; + + /* Attempt to allocate memory for new item */ + if (!RBUF_TRYFIT(list->items, num_items + 1)) + goto end; + + /* Allocation successful - increment array size */ + RBUF_RESIZE(list->items, num_items + 1); + + /* Get handle of new entry at end of list */ + new_item = &list->items[num_items]; + + /* Assign members */ + new_item->parent_item = parent_item; + new_item->parent_list = list; + new_item->children = child_list; + new_item->id = strdup(id); + new_item->value = value; + + /* Update map */ + RHMAP_SET_STR(list->item_map, id, new_item); +end: + return new_item; +} + +/** + * nested_list_add_item: + * + * @list : pointer to nested_list_t object + * @address : a delimited list of item identifiers, + * corresponding to item 'levels' + * @delim : delimiter to use when splitting @address + * into individual ids + * @value : optional value (user data) associated with + * new list item. This is added to the last + * item specified by @address + * + * Appends a new item to the specified nested list. + * If @delim is NULL, item is added to the top level + * list (@list itself) with id equal to @address. + * Otherwise, @address is split by @delim and each + * id is added as new 'layer'. For example: + * + * > @address = "one:two:three", @delim = ":" will + * produce: + * top_level_list:one + * `- "one" list:two + * `- "two" list:three + * where @value is assigned to the "two" list:three + * item. + * + * Returns: true if successful, otherwise false. Will + * always return false if item specified by @address + * already exists in the nested list. + */ +bool nested_list_add_item(nested_list_t *list, + const char *address, const char *delim, const void *value) +{ + struct string_list id_list = {0}; + const char *top_id = NULL; + bool success = false; + + if (!list || string_is_empty(address)) + goto end; + + /* If delim is NULL or address contains a single + * token, then we are adding an item to the top + * level list */ + if (string_is_empty(delim)) + top_id = address; + else + { + string_list_initialize(&id_list); + if (!string_split_noalloc(&id_list, address, delim) || + (id_list.size < 1)) + goto end; + + if (id_list.size == 1) + top_id = id_list.elems[0].data; + } + + if (!string_is_empty(top_id)) + { + if (nested_list_add_item_to_list(list, NULL, top_id, value)) + success = true; + } + else + { + nested_list_t *current_list = list; + nested_list_item_t *parent_item = NULL; + nested_list_item_t *next_item = NULL; + size_t i; + + /* Loop over list item ids */ + for (i = 0; i < id_list.size; i++) + { + const char *id = id_list.elems[i].data; + + if (string_is_empty(id)) + goto end; + + /* If this is the last entry in the id list, + * then we are adding the item itself */ + if (i == (id_list.size - 1)) + { + if (nested_list_add_item_to_list(current_list, + parent_item, id, value)) + success = true; + + break; + } + /* Otherwise, id corresponds to a 'category' */ + else + { + /* Check whether category item already exists */ + next_item = RHMAP_GET_STR(current_list->item_map, id); + + /* Create it, if required */ + if (!next_item) + next_item = nested_list_add_item_to_list(current_list, + parent_item, id, NULL); + + if (!next_item) + break; + + /* Update pointers */ + parent_item = next_item; + current_list = next_item->children; + } + } + } + +end: + string_list_deinitialize(&id_list); + return success; +} + +/***********/ +/* Getters */ +/***********/ + +/** + * nested_list_get_size: + * + * @list : pointer to nested_list_t object + * + * Fetches the current size (number of items) in + * the specified list. + * + * Returns: list size. + */ +size_t nested_list_get_size(nested_list_t *list) +{ + if (!list) + return 0; + + return RBUF_LEN(list->items); +} + +/** + * nested_list_get_item: + * + * @list : pointer to nested_list_t object + * @address : a delimited list of item identifiers, + * corresponding to item 'levels' + * @delim : delimiter to use when splitting @address + * into individual ids + * + * Searches for (and returns) the list item corresponding + * to @address. If @delim is NULL, the top level list + * (@list itself) is searched for an item with an id + * equal to @address. Otherwise, @address is split by + * @delim and each id is searched for in a subsequent + * list level. + * + * Returns: valid nested_list_item_t pointer if item + * is found, otherwise NULL. + */ +nested_list_item_t *nested_list_get_item(nested_list_t *list, + const char *address, const char *delim) +{ + nested_list_item_t *search_item = NULL; + struct string_list id_list = {0}; + const char *top_id = NULL; + + if (!list || string_is_empty(address)) + goto end; + + /* If delim is NULL or address contains a single + * token, then we are fetching an item from the + * top level list */ + if (string_is_empty(delim)) + top_id = address; + else + { + string_list_initialize(&id_list); + if (!string_split_noalloc(&id_list, address, delim) || + (id_list.size < 1)) + goto end; + + if (id_list.size == 1) + top_id = id_list.elems[0].data; + } + + if (!string_is_empty(top_id)) + search_item = RHMAP_GET_STR(list->item_map, top_id); + else + { + /* Otherwise, search 'category' levels */ + nested_list_t *current_list = list; + nested_list_item_t *next_item = NULL; + size_t i; + + /* Loop over list item ids */ + for (i = 0; i < id_list.size; i++) + { + const char *id = id_list.elems[i].data; + + if (string_is_empty(id)) + goto end; + + /* If this is the last entry in the id list, + * then we are searching for the item itself */ + if (i == (id_list.size - 1)) + { + search_item = RHMAP_GET_STR(current_list->item_map, id); + break; + } + /* Otherwise, id corresponds to a 'category' */ + else + { + next_item = RHMAP_GET_STR(current_list->item_map, id); + + if (!next_item) + break; + + /* Update pointer */ + current_list = next_item->children; + } + } + } + +end: + string_list_deinitialize(&id_list); + return search_item; +} + +/** + * nested_list_get_item_idx: + * + * @list : pointer to nested_list_t object + * @idx : item index + * + * Fetches the item corresponding to index @idx in + * the top level list (@list itself) of the specified + * nested list. + * + * Returns: valid nested_list_item_t pointer if item + * exists, otherwise NULL. + */ +nested_list_item_t *nested_list_get_item_idx(nested_list_t *list, + size_t idx) +{ + if (!list || (idx >= RBUF_LEN(list->items))) + return NULL; + + return &list->items[idx]; +} + +/** + * nested_list_item_get_parent: + * + * @list_item : pointer to nested_list_item_t object + * + * Fetches the parent item of the specified nested + * list item. If returned value is NULL, specified + * nested list item belongs to a top level list. + * + * Returns: valid nested_list_item_t pointer if item + * has a parent, otherwise NULL. + */ +nested_list_item_t *nested_list_item_get_parent(nested_list_item_t *list_item) +{ + if (!list_item) + return NULL; + + return list_item->parent_item; +} + +/** + * nested_list_item_get_parent_list: + * + * @list_item : pointer to nested_list_item_t object + * + * Fetches a pointer to the nested list of which the + * specified list item is a direct member. + * + * Returns: valid nested_list_t pointer if successful, + * otherwise NULL. + */ +nested_list_t *nested_list_item_get_parent_list(nested_list_item_t *list_item) +{ + if (!list_item) + return NULL; + + return list_item->parent_list; +} + +/** + * nested_list_item_get_children: + * + * @list_item : pointer to nested_list_item_t object + * + * Fetches a pointer to the nested list of child items + * belonging to the specified list item. + * + * Returns: valid nested_list_t pointer if item has + * children, otherwise NULL. + */ +nested_list_t *nested_list_item_get_children(nested_list_item_t *list_item) +{ + if (!list_item || + !list_item->children || + (RBUF_LEN(list_item->children->items) < 1)) + return NULL; + + return list_item->children; +} + +/** + * nested_list_item_get_id: + * + * @list_item : pointer to nested_list_item_t object + * + * Fetches the id string of the specified list item, + * as set by nested_list_add_item(). + * + * Returns: item id if successful, otherwise NULL. + */ +const char *nested_list_item_get_id(nested_list_item_t *list_item) +{ + if (!list_item) + return NULL; + + return list_item->id; +} + +/** + * nested_list_item_get_address: + * + * @list_item : pointer to nested_list_item_t object + * @delim : delimiter to use when concatenating + * individual item ids into a an @address + * string + * @address : a delimited list of item identifiers, + * corresponding to item 'levels' + * @len : length of supplied @address char array + + * Fetches a compound @address string corresponding to + * the specified item's 'position' in the top level + * nested list of which it is a member. The resultant + * @address may be used to find the item when calling + * nested_list_get_item() on the top level nested list. + * + * Returns: true if successful, otherwise false. + */ +bool nested_list_item_get_address(nested_list_item_t *list_item, + const char *delim, char *address, size_t len) +{ + nested_list_item_t *current_item = list_item; + struct string_list id_list = {0}; + bool success = false; + union string_list_elem_attr attr; + size_t i; + + if (!list_item || + string_is_empty(delim) || + !address || + (len < 1)) + goto end; + + address[0] = '\0'; + attr.i = 0; + + /* If this is an item of the top level + * list, just copy the item id directly */ + if (!list_item->parent_item) + { + strlcpy(address, list_item->id, len); + success = true; + goto end; + } + + /* ...otherwise we have to combine the ids + * of the item and all of its 'ancestors' */ + string_list_initialize(&id_list); + + /* Fetch all ids */ + do + { + const char *id = current_item->id; + + if (string_is_empty(id) || + !string_list_append(&id_list, id, attr)) + goto end; + + current_item = current_item->parent_item; + } + while (current_item); + + if (id_list.size < 1) + goto end; + + /* Build address string */ + for (i = id_list.size; i > 0; i--) + { + const char *id = id_list.elems[i - 1].data; + + if (string_is_empty(id)) + goto end; + + strlcat(address, id, len); + if (i > 1) + strlcat(address, delim, len); + } + + success = true; +end: + string_list_deinitialize(&id_list); + return success; +} + +/** + * nested_list_item_get_value: + * + * @list_item : pointer to nested_list_item_t object + * + * Fetches the value (user data) associated with the + * specified list item. + * + * Returns: pointer to user data if set, otherwise + * NULL. + */ +const void *nested_list_item_get_value(nested_list_item_t *list_item) +{ + if (!list_item) + return NULL; + + return list_item->value; +} diff --git a/libretro-common/samples/core_options/README.md b/libretro-common/samples/core_options/README.md index ea7868b0d9..7406f30cda 100644 --- a/libretro-common/samples/core_options/README.md +++ b/libretro-common/samples/core_options/README.md @@ -51,3 +51,9 @@ if (strcmp(key, "mycore_show_speedhacks") == 0) ``` For any cores that require this functionality, `example_hide_option/libretro_core_options.h` should be used as a template in place of `example_default/libretro_core_options.h` + +## Adding core option categories + +Core options v2 adds a mechanism for assigning categories to options. On supported fontends, options of a particular category will be displayed in a submenu/subsection of the main core options menu. This functionality may be used to reduce visual clutter, or to effectively 'hide' advanced settings without requiring a dedicated 'toggle display' option. + +A template for enabling categories via the core options v2 interface is provided in `example_categories`. The usage of this code is identical to that described in the `Adding 'enhanced' core options to a core` section, with one addition: the `libretro_set_core_options()` function here includes an additional argument identifying whether the frontend has option category support (a core may wish to selectively hide or reorganise options based upon this variable). diff --git a/libretro-common/samples/core_options/example_categories/libretro_core_options.h b/libretro-common/samples/core_options/example_categories/libretro_core_options.h new file mode 100644 index 0000000000..4f38049ea5 --- /dev/null +++ b/libretro-common/samples/core_options/example_categories/libretro_core_options.h @@ -0,0 +1,456 @@ +#ifndef LIBRETRO_CORE_OPTIONS_H__ +#define LIBRETRO_CORE_OPTIONS_H__ + +#include +#include + +#include +#include + +#ifndef HAVE_NO_LANGEXTRA +#include "libretro_core_options_intl.h" +#endif + +/* + ******************************** + * VERSION: 2.0 + ******************************** + * + * - 2.0: Add support for core options v2 interface + * - 1.3: Move translations to libretro_core_options_intl.h + * - libretro_core_options_intl.h includes BOM and utf-8 + * fix for MSVC 2010-2013 + * - Added HAVE_NO_LANGEXTRA flag to disable translations + * on platforms/compilers without BOM support + * - 1.2: Use core options v1 interface when + * RETRO_ENVIRONMENT_GET_CORE_OPTIONS_VERSION is >= 1 + * (previously required RETRO_ENVIRONMENT_GET_CORE_OPTIONS_VERSION == 1) + * - 1.1: Support generation of core options v0 retro_core_option_value + * arrays containing options with a single value + * - 1.0: First commit +*/ + +#ifdef __cplusplus +extern "C" { +#endif + +/* + ******************************** + * Core Option Definitions + ******************************** +*/ + +/* RETRO_LANGUAGE_ENGLISH */ + +/* Default language: + * - All other languages must include the same keys and values + * - Will be used as a fallback in the event that frontend language + * is not available + * - Will be used as a fallback for any missing entries in + * frontend language definition */ + +struct retro_core_option_v2_category option_cats_us[] = { + { + "video", /* key (category name) */ + "Video", /* category description (label) */ + "Configure display options." /* category sublabel */ + }, + { + "hacks", + "Advanced", + "Options affecting low-level emulation performance and accuracy." + }, + { NULL, NULL, NULL }, +}; + +struct retro_core_option_v2_definition option_defs_us[] = { + { + "mycore_region", /* key (option name) */ + "Console Region", /* description (label) */ + NULL, /* 'categorised' description (used instead of + * 'description' if frontend has category + * support; if NULL or empty, regular + * description is always used */ + "Specify which region the system is from.", /* sublabel */ + NULL, /* 'categorised' sublabel (used instead of + * 'sublabel' if frontend has category + * support; if NULL or empty, regular + * sublabel is always used */ + NULL, /* category key (must match an entry in + * option_cats_us; if NULL or empty, + * option is uncategorised */ + { + { "auto", "Auto" }, /* value_1, value_1_label */ + { "ntsc-j", "Japan" }, /* value_2, value_2_label */ + { "ntsc-u", "America" }, /* value_3, value_3_label */ + { "pal", "Europe" }, /* value_4, value_4_label */ + { NULL, NULL }, + }, + "auto" /* default_value */ + }, + { + "mycore_video_scale", + "Video > Scale", /* description: here a 'Video >' prefix is used to + * signify a category on frontends without explicit + * category support */ + "Scale", /* 'categorised' description: will be displayed inside + * the 'Video' submenu */ + "Set internal video scale factor.", + NULL, + "video", /* category key */ + { + { "1x", NULL }, /* If value itself is human-readable (e.g. a number) */ + { "2x", NULL }, /* and can displayed directly, the value_label should */ + { "3x", NULL }, /* be set to NULL */ + { "4x", NULL }, + { NULL, NULL }, + }, + "3x" + }, + { + "mycore_overclock", + "Advanced > Reduce Slowdown", + "Reduce Slowdown", + "Enabling 'Advanced > Reduce Slowdown' will reduce accuracy.", /* sublabel */ + "Enabling 'Reduce Slowdown' will reduce accuracy.", /* 'categorised' sublabel: + * will be displayed inside the 'Advanced' submenu; note that + * 'Advanced > Reduce Slowdown' is replaced with 'Reduce Slowdown' */ + "hacks", + { + { "enabled", NULL }, /* If value is equal to 'enabled' or 'disabled', */ + { "disabled", NULL }, /* value_label should be set to NULL */ + { NULL, NULL }, + }, + "disabled" + }, + { NULL, NULL, NULL, NULL, NULL, NULL, {{0}}, NULL }, +}; + +struct retro_core_options_v2 options_us = { + option_cats_us, + option_defs_us +}; + +/* + ******************************** + * Language Mapping + ******************************** +*/ + +#ifndef HAVE_NO_LANGEXTRA +struct retro_core_options_v2 *options_intl[RETRO_LANGUAGE_LAST] = { + &options_us, /* RETRO_LANGUAGE_ENGLISH */ + NULL, /* RETRO_LANGUAGE_JAPANESE */ + &options_fr, /* RETRO_LANGUAGE_FRENCH */ + NULL, /* RETRO_LANGUAGE_SPANISH */ + NULL, /* RETRO_LANGUAGE_GERMAN */ + NULL, /* RETRO_LANGUAGE_ITALIAN */ + NULL, /* RETRO_LANGUAGE_DUTCH */ + NULL, /* RETRO_LANGUAGE_PORTUGUESE_BRAZIL */ + NULL, /* RETRO_LANGUAGE_PORTUGUESE_PORTUGAL */ + NULL, /* RETRO_LANGUAGE_RUSSIAN */ + NULL, /* RETRO_LANGUAGE_KOREAN */ + NULL, /* RETRO_LANGUAGE_CHINESE_TRADITIONAL */ + NULL, /* RETRO_LANGUAGE_CHINESE_SIMPLIFIED */ + NULL, /* RETRO_LANGUAGE_ESPERANTO */ + NULL, /* RETRO_LANGUAGE_POLISH */ + NULL, /* RETRO_LANGUAGE_VIETNAMESE */ + NULL, /* RETRO_LANGUAGE_ARABIC */ + NULL, /* RETRO_LANGUAGE_GREEK */ + NULL, /* RETRO_LANGUAGE_TURKISH */ + NULL, /* RETRO_LANGUAGE_SLOVAK */ + NULL, /* RETRO_LANGUAGE_PERSIAN */ + NULL, /* RETRO_LANGUAGE_HEBREW */ + NULL, /* RETRO_LANGUAGE_ASTURIAN */ + NULL, /* RETRO_LANGUAGE_FINNISH */ +}; +#endif + +/* + ******************************** + * Functions + ******************************** +*/ + +/* Handles configuration/setting of core options. + * Should be called as early as possible - ideally inside + * retro_set_environment(), and no later than retro_load_game() + * > We place the function body in the header to avoid the + * necessity of adding more .c files (i.e. want this to + * be as painless as possible for core devs) + */ + +static INLINE void libretro_set_core_options(retro_environment_t environ_cb, + bool *categories_supported) +{ + unsigned version = 0; +#ifndef HAVE_NO_LANGEXTRA + unsigned language = 0; +#endif + + if (!environ_cb || !categories_supported) + return; + + *categories_supported = false; + + if (!environ_cb(RETRO_ENVIRONMENT_GET_CORE_OPTIONS_VERSION, &version)) + version = 0; + + if (version >= 2) + { +#ifndef HAVE_NO_LANGEXTRA + struct retro_core_options_v2_intl core_options_intl; + + core_options_intl.us = &options_us; + core_options_intl.local = NULL; + + if (environ_cb(RETRO_ENVIRONMENT_GET_LANGUAGE, &language) && + (language < RETRO_LANGUAGE_LAST) && (language != RETRO_LANGUAGE_ENGLISH)) + core_options_intl.local = options_intl[language]; + + *categories_supported = environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2_INTL, + &core_options_intl); +#else + *categories_supported = environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2, + &options_us); +#endif + } + else + { + size_t i, j; + size_t option_index = 0; + size_t num_options = 0; + struct retro_core_option_definition + *option_v1_defs_us = NULL; +#ifndef HAVE_NO_LANGEXTRA + size_t num_options_intl = 0; + struct retro_core_option_v2_definition + *option_defs_intl = NULL; + struct retro_core_option_definition + *option_v1_defs_intl = NULL; + struct retro_core_options_intl + core_options_v1_intl; +#endif + struct retro_variable *variables = NULL; + char **values_buf = NULL; + + /* Determine total number of options */ + while (true) + { + if (option_defs_us[num_options].key) + num_options++; + else + break; + } + + if (version >= 1) + { + /* Allocate US array */ + option_v1_defs_us = (struct retro_core_option_definition *) + calloc(num_options + 1, sizeof(struct retro_core_option_definition)); + + /* Copy parameters from option_defs_us array */ + for (i = 0; i < num_options; i++) + { + struct retro_core_option_v2_definition *option_def_us = &option_defs_us[i]; + struct retro_core_option_value *option_values = option_def_us->values; + struct retro_core_option_definition *option_v1_def_us = &option_v1_defs_us[i]; + struct retro_core_option_value *option_v1_values = option_v1_def_us->values; + + option_v1_def_us->key = option_def_us->key; + option_v1_def_us->desc = option_def_us->desc; + option_v1_def_us->info = option_def_us->info; + option_v1_def_us->default_value = option_def_us->default_value; + + /* Values must be copied individually... */ + while (option_values->value) + { + option_v1_values->value = option_values->value; + option_v1_values->label = option_values->label; + + option_values++; + option_v1_values++; + } + } + +#ifndef HAVE_NO_LANGEXTRA + if (environ_cb(RETRO_ENVIRONMENT_GET_LANGUAGE, &language) && + (language < RETRO_LANGUAGE_LAST) && (language != RETRO_LANGUAGE_ENGLISH) && + options_intl[language]) + option_defs_intl = options_intl[language]->definitions; + + if (option_defs_intl) + { + /* Determine number of intl options */ + while (true) + { + if (option_defs_intl[num_options_intl].key) + num_options_intl++; + else + break; + } + + /* Allocate intl array */ + option_v1_defs_intl = (struct retro_core_option_definition *) + calloc(num_options_intl + 1, sizeof(struct retro_core_option_definition)); + + /* Copy parameters from option_defs_intl array */ + for (i = 0; i < num_options_intl; i++) + { + struct retro_core_option_v2_definition *option_def_intl = &option_defs_intl[i]; + struct retro_core_option_value *option_values = option_def_intl->values; + struct retro_core_option_definition *option_v1_def_intl = &option_v1_defs_intl[i]; + struct retro_core_option_value *option_v1_values = option_v1_def_intl->values; + + option_v1_def_intl->key = option_def_intl->key; + option_v1_def_intl->desc = option_def_intl->desc; + option_v1_def_intl->info = option_def_intl->info; + option_v1_def_intl->default_value = option_def_intl->default_value; + + /* Values must be copied individually... */ + while (option_values->value) + { + option_v1_values->value = option_values->value; + option_v1_values->label = option_values->label; + + option_values++; + option_v1_values++; + } + } + } + + core_options_v1_intl.us = option_v1_defs_us; + core_options_v1_intl.local = option_v1_defs_intl; + + environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_INTL, &core_options_v1_intl); +#else + environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS, option_v1_defs_us); +#endif + } + else + { + /* Allocate arrays */ + variables = (struct retro_variable *)calloc(num_options + 1, + sizeof(struct retro_variable)); + values_buf = (char **)calloc(num_options, sizeof(char *)); + + if (!variables || !values_buf) + goto error; + + /* Copy parameters from option_defs_us array */ + for (i = 0; i < num_options; i++) + { + const char *key = option_defs_us[i].key; + const char *desc = option_defs_us[i].desc; + const char *default_value = option_defs_us[i].default_value; + struct retro_core_option_value *values = option_defs_us[i].values; + size_t buf_len = 3; + size_t default_index = 0; + + values_buf[i] = NULL; + + if (desc) + { + size_t num_values = 0; + + /* Determine number of values */ + while (true) + { + if (values[num_values].value) + { + /* Check if this is the default value */ + if (default_value) + if (strcmp(values[num_values].value, default_value) == 0) + default_index = num_values; + + buf_len += strlen(values[num_values].value); + num_values++; + } + else + break; + } + + /* Build values string */ + if (num_values > 0) + { + size_t j; + + buf_len += num_values - 1; + buf_len += strlen(desc); + + values_buf[i] = (char *)calloc(buf_len, sizeof(char)); + if (!values_buf[i]) + goto error; + + strcpy(values_buf[i], desc); + strcat(values_buf[i], "; "); + + /* Default value goes first */ + strcat(values_buf[i], values[default_index].value); + + /* Add remaining values */ + for (j = 0; j < num_values; j++) + { + if (j != default_index) + { + strcat(values_buf[i], "|"); + strcat(values_buf[i], values[j].value); + } + } + } + } + + variables[option_index].key = key; + variables[option_index].value = values_buf[i]; + option_index++; + } + + /* Set variables */ + environ_cb(RETRO_ENVIRONMENT_SET_VARIABLES, variables); + } + +error: + /* Clean up */ + + if (option_v1_defs_us) + { + free(option_v1_defs_us); + option_v1_defs_us = NULL; + } + +#ifndef HAVE_NO_LANGEXTRA + if (option_v1_defs_intl) + { + free(option_v1_defs_intl); + option_v1_defs_intl = NULL; + } +#endif + + if (values_buf) + { + for (i = 0; i < num_options; i++) + { + if (values_buf[i]) + { + free(values_buf[i]); + values_buf[i] = NULL; + } + } + + free(values_buf); + values_buf = NULL; + } + + if (variables) + { + free(variables); + variables = NULL; + } + } +} + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libretro-common/samples/core_options/example_categories/libretro_core_options_intl.h b/libretro-common/samples/core_options/example_categories/libretro_core_options_intl.h new file mode 100644 index 0000000000..0eaf762fb7 --- /dev/null +++ b/libretro-common/samples/core_options/example_categories/libretro_core_options_intl.h @@ -0,0 +1,157 @@ +#ifndef LIBRETRO_CORE_OPTIONS_INTL_H__ +#define LIBRETRO_CORE_OPTIONS_INTL_H__ + +#if defined(_MSC_VER) && (_MSC_VER >= 1500 && _MSC_VER < 1900) +/* https://support.microsoft.com/en-us/kb/980263 */ +#pragma execution_character_set("utf-8") +#pragma warning(disable:4566) +#endif + +#include + +/* + ******************************** + * VERSION: 2.0 + ******************************** + * + * - 2.0: Add support for core options v2 interface + * - 1.3: Move translations to libretro_core_options_intl.h + * - libretro_core_options_intl.h includes BOM and utf-8 + * fix for MSVC 2010-2013 + * - Added HAVE_NO_LANGEXTRA flag to disable translations + * on platforms/compilers without BOM support + * - 1.2: Use core options v1 interface when + * RETRO_ENVIRONMENT_GET_CORE_OPTIONS_VERSION is >= 1 + * (previously required RETRO_ENVIRONMENT_GET_CORE_OPTIONS_VERSION == 1) + * - 1.1: Support generation of core options v0 retro_core_option_value + * arrays containing options with a single value + * - 1.0: First commit +*/ + +#ifdef __cplusplus +extern "C" { +#endif + +/* + ******************************** + * Core Option Definitions + ******************************** +*/ + +/* RETRO_LANGUAGE_JAPANESE */ + +/* RETRO_LANGUAGE_FRENCH */ + +struct retro_core_option_v2_category option_cats_fr[] = { + { + "video", /* key must match option_cats_us entry */ + "Vidéo", /* translated category description */ + "Configurez les options d'affichage." /* translated category sublabel */ + }, + { + "hacks", + "Avancée", + "Options affectant les performances et la précision de l'émulation de bas niveau." + }, + { NULL, NULL, NULL }, +}; + +struct retro_core_option_v2_definition option_defs_fr[] = { + { + "mycore_region", /* key must match option_defs_us entry */ + "Région de la console", /* translated description */ + NULL, + "Spécifiez la région d'origine du système.", /* translated sublabel */ + NULL, + NULL, /* category key is taken from option_defs_us + * -> can set to NULL here */ + { + { "auto", "Auto" }, /* value must match option_defs_us entry */ + { "ntsc-j", "Japon" }, /* > only value_label should be translated */ + { "ntsc-u", "Amérique" }, + { "pal", "L'Europe" }, + { NULL, NULL }, + }, + NULL /* default_value is taken from option_defs_us + * -> can set to NULL here */ + }, + { + "mycore_video_scale", + "Vidéo > Échelle", /* translated description */ + "Échelle", /* translated 'categorised' description */ + "Définir le facteur d'échelle vidéo interne.", + NULL, + NULL, + { + { NULL, NULL }, /* If value_labels do not require translation (e.g. numbers), values may be omitted */ + }, + NULL + }, + { + "mycore_overclock", + "Avancé > Réduire le ralentissement", + "Réduire le ralentissement", + "L'activation de « Avancé > Réduire le ralentissement » réduira la précision.", /* translated sublabel */ + "L'activation de « Réduire le ralentissement » réduira la précision.", /* translated 'categorised' + * sublabel */ + NULL, + { + { NULL, NULL }, /* 'enabled' and 'disabled' values should not be translated */ + }, + NULL + }, + { NULL, NULL, NULL, NULL, NULL, NULL, {{0}}, NULL }, +}; + +struct retro_core_options_v2 options_fr = { + option_cats_fr, + option_defs_fr +}; + +/* RETRO_LANGUAGE_SPANISH */ + +/* RETRO_LANGUAGE_GERMAN */ + +/* RETRO_LANGUAGE_ITALIAN */ + +/* RETRO_LANGUAGE_DUTCH */ + +/* RETRO_LANGUAGE_PORTUGUESE_BRAZIL */ + +/* RETRO_LANGUAGE_PORTUGUESE_PORTUGAL */ + +/* RETRO_LANGUAGE_RUSSIAN */ + +/* RETRO_LANGUAGE_KOREAN */ + +/* RETRO_LANGUAGE_CHINESE_TRADITIONAL */ + +/* RETRO_LANGUAGE_CHINESE_SIMPLIFIED */ + +/* RETRO_LANGUAGE_ESPERANTO */ + +/* RETRO_LANGUAGE_POLISH */ + +/* RETRO_LANGUAGE_VIETNAMESE */ + +/* RETRO_LANGUAGE_ARABIC */ + +/* RETRO_LANGUAGE_GREEK */ + +/* RETRO_LANGUAGE_TURKISH */ + +/* RETRO_LANGUAGE_SLOVAK */ + +/* RETRO_LANGUAGE_PERSIAN */ + +/* RETRO_LANGUAGE_HEBREW */ + +/* RETRO_LANGUAGE_ASTURIAN */ + +/* RETRO_LANGUAGE_FINNISH */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/menu/cbs/menu_cbs_get_value.c b/menu/cbs/menu_cbs_get_value.c index 4653e1ab92..1cc1314d7f 100644 --- a/menu/cbs/menu_cbs_get_value.c +++ b/menu/cbs/menu_cbs_get_value.c @@ -1497,6 +1497,41 @@ static void menu_action_setting_disp_set_label_core_options( char *s, size_t len, const char *path, char *s2, size_t len2) +{ + const char *category = path; + const char *desc = NULL; + + /* Add 'more' value text */ + strlcpy(s, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_MORE), len); + *w = 19; + + /* If this is an options subcategory, fetch + * the category description */ + if (!string_is_empty(category)) + { + core_option_manager_t *coreopts = NULL; + + if (rarch_ctl(RARCH_CTL_CORE_OPTIONS_LIST_GET, &coreopts)) + desc = core_option_manager_get_category_desc( + coreopts, category); + } + + /* If this isn't a subcategory (or something + * went wrong...), use top level core options + * menu label */ + if (string_is_empty(desc)) + desc = msg_hash_to_str(MENU_ENUM_LABEL_VALUE_CORE_OPTIONS); + + strlcpy(s2, desc, len2); +} + +static void menu_action_setting_disp_set_label_core_option( + file_list_t* list, + unsigned *w, unsigned type, unsigned i, + const char *label, + char *s, size_t len, + const char *path, + char *s2, size_t len2) { core_option_manager_t *coreopts = NULL; const char *coreopt_label = NULL; @@ -1783,11 +1818,14 @@ static int menu_cbs_init_bind_get_string_representation_compare_label( BIND_ACTION_GET_VALUE(cbs, menu_action_setting_disp_set_label_menu_video_resolution); break; + case MENU_ENUM_LABEL_CORE_OPTIONS: + BIND_ACTION_GET_VALUE(cbs, + menu_action_setting_disp_set_label_core_options); + break; case MENU_ENUM_LABEL_PLAYLISTS_TAB: case MENU_ENUM_LABEL_LOAD_CONTENT_HISTORY: case MENU_ENUM_LABEL_DOWNLOADED_FILE_DETECT_CORE_LIST: case MENU_ENUM_LABEL_FAVORITES: - case MENU_ENUM_LABEL_CORE_OPTIONS: case MENU_ENUM_LABEL_CORE_OPTION_OVERRIDE_LIST: case MENU_ENUM_LABEL_CORE_CHEAT_OPTIONS: case MENU_ENUM_LABEL_SHADER_OPTIONS: @@ -2176,7 +2214,7 @@ int menu_cbs_init_bind_get_string_representation(menu_file_list_cbs_t *cbs, (type < MENU_SETTINGS_CHEEVOS_START)) { BIND_ACTION_GET_VALUE(cbs, - menu_action_setting_disp_set_label_core_options); + menu_action_setting_disp_set_label_core_option); return 0; } diff --git a/menu/cbs/menu_cbs_ok.c b/menu/cbs/menu_cbs_ok.c index 0944d9535e..a80c4fcacf 100644 --- a/menu/cbs/menu_cbs_ok.c +++ b/menu/cbs/menu_cbs_ok.c @@ -1172,6 +1172,13 @@ int generic_action_ok_displaylist_push(const char *path, info_label = label; dl_type = DISPLAYLIST_FILE_BROWSER_SELECT_FILE; break; + case ACTION_OK_DL_CORE_OPTIONS_LIST: + info.type = type; + info.directory_ptr = idx; + info_path = path; + info_label = label; + dl_type = DISPLAYLIST_GENERIC; + break; case ACTION_OK_DL_CONTENT_COLLECTION_LIST: info.type = type; info.directory_ptr = idx; @@ -3774,6 +3781,8 @@ int action_ok_core_option_dropdown_list(const char *path, option_path_str[0] = '\0'; option_lbl_str[0] = '\0'; + option_index = type - MENU_SETTINGS_CORE_OPTION_START; + /* Boolean options are toggled directly, * without the use of a drop-down list */ @@ -3781,8 +3790,6 @@ int action_ok_core_option_dropdown_list(const char *path, if (type < MENU_SETTINGS_CORE_OPTION_START) goto push_dropdown_list; - option_index = type - MENU_SETTINGS_CORE_OPTION_START; - /* > Get core options struct */ if (!rarch_ctl(RARCH_CTL_CORE_OPTIONS_LIST_GET, &coreopts) || (option_index >= coreopts->size)) @@ -3822,7 +3829,7 @@ push_dropdown_list: /* If this option is not a boolean toggle, * push drop-down list */ snprintf(option_path_str, sizeof(option_path_str), - "core_option_%d", (int)idx); + "core_option_%d", (int)option_index); snprintf(option_lbl_str, sizeof(option_lbl_str), "%d", type); @@ -5628,6 +5635,7 @@ DEFAULT_ACTION_OK_FUNC(action_ok_push_manual_content_scan_list, ACTION_OK_DL_MAN DEFAULT_ACTION_OK_FUNC(action_ok_manual_content_scan_dat_file, ACTION_OK_DL_MANUAL_CONTENT_SCAN_DAT_FILE) DEFAULT_ACTION_OK_FUNC(action_ok_push_core_manager_list, ACTION_OK_DL_CORE_MANAGER_LIST) DEFAULT_ACTION_OK_FUNC(action_ok_push_core_option_override_list, ACTION_OK_DL_CORE_OPTION_OVERRIDE_LIST) +DEFAULT_ACTION_OK_FUNC(action_ok_push_core_options_list, ACTION_OK_DL_CORE_OPTIONS_LIST) static int action_ok_open_uwp_permission_settings(const char *path, const char *label, unsigned type, size_t idx, size_t entry_idx) @@ -7722,7 +7730,7 @@ static int menu_cbs_init_bind_ok_compare_label(menu_file_list_cbs_t *cbs, {MENU_ENUM_LABEL_DUMP_DISC, action_ok_push_dump_disc_list}, {MENU_ENUM_LABEL_LOAD_DISC, action_ok_push_load_disc_list}, {MENU_ENUM_LABEL_SHADER_OPTIONS, action_ok_push_default}, - {MENU_ENUM_LABEL_CORE_OPTIONS, action_ok_push_default}, + {MENU_ENUM_LABEL_CORE_OPTIONS, action_ok_push_core_options_list}, {MENU_ENUM_LABEL_CORE_OPTION_OVERRIDE_LIST, action_ok_push_core_option_override_list}, {MENU_ENUM_LABEL_CORE_CHEAT_OPTIONS, action_ok_push_default}, {MENU_ENUM_LABEL_CORE_INPUT_REMAPPING_OPTIONS, action_ok_push_default}, diff --git a/menu/cbs/menu_cbs_sublabel.c b/menu/cbs/menu_cbs_sublabel.c index d61e6179bf..a23ce429a4 100644 --- a/menu/cbs/menu_cbs_sublabel.c +++ b/menu/cbs/menu_cbs_sublabel.c @@ -681,7 +681,6 @@ DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_save_current_config_override_content DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_save_current_config_override_game, MENU_ENUM_SUBLABEL_SAVE_CURRENT_CONFIG_OVERRIDE_GAME) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_shader_options, MENU_ENUM_SUBLABEL_SHADER_OPTIONS) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_core_input_remapping_options, MENU_ENUM_SUBLABEL_CORE_INPUT_REMAPPING_OPTIONS) -DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_core_options, MENU_ENUM_SUBLABEL_CORE_OPTIONS) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_core_option_override_list, MENU_ENUM_SUBLABEL_CORE_OPTION_OVERRIDE_LIST) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_core_options_reset, MENU_ENUM_SUBLABEL_CORE_OPTIONS_RESET) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_show_advanced_settings, MENU_ENUM_SUBLABEL_SHOW_ADVANCED_SETTINGS) @@ -1506,6 +1505,41 @@ static int action_bind_sublabel_playlist_entry( return 0; } +static int action_bind_sublabel_core_options( + file_list_t *list, + unsigned type, unsigned i, + const char *label, const char *path, + char *s, size_t len) +{ + const char *category = path; + const char *info = NULL; + + /* If this is an options subcategory, fetch + * the category info string */ + if (!string_is_empty(category)) + { + core_option_manager_t *coreopts = NULL; + + if (rarch_ctl(RARCH_CTL_CORE_OPTIONS_LIST_GET, &coreopts)) + info = core_option_manager_get_category_info( + coreopts, category); + } + + /* If this isn't a subcategory (or something + * went wrong...), use top level core options + * menu sublabel */ + if (string_is_empty(info)) + info = msg_hash_to_str(MENU_ENUM_SUBLABEL_CORE_OPTIONS); + + if (!string_is_empty(info)) + { + strlcpy(s, info, len); + return 1; + } + + return 0; +} + static int action_bind_sublabel_core_option( file_list_t *list, unsigned type, unsigned i, @@ -1518,7 +1552,8 @@ static int action_bind_sublabel_core_option( if (!rarch_ctl(RARCH_CTL_CORE_OPTIONS_LIST_GET, &opt)) return 0; - info = core_option_manager_get_info(opt, type - MENU_SETTINGS_CORE_OPTION_START); + info = core_option_manager_get_info(opt, + type - MENU_SETTINGS_CORE_OPTION_START, true); if (!string_is_empty(info)) strlcpy(s, info, len); diff --git a/menu/cbs/menu_cbs_title.c b/menu/cbs/menu_cbs_title.c index 51ecd29fd7..640442e12f 100644 --- a/menu/cbs/menu_cbs_title.c +++ b/menu/cbs/menu_cbs_title.c @@ -202,6 +202,39 @@ static int action_get_title_left_thumbnails( return 0; } +static int action_get_title_core_options_list( + const char *path, const char *label, unsigned menu_type, + char *s, size_t len) +{ + const char *category = path; + const char *title = NULL; + + /* If this is an options subcategory, fetch + * the category description */ + if (!string_is_empty(category)) + { + core_option_manager_t *coreopts = NULL; + + if (rarch_ctl(RARCH_CTL_CORE_OPTIONS_LIST_GET, &coreopts)) + title = core_option_manager_get_category_desc( + coreopts, category); + } + + /* If this isn't a subcategory (or something + * went wrong...), use top level core options + * menu label */ + if (string_is_empty(title)) + title = msg_hash_to_str(MENU_ENUM_LABEL_VALUE_CORE_OPTIONS); + + if (s && !string_is_empty(title)) + { + strlcpy(s, title, len); + return 1; + } + + return 0; +} + static int action_get_title_dropdown_item( const char *path, const char *label, unsigned menu_type, char *s, size_t len) @@ -210,61 +243,32 @@ static int action_get_title_dropdown_item( if (string_is_empty(path)) return 0; - if (string_starts_with_size(path, "core_option_", STRLEN_CONST("core_option_"))) + if (string_starts_with_size(path, "core_option_", + STRLEN_CONST("core_option_"))) { /* This is a core options item */ - struct string_list tmp_str_list = {0}; - int ret = 0; + struct string_list tmp_str_list = {0}; + core_option_manager_t *coreopts = NULL; + int ret = 0; string_list_initialize(&tmp_str_list); string_split_noalloc(&tmp_str_list, path, "_"); if (tmp_str_list.size > 0) { - core_option_manager_t *coreopts = NULL; - rarch_ctl(RARCH_CTL_CORE_OPTIONS_LIST_GET, &coreopts); if (coreopts) { - unsigned i; - unsigned menu_index = string_to_unsigned( + unsigned option_index = string_to_unsigned( tmp_str_list.elems[(unsigned)tmp_str_list.size - 1].data); - unsigned visible_index = 0; - unsigned option_index = 0; - bool option_found = false; - settings_t *settings = config_get_ptr(); - bool game_specific_options = settings->bools.game_specific_options; + const char *title = core_option_manager_get_desc( + coreopts, option_index, true); - /* Convert menu index to option index */ - if (game_specific_options) - menu_index--; - - for (i = 0; i < coreopts->size; i++) + if (s && !string_is_empty(title)) { - if (core_option_manager_get_visible(coreopts, i)) - { - if (visible_index == menu_index) - { - option_found = true; - option_index = i; - break; - } - visible_index++; - } - } - - /* If option was found, title == option description */ - if (option_found) - { - const char *title = core_option_manager_get_desc( - coreopts, option_index); - - if (s && !string_is_empty(title)) - { - SANITIZE_TO_STRING(s, title, len); - ret = 1; - } + strlcpy(s, title, len); + ret = 1; } } } @@ -568,7 +572,6 @@ DEFAULT_TITLE_MACRO(action_get_online_thumbnails_updater_list, MENU_ENUM_LABEL_ DEFAULT_TITLE_MACRO(action_get_online_pl_thumbnails_updater_list, MENU_ENUM_LABEL_VALUE_PL_THUMBNAILS_UPDATER_LIST) DEFAULT_TITLE_MACRO(action_get_add_content_list, MENU_ENUM_LABEL_VALUE_ADD_CONTENT_LIST) DEFAULT_TITLE_MACRO(action_get_configurations_list, MENU_ENUM_LABEL_VALUE_CONFIGURATIONS_LIST) -DEFAULT_TITLE_MACRO(action_get_core_options_list, MENU_ENUM_LABEL_VALUE_CORE_OPTIONS) DEFAULT_TITLE_MACRO(action_get_core_option_override_list, MENU_ENUM_LABEL_VALUE_CORE_OPTION_OVERRIDE_LIST) DEFAULT_TITLE_MACRO(action_get_quick_menu_list, MENU_ENUM_LABEL_VALUE_CONTENT_SETTINGS) DEFAULT_TITLE_MACRO(action_get_input_remapping_options_list, MENU_ENUM_LABEL_VALUE_CORE_INPUT_REMAPPING_OPTIONS) @@ -1130,7 +1133,7 @@ static int menu_cbs_init_bind_title_compare_label(menu_file_list_cbs_t *cbs, {MENU_ENUM_LABEL_ADD_CONTENT_LIST, action_get_add_content_list}, {MENU_ENUM_LABEL_CORE_OPTIONS, - action_get_core_options_list}, + action_get_title_core_options_list}, {MENU_ENUM_LABEL_DEFERRED_CORE_OPTION_OVERRIDE_LIST, action_get_core_option_override_list}, {MENU_ENUM_LABEL_CONTENT_SETTINGS, @@ -1451,7 +1454,7 @@ static int menu_cbs_init_bind_title_compare_label(menu_file_list_cbs_t *cbs, BIND_ACTION_GET_TITLE(cbs, action_get_configurations_list); break; case MENU_ENUM_LABEL_CORE_OPTIONS: - BIND_ACTION_GET_TITLE(cbs, action_get_core_options_list); + BIND_ACTION_GET_TITLE(cbs, action_get_title_core_options_list); break; case MENU_ENUM_LABEL_DEFERRED_CORE_OPTION_OVERRIDE_LIST: BIND_ACTION_GET_TITLE(cbs, action_get_core_option_override_list); diff --git a/menu/menu_cbs.h b/menu/menu_cbs.h index 3cf37e64b6..133f1c1351 100644 --- a/menu/menu_cbs.h +++ b/menu/menu_cbs.h @@ -214,7 +214,8 @@ enum ACTION_OK_DL_MANUAL_CONTENT_SCAN_DAT_FILE, ACTION_OK_DL_CORE_RESTORE_BACKUP_LIST, ACTION_OK_DL_CORE_DELETE_BACKUP_LIST, - ACTION_OK_DL_CORE_OPTION_OVERRIDE_LIST + ACTION_OK_DL_CORE_OPTION_OVERRIDE_LIST, + ACTION_OK_DL_CORE_OPTIONS_LIST }; /* Function callbacks */ diff --git a/menu/menu_displaylist.c b/menu/menu_displaylist.c index ec2b0d8feb..643821a043 100644 --- a/menu/menu_displaylist.c +++ b/menu/menu_displaylist.c @@ -932,6 +932,96 @@ static unsigned menu_displaylist_parse_core_manager_list( return count; } +static unsigned menu_displaylist_parse_core_option_dropdown_list( + menu_displaylist_info_t *info) +{ + unsigned count = 0; + struct string_list tmp_str_list = {0}; + unsigned option_index = 0; + unsigned checked = 0; + bool checked_found = false; + core_option_manager_t *coreopts = NULL; + struct core_option *option = NULL; + const char *val = NULL; + unsigned i, j; + + /* Fetch options */ + rarch_ctl(RARCH_CTL_CORE_OPTIONS_LIST_GET, &coreopts); + + if (!coreopts) + goto end; + + /* Path string has the format core_option_ + * > Extract option index */ + if (string_is_empty(info->path)) + goto end; + + string_list_initialize(&tmp_str_list); + string_split_noalloc(&tmp_str_list, info->path, "_"); + + if (tmp_str_list.size < 1) + goto end; + + option_index = string_to_unsigned( + tmp_str_list.elems[tmp_str_list.size - 1].data); + + /* Get option itself + current value */ + option = (struct core_option*)&coreopts->opts[option_index]; + val = core_option_manager_get_val(coreopts, option_index); + + if (!option || + string_is_empty(val)) + goto end; + + /* Loop over all option values */ + for (j = 0; j < option->vals->size; j++) + { + const char *val_str = option->vals->elems[j].data; + const char *val_label_str = option->val_labels->elems[j].data; + + if (!string_is_empty(val_label_str)) + { + char val_d[256]; + + val_d[0] = '\0'; + snprintf(val_d, sizeof(val_d), "%d", option_index); + + if (string_is_equal(val_label_str, msg_hash_to_str(MENU_ENUM_LABEL_ENABLED))) + val_label_str = msg_hash_to_str(MENU_ENUM_LABEL_VALUE_ON); + else if (string_is_equal(val_label_str, msg_hash_to_str(MENU_ENUM_LABEL_DISABLED))) + val_label_str = msg_hash_to_str(MENU_ENUM_LABEL_VALUE_OFF); + + if (menu_entries_append_enum(info->list, + val_label_str, + val_d, + MENU_ENUM_LABEL_NO_ITEMS, + MENU_SETTING_DROPDOWN_SETTING_CORE_OPTIONS_ITEM, j, 0)) + count++; + + if (!checked_found && string_is_equal(val_str, val)) + { + checked = j; + checked_found = true; + } + } + } + + if (checked_found) + { + menu_file_list_cbs_t *cbs = (menu_file_list_cbs_t*) + info->list->list[checked].actiondata; + + if (cbs) + cbs->checked = true; + + menu_navigation_set_selection(checked); + } + +end: + string_list_deinitialize(&tmp_str_list); + return count; +} + static unsigned menu_displaylist_parse_core_option_override_list( menu_displaylist_info_t *info) { @@ -2929,8 +3019,10 @@ static int menu_displaylist_parse_load_content_settings( if (settings->bools.quick_menu_show_options && !settings->bools.kiosk_mode_enable) { + /* Empty 'path' string signifies top level + * core options menu */ if (menu_entries_append_enum(list, - msg_hash_to_str(MENU_ENUM_LABEL_VALUE_CORE_OPTIONS), + "", msg_hash_to_str(MENU_ENUM_LABEL_CORE_OPTIONS), MENU_ENUM_LABEL_CORE_OPTIONS, MENU_SETTING_ACTION_CORE_OPTIONS, 0, 0)) @@ -11191,12 +11283,12 @@ bool menu_displaylist_ctl(enum menu_displaylist_ctl_state type, if (rarch_ctl(RARCH_CTL_HAS_CORE_OPTIONS, NULL)) { - size_t num_opts = 0; - bool game_specific_options = settings->bools.game_specific_options; + bool game_specific_options = settings->bools.game_specific_options; + const char *category = info->path; + bool is_category = !string_is_empty(category); + core_option_manager_t *coreopts = NULL; - rarch_ctl(RARCH_CTL_GET_CORE_OPTION_SIZE, &num_opts); - - if (game_specific_options) + if (game_specific_options && !is_category) if (menu_entries_append_enum(info->list, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_CORE_OPTION_OVERRIDE_LIST), msg_hash_to_str(MENU_ENUM_LABEL_CORE_OPTION_OVERRIDE_LIST), @@ -11204,20 +11296,73 @@ bool menu_displaylist_ctl(enum menu_displaylist_ctl_state type, MENU_SETTING_ACTION_CORE_OPTION_OVERRIDE_LIST, 0, 0)) count++; - if (num_opts != 0) + if (rarch_ctl(RARCH_CTL_CORE_OPTIONS_LIST_GET, &coreopts)) { - core_option_manager_t *coreopts = NULL; + nested_list_item_t *category_item = NULL; + nested_list_t *option_list = NULL; + nested_list_item_t *option_item = NULL; + const struct core_option *option = NULL; + size_t i; - if (rarch_ctl(RARCH_CTL_CORE_OPTIONS_LIST_GET, &coreopts)) + /* Empty 'category' string signifies top + * level core options menu */ + if (!is_category) + option_list = coreopts->option_map; + else { - for (i = 0; i < num_opts; i++) + category_item = nested_list_get_item(coreopts->option_map, + category, NULL); + if (category_item) + option_list = nested_list_item_get_children(category_item); + } + + if (option_list) + { + /* Loop over child options */ + for (i = 0; i < nested_list_get_size(option_list); i++) { - if (core_option_manager_get_visible(coreopts, i)) - if (menu_entries_append_enum(info->list, - core_option_manager_get_desc(coreopts, i), "", - MENU_ENUM_LABEL_CORE_OPTION_ENTRY, - (unsigned)(MENU_SETTINGS_CORE_OPTION_START + i), 0, 0)) - count++; + option_item = nested_list_get_item_idx(option_list, i); + option = (const struct core_option *) + nested_list_item_get_value(option_item); + + /* Check whether this is an option or a + * subcategory */ + if (option) + { + /* This is a regular option */ + size_t opt_idx = option->opt_idx; + + if (core_option_manager_get_visible(coreopts, opt_idx)) + if (menu_entries_append_enum(info->list, + core_option_manager_get_desc(coreopts, opt_idx, true), + "", MENU_ENUM_LABEL_CORE_OPTION_ENTRY, + (unsigned)(MENU_SETTINGS_CORE_OPTION_START + opt_idx), + 0, 0)) + count++; + } + else if (option_item) + { + /* This is a subcategory */ + const char *catgory_id = nested_list_item_get_id(option_item); + bool category_visible = core_option_manager_get_category_visible( + coreopts, catgory_id); + + /* Note: We use nested_list_item_get_id() because we + * guarantee that the list can only be two levels + * deep. If we supported arbitrary nesting, would + * have to use nested_list_item_get_address() here */ + + if (category_visible && + !string_is_empty(catgory_id)) + { + if (menu_entries_append_enum(info->list, + catgory_id, + msg_hash_to_str(MENU_ENUM_LABEL_CORE_OPTIONS), + MENU_ENUM_LABEL_CORE_OPTIONS, + MENU_SETTING_ACTION_CORE_OPTIONS, 0, 0)) + count++; + } + } } } } @@ -12473,107 +12618,8 @@ bool menu_displaylist_ctl(enum menu_displaylist_ctl_state type, menu_entries_ctl(MENU_ENTRIES_CTL_CLEAR, info->list); { if (string_starts_with_size(info->path, "core_option_", - STRLEN_CONST("core_option_"))) - { - struct string_list tmp_str_list = {0}; - string_list_initialize(&tmp_str_list); - string_split_noalloc(&tmp_str_list, info->path, "_"); - - if (tmp_str_list.size > 0) - { - core_option_manager_t *coreopts = NULL; - const char *val = NULL; - - rarch_ctl(RARCH_CTL_CORE_OPTIONS_LIST_GET, &coreopts); - - if (coreopts) - { - unsigned i; - unsigned size = (unsigned) - tmp_str_list.size; - unsigned menu_index = atoi(tmp_str_list.elems[size-1].data); - unsigned visible_index = 0; - unsigned option_index = 0; - bool option_found = false; - struct core_option *option = NULL; - bool checked_found = false; - unsigned checked = 0; - bool game_specific_options = settings->bools.game_specific_options; - - /* Note: Although we display value labels here, - * most logic is performed using values. This seems - * more appropriate somehow... */ - - /* Convert menu index to option index */ - if (game_specific_options) - menu_index--; - - for (i = 0; i < coreopts->size; i++) - { - if (core_option_manager_get_visible(coreopts, i)) - { - if (visible_index == menu_index) - { - option_found = true; - option_index = i; - break; - } - visible_index++; - } - } - - if (option_found) - { - val = core_option_manager_get_val(coreopts, option_index); - option = (struct core_option*)&coreopts->opts[option_index]; - } - - if (option) - { - unsigned k; - for (k = 0; k < option->vals->size; k++) - { - const char *val_str = option->vals->elems[k].data; - const char *val_label_str = option->val_labels->elems[k].data; - - if (!string_is_empty(val_label_str)) - { - char val_d[256]; - snprintf(val_d, sizeof(val_d), "%d", option_index); - - if (string_is_equal(val_label_str, msg_hash_to_str(MENU_ENUM_LABEL_ENABLED))) - val_label_str = msg_hash_to_str(MENU_ENUM_LABEL_VALUE_ON); - else if (string_is_equal(val_label_str, msg_hash_to_str(MENU_ENUM_LABEL_DISABLED))) - val_label_str = msg_hash_to_str(MENU_ENUM_LABEL_VALUE_OFF); - - if (menu_entries_append_enum(info->list, - val_label_str, - val_d, - MENU_ENUM_LABEL_NO_ITEMS, - MENU_SETTING_DROPDOWN_SETTING_CORE_OPTIONS_ITEM, k, 0)) - count++; - - if (!checked_found && string_is_equal(val_str, val)) - { - checked = k; - checked_found = true; - } - } - } - - if (checked_found) - { - menu_file_list_cbs_t *cbs = (menu_file_list_cbs_t*)info->list->list[checked].actiondata; - if (cbs) - cbs->checked = true; - menu_navigation_set_selection(checked); - } - } - } - } - - string_list_deinitialize(&tmp_str_list); - } + STRLEN_CONST("core_option_"))) + count = menu_displaylist_parse_core_option_dropdown_list(info); else { enum msg_hash_enums enum_idx = (enum msg_hash_enums)atoi(info->path); @@ -12930,106 +12976,8 @@ bool menu_displaylist_ctl(enum menu_displaylist_ctl_state type, menu_entries_ctl(MENU_ENTRIES_CTL_CLEAR, info->list); if (string_starts_with_size(info->path, "core_option_", - STRLEN_CONST("core_option_"))) - { - struct string_list tmp_str_list = {0}; - - string_list_initialize(&tmp_str_list); - string_split_noalloc(&tmp_str_list, info->path, "_"); - - if (tmp_str_list.size > 0) - { - core_option_manager_t *coreopts = NULL; - const char *val = NULL; - - rarch_ctl(RARCH_CTL_CORE_OPTIONS_LIST_GET, &coreopts); - - if (coreopts) - { - unsigned size = (unsigned) - tmp_str_list.size; - unsigned menu_index = atoi(tmp_str_list.elems[size-1].data); - unsigned visible_index = 0; - unsigned option_index = 0; - bool option_found = false; - struct core_option *option = NULL; - bool checked_found = false; - unsigned checked = 0; - unsigned i; - - /* Note: Although we display value labels here, - * most logic is performed using values. This seems - * more appropriate somehow... */ - - /* Convert menu index to option index */ - menu_index--; - - for (i = 0; i < coreopts->size; i++) - { - if (core_option_manager_get_visible(coreopts, i)) - { - if (visible_index == menu_index) - { - option_found = true; - option_index = i; - break; - } - visible_index++; - } - } - - if (option_found) - { - val = core_option_manager_get_val(coreopts, option_index); - option = (struct core_option*)&coreopts->opts[option_index]; - } - - if (option) - { - unsigned k; - for (k = 0; k < option->vals->size; k++) - { - const char *val_str = option->vals->elems[k].data; - const char *val_label_str = option->val_labels->elems[k].data; - - if (!string_is_empty(val_label_str)) - { - char val_d[256]; - snprintf(val_d, sizeof(val_d), "%d", option_index); - - if (string_is_equal(val_label_str, msg_hash_to_str(MENU_ENUM_LABEL_ENABLED))) - val_label_str = msg_hash_to_str(MENU_ENUM_LABEL_VALUE_ON); - else if (string_is_equal(val_label_str, msg_hash_to_str(MENU_ENUM_LABEL_DISABLED))) - val_label_str = msg_hash_to_str(MENU_ENUM_LABEL_VALUE_OFF); - - if (menu_entries_append_enum(info->list, - val_label_str, - val_d, - MENU_ENUM_LABEL_NO_ITEMS, - MENU_SETTING_DROPDOWN_SETTING_CORE_OPTIONS_ITEM_SPECIAL, k, 0)) - count++; - - if (!checked_found && string_is_equal(val_str, val)) - { - checked = k; - checked_found = true; - } - } - } - - if (checked_found) - { - menu_file_list_cbs_t *cbs = (menu_file_list_cbs_t*)info->list->list[checked].actiondata; - if (cbs) - cbs->checked = true; - menu_navigation_set_selection(checked); - } - } - } - } - - string_list_deinitialize(&tmp_str_list); - } + STRLEN_CONST("core_option_"))) + count = menu_displaylist_parse_core_option_dropdown_list(info); else { enum msg_hash_enums enum_idx = (enum msg_hash_enums)atoi(info->path); diff --git a/retroarch.c b/retroarch.c index 59058c90d9..db80f15d8c 100644 --- a/retroarch.c +++ b/retroarch.c @@ -15786,776 +15786,6 @@ int main(int argc, char *argv[]) } #endif -/* CORE OPTIONS */ -static const char *core_option_manager_parse_value_label( - const char *value, const char *value_label) -{ - /* 'value_label' may be NULL */ - const char *label = string_is_empty(value_label) ? - value : value_label; - - if (string_is_empty(label)) - return NULL; - - /* Any label starting with a digit (or +/-) - * cannot be a boolean string, and requires - * no further processing */ - if (ISDIGIT((unsigned char)*label) || - (*label == '+') || - (*label == '-')) - return label; - - /* Core devs have a habit of using arbitrary - * strings to label boolean values (i.e. enabled, - * Enabled, on, On, ON, true, True, TRUE, disabled, - * Disabled, off, Off, OFF, false, False, FALSE). - * These should all be converted to standard ON/OFF - * strings - * > Note: We require some duplication here - * (e.g. MENU_ENUM_LABEL_ENABLED *and* - * MENU_ENUM_LABEL_VALUE_ENABLED) in order - * to match both localised and non-localised - * strings. This function is not performance - * critical, so these extra comparisons do - * no harm */ - if (string_is_equal_noncase(label, msg_hash_to_str(MENU_ENUM_LABEL_ENABLED)) || - string_is_equal_noncase(label, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_ENABLED)) || - string_is_equal_noncase(label, "enable") || - string_is_equal_noncase(label, "on") || - string_is_equal_noncase(label, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_ON)) || - string_is_equal_noncase(label, "true") || - string_is_equal_noncase(label, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_TRUE))) - label = msg_hash_to_str(MENU_ENUM_LABEL_VALUE_ON); - else if (string_is_equal_noncase(label, msg_hash_to_str(MENU_ENUM_LABEL_DISABLED)) || - string_is_equal_noncase(label, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_DISABLED)) || - string_is_equal_noncase(label, "disable") || - string_is_equal_noncase(label, "off") || - string_is_equal_noncase(label, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_OFF)) || - string_is_equal_noncase(label, "false") || - string_is_equal_noncase(label, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_FALSE))) - label = msg_hash_to_str(MENU_ENUM_LABEL_VALUE_OFF); - - return label; -} - -static bool core_option_manager_parse_variable( - core_option_manager_t *opt, size_t idx, - const struct retro_variable *var, - config_file_t *config_src) -{ - size_t i; - union string_list_elem_attr attr; - const char *val_start = NULL; - char *value = NULL; - char *desc_end = NULL; - struct core_option *option = (struct core_option*)&opt->opts[idx]; - struct config_entry_list - *entry = NULL; - - /* All options are visible by default */ - option->visible = true; - - if (!string_is_empty(var->key)) - option->key = strdup(var->key); - if (!string_is_empty(var->value)) - value = strdup(var->value); - - if (!string_is_empty(value)) - desc_end = strstr(value, "; "); - - if (!desc_end) - goto error; - - *desc_end = '\0'; - - if (!string_is_empty(value)) - option->desc = strdup(value); - - val_start = desc_end + 2; - option->vals = string_split(val_start, "|"); - - if (!option->vals) - goto error; - - /* Legacy core option interface has no concept - * of value labels - * > Use actual values for display purposes */ - attr.i = 0; - option->val_labels = string_list_new(); - - if (!option->val_labels) - goto error; - - /* > Loop over values and 'extract' labels */ - for (i = 0; i < option->vals->size; i++) - { - const char *value = option->vals->elems[i].data; - const char *value_label = core_option_manager_parse_value_label( - value, NULL); - - /* Redundant safely check... */ - value_label = string_is_empty(value_label) ? - value : value_label; - - /* Append value label string */ - string_list_append(option->val_labels, value_label, attr); - } - - /* Legacy core option interface always uses first - * defined value as the default */ - option->default_index = 0; - option->index = 0; - - if (config_src) - entry = config_get_entry(config_src, option->key); - else - entry = config_get_entry(opt->conf, option->key); - - /* Set current config value */ - if (entry && !string_is_empty(entry->value)) - { - for (i = 0; i < option->vals->size; i++) - { - if (string_is_equal(option->vals->elems[i].data, entry->value)) - { - option->index = i; - break; - } - } - } - - free(value); - - return true; - -error: - free(value); - return false; -} - -static bool core_option_manager_parse_option( - core_option_manager_t *opt, size_t idx, - const struct retro_core_option_definition *option_def, - config_file_t *config_src) -{ - size_t i; - union string_list_elem_attr attr; - struct config_entry_list - *entry = NULL; - size_t num_vals = 0; - struct core_option *option = (struct core_option*)&opt->opts[idx]; - const struct retro_core_option_value - *values = option_def->values; - - /* All options are visible by default */ - option->visible = true; - - if (!string_is_empty(option_def->key)) - option->key = strdup(option_def->key); - - if (!string_is_empty(option_def->desc)) - option->desc = strdup(option_def->desc); - - if (!string_is_empty(option_def->info)) - option->info = strdup(option_def->info); - - /* Get number of values */ - for (;;) - { - if (string_is_empty(values[num_vals].value)) - break; - num_vals++; - } - - if (num_vals < 1) - return false; - - /* Initialise string lists */ - attr.i = 0; - option->vals = string_list_new(); - option->val_labels = string_list_new(); - - if (!option->vals || !option->val_labels) - return false; - - /* Initialise default value */ - option->default_index = 0; - option->index = 0; - - /* Extract value/label pairs */ - for (i = 0; i < num_vals; i++) - { - const char *value = values[i].value; - const char *value_label = values[i].label; - - /* Append value string - * > We know that 'value' is always valid */ - string_list_append(option->vals, value, attr); - - /* Value label requires additional processing */ - value_label = core_option_manager_parse_value_label( - value, value_label); - - /* > Redundant safely check... */ - value_label = string_is_empty(value_label) ? - value : value_label; - - /* Append value label string */ - string_list_append(option->val_labels, value_label, attr); - - /* Check whether this value is the default setting */ - if (!string_is_empty(option_def->default_value)) - { - if (string_is_equal(option_def->default_value, value)) - { - option->default_index = i; - option->index = i; - } - } - } - - if (config_src) - entry = config_get_entry(config_src, option->key); - else - entry = config_get_entry(opt->conf, option->key); - - /* Set current config value */ - if (entry && !string_is_empty(entry->value)) - { - for (i = 0; i < option->vals->size; i++) - { - if (string_is_equal(option->vals->elems[i].data, entry->value)) - { - option->index = i; - break; - } - } - } - - return true; -} - -/** - * core_option_manager_free: - * @opt : options manager handle - * - * Frees core option manager handle. - **/ -static void core_option_manager_free(core_option_manager_t *opt) -{ - size_t i; - - if (!opt) - return; - - for (i = 0; i < opt->size; i++) - { - if (opt->opts[i].desc) - free(opt->opts[i].desc); - if (opt->opts[i].info) - free(opt->opts[i].info); - if (opt->opts[i].key) - free(opt->opts[i].key); - - if (opt->opts[i].vals) - string_list_free(opt->opts[i].vals); - if (opt->opts[i].val_labels) - string_list_free(opt->opts[i].val_labels); - - opt->opts[i].desc = NULL; - opt->opts[i].info = NULL; - opt->opts[i].key = NULL; - opt->opts[i].vals = NULL; - } - - if (opt->conf) - config_file_free(opt->conf); - free(opt->opts); - free(opt); -} - -/** - * core_option_manager_new_vars: - * @conf_path : Filesystem path to write core option config file to. - * @src_conf_path : Filesystem path from which to load initial config settings. - * @vars : Pointer to variable array handle. - * - * Legacy version of core_option_manager_new(). - * Creates and initializes a core manager handle. - * - * Returns: handle to new core manager handle, otherwise NULL. - **/ -static core_option_manager_t *core_option_manager_new_vars( - const char *conf_path, const char *src_conf_path, - const struct retro_variable *vars) -{ - const struct retro_variable *var; - size_t size = 0; - config_file_t *config_src = NULL; - core_option_manager_t *opt = (core_option_manager_t*) - malloc(sizeof(*opt)); - - if (!opt) - return NULL; - - opt->conf = NULL; - opt->conf_path[0] = '\0'; - opt->opts = NULL; - opt->size = 0; - opt->updated = false; - - if (!string_is_empty(conf_path)) - if (!(opt->conf = config_file_new_from_path_to_string(conf_path))) - if (!(opt->conf = config_file_new_alloc())) - goto error; - - strlcpy(opt->conf_path, conf_path, sizeof(opt->conf_path)); - - /* Load source config file, if required */ - if (!string_is_empty(src_conf_path)) - config_src = config_file_new_from_path_to_string(src_conf_path); - - for (var = vars; var->key && var->value; var++) - size++; - - if (size == 0) - goto error; - - opt->opts = (struct core_option*)calloc(size, sizeof(*opt->opts)); - if (!opt->opts) - goto error; - - opt->size = size; - size = 0; - - for (var = vars; var->key && var->value; size++, var++) - { - if (!core_option_manager_parse_variable(opt, size, var, config_src)) - goto error; - } - - if (config_src) - config_file_free(config_src); - - return opt; - -error: - if (config_src) - config_file_free(config_src); - core_option_manager_free(opt); - return NULL; -} - -/** - * core_option_manager_new: - * @conf_path : Filesystem path to write core option config file to. - * @src_conf_path : Filesystem path from which to load initial config settings. - * @option_defs : Pointer to variable array handle. - * - * Creates and initializes a core manager handle. - * - * Returns: handle to new core manager handle, otherwise NULL. - **/ -static core_option_manager_t *core_option_manager_new( - const char *conf_path, const char *src_conf_path, - const struct retro_core_option_definition *option_defs) -{ - const struct retro_core_option_definition *option_def; - size_t size = 0; - config_file_t *config_src = NULL; - core_option_manager_t *opt = (core_option_manager_t*) - malloc(sizeof(*opt)); - - if (!opt) - return NULL; - - opt->conf = NULL; - opt->conf_path[0] = '\0'; - opt->opts = NULL; - opt->size = 0; - opt->updated = false; - - if (!string_is_empty(conf_path)) - if (!(opt->conf = config_file_new_from_path_to_string(conf_path))) - if (!(opt->conf = config_file_new_alloc())) - goto error; - - strlcpy(opt->conf_path, conf_path, sizeof(opt->conf_path)); - - /* Load source config file, if required */ - if (!string_is_empty(src_conf_path)) - config_src = config_file_new_from_path_to_string(src_conf_path); - - /* Note: 'option_def->info == NULL' is valid */ - for (option_def = option_defs; - option_def->key && option_def->desc && option_def->values[0].value; - option_def++) - size++; - - if (size == 0) - goto error; - - opt->opts = (struct core_option*)calloc(size, sizeof(*opt->opts)); - if (!opt->opts) - goto error; - - opt->size = size; - size = 0; - - /* Note: 'option_def->info == NULL' is valid */ - for (option_def = option_defs; - option_def->key && option_def->desc && option_def->values[0].value; - size++, option_def++) - if (!core_option_manager_parse_option(opt, size, option_def, config_src)) - goto error; - - if (config_src) - config_file_free(config_src); - - return opt; - -error: - if (config_src) - config_file_free(config_src); - core_option_manager_free(opt); - return NULL; -} - -/** - * core_option_manager_flush: - * @opt : options manager handle - * - * Writes core option key-pair values to file. - **/ -static void core_option_manager_flush( - config_file_t *conf, - core_option_manager_t *opt) -{ - size_t i; - - for (i = 0; i < opt->size; i++) - { - struct core_option *option = (struct core_option*)&opt->opts[i]; - - if (option) - config_set_string(conf, option->key, - opt->opts[i].vals->elems[opt->opts[i].index].data); - } -} - -/** - * core_option_manager_get_desc: - * @opt : options manager handle - * @index : index identifier of the option - * - * Gets description for an option. - * - * Returns: Description for an option. - **/ -const char *core_option_manager_get_desc( - core_option_manager_t *opt, size_t idx) -{ - if (!opt) - return NULL; - if (idx >= opt->size) - return NULL; - return opt->opts[idx].desc; -} - -/** - * core_option_manager_get_info: - * @opt : options manager handle - * @idx : idx identifier of the option - * - * Gets information text for an option. - * - * Returns: Information text for an option. - **/ -const char *core_option_manager_get_info( - core_option_manager_t *opt, size_t idx) -{ - if (!opt) - return NULL; - if (idx >= opt->size) - return NULL; - return opt->opts[idx].info; -} - -/** - * core_option_manager_get_val: - * @opt : options manager handle - * @index : index identifier of the option - * - * Gets value for an option. - * - * Returns: Value for an option. - **/ -const char *core_option_manager_get_val(core_option_manager_t *opt, size_t idx) -{ - struct core_option *option = NULL; - if (!opt) - return NULL; - if (idx >= opt->size) - return NULL; - option = (struct core_option*)&opt->opts[idx]; - return option->vals->elems[option->index].data; -} - -/** - * core_option_manager_get_val_label: - * @opt : options manager handle - * @idx : idx identifier of the option - * - * Gets value label for an option. - * - * Returns: Value label for an option. - **/ -const char *core_option_manager_get_val_label(core_option_manager_t *opt, size_t idx) -{ - struct core_option *option = NULL; - if (!opt) - return NULL; - if (idx >= opt->size) - return NULL; - option = (struct core_option*)&opt->opts[idx]; - return option->val_labels->elems[option->index].data; -} - -/** - * core_option_manager_get_visible: - * @opt : options manager handle - * @idx : idx identifier of the option - * - * Gets whether option should be visible when displaying - * core options in the frontend - * - * Returns: 'true' if option should be displayed by the frontend. - **/ -bool core_option_manager_get_visible(core_option_manager_t *opt, - size_t idx) -{ - if (!opt) - return false; - if (idx >= opt->size) - return false; - return opt->opts[idx].visible; -} - -void core_option_manager_set_val(core_option_manager_t *opt, - size_t idx, size_t val_idx) -{ - struct core_option *option = NULL; - - if (!opt) - return; - if (idx >= opt->size) - return; - - option = (struct core_option*)&opt->opts[idx]; - option->index = val_idx % option->vals->size; - - opt->updated = true; - -#ifdef HAVE_CHEEVOS - rcheevos_validate_config_settings(); -#endif -} - -static void core_option_manager_adjust_val(core_option_manager_t* opt, - size_t idx, int adjustment) -{ - struct core_option* option = NULL; - - if (!opt) - return; - if (idx >= opt->size) - return; - - option = (struct core_option*)&opt->opts[idx]; - option->index = (option->index + option->vals->size + adjustment) % option->vals->size; - - opt->updated = true; - -#ifdef HAVE_CHEEVOS - rcheevos_validate_config_settings(); -#endif -} - -/** - * core_option_manager_set_default: - * @opt : pointer to core option manager object. - * @idx : index of core option to be reset to defaults. - * - * Reset core option specified by @idx and sets default value for option. - **/ -void core_option_manager_set_default(core_option_manager_t *opt, size_t idx) -{ - if (!opt) - return; - if (idx >= opt->size) - return; - - opt->opts[idx].index = opt->opts[idx].default_index; - opt->updated = true; - -#ifdef HAVE_CHEEVOS - rcheevos_validate_config_settings(); -#endif -} - -static struct retro_core_option_definition *core_option_manager_get_definitions( - const struct retro_core_options_intl *core_options_intl) -{ - size_t i; - size_t num_options = 0; - struct retro_core_option_definition *option_defs_us = NULL; - struct retro_core_option_definition *option_defs_local = NULL; - struct retro_core_option_definition *option_defs = NULL; - - if (!core_options_intl) - return NULL; - - option_defs_us = core_options_intl->us; - option_defs_local = core_options_intl->local; - - if (!option_defs_us) - return NULL; - - /* Determine number of options */ - for (;;) - { - if (string_is_empty(option_defs_us[num_options].key)) - break; - num_options++; - } - - if (num_options < 1) - return NULL; - - /* Allocate output option_defs array - * > One extra entry required for terminating NULL entry - * > Note that calloc() sets terminating NULL entry and - * correctly 'nullifies' each values array */ - option_defs = (struct retro_core_option_definition *)calloc( - num_options + 1, sizeof(struct retro_core_option_definition)); - - if (!option_defs) - return NULL; - - /* Loop through options... */ - for (i = 0; i < num_options; i++) - { - size_t j; - size_t num_values = 0; - const char *key = option_defs_us[i].key; - const char *local_desc = NULL; - const char *local_info = NULL; - struct retro_core_option_value *local_values = NULL; - - /* Key is always taken from us english defs */ - option_defs[i].key = key; - - /* Default value is always taken from us english defs */ - option_defs[i].default_value = option_defs_us[i].default_value; - - /* Try to find corresponding entry in local defs array */ - if (option_defs_local) - { - size_t index = 0; - - for (;;) - { - const char *local_key = option_defs_local[index].key; - - if (string_is_empty(local_key)) - break; - - if (string_is_equal(key, local_key)) - { - local_desc = option_defs_local[index].desc; - local_info = option_defs_local[index].info; - local_values = option_defs_local[index].values; - break; - } - - index++; - } - } - - /* Set desc and info strings */ - option_defs[i].desc = string_is_empty(local_desc) ? option_defs_us[i].desc : local_desc; - option_defs[i].info = string_is_empty(local_info) ? option_defs_us[i].info : local_info; - - /* Determine number of values - * (always taken from us english defs) */ - for (;;) - { - if (string_is_empty(option_defs_us[i].values[num_values].value)) - break; - num_values++; - } - - /* Copy values */ - for (j = 0; j < num_values; j++) - { - const char *value = option_defs_us[i].values[j].value; - const char *local_label = NULL; - - /* Value string is always taken from us english defs */ - option_defs[i].values[j].value = value; - - /* Try to find corresponding entry in local defs values array */ - if (local_values) - { - size_t value_index = 0; - - for (;;) - { - const char *local_value = local_values[value_index].value; - - if (string_is_empty(local_value)) - break; - - if (string_is_equal(value, local_value)) - { - local_label = local_values[value_index].label; - break; - } - - value_index++; - } - } - - /* Set value label string */ - option_defs[i].values[j].label = string_is_empty(local_label) ? - option_defs_us[i].values[j].label : local_label; - } - } - - return option_defs; -} - -static void core_option_manager_set_display(core_option_manager_t *opt, - const char *key, bool visible) -{ - size_t i; - - if (!opt || string_is_empty(key)) - return; - - for (i = 0; i < opt->size; i++) - { - if (string_is_empty(opt->opts[i].key)) - continue; - - if (string_is_equal(opt->opts[i].key, key)) - { - opt->opts[i].visible = visible; - return; - } - } -} - /* DYNAMIC LIBRETRO CORE */ const struct retro_subsystem_info *libretro_find_subsystem_info( @@ -17214,6 +16444,20 @@ static bool rarch_environment_cb(unsigned cmd, void *data) if (ignore_environment_cb) return false; + /* RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE gets called + * by every core on every frame. Handle it first, + * to avoid the overhead of traversing the subsequent + * (enormous) case statement */ + if (cmd == RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE) + { + if (runloop_state.core_options) + *(bool*)data = runloop_state.core_options->updated; + else + *(bool*)data = false; + + return true; + } + switch (cmd) { case RETRO_ENVIRONMENT_GET_OVERSCAN: @@ -17234,6 +16478,7 @@ static bool rarch_environment_cb(unsigned cmd, void *data) { unsigned log_level = settings->uints.libretro_log_level; struct retro_variable *var = (struct retro_variable*)data; + size_t opt_idx; if (!var) return true; @@ -17247,96 +16492,132 @@ static bool rarch_environment_cb(unsigned cmd, void *data) return true; } - { - size_t i; - #ifdef HAVE_RUNAHEAD - if (runloop_state.core_options->updated) - p_rarch->has_variable_update = true; + if (runloop_state.core_options->updated) + p_rarch->has_variable_update = true; #endif + runloop_state.core_options->updated = false; - runloop_state.core_options->updated = false; - - for (i = 0; i < runloop_state.core_options->size; i++) - { - if (!string_is_empty(runloop_state.core_options->opts[i].key)) - if (string_is_equal( - runloop_state.core_options->opts[i].key, var->key)) - { - var->value = runloop_state.core_options->opts[i].vals->elems[ - runloop_state.core_options->opts[i].index].data; - break; - } - } - } + if (core_option_manager_get_idx(runloop_state.core_options, + var->key, &opt_idx)) + var->value = core_option_manager_get_val( + runloop_state.core_options, opt_idx); if (log_level == RETRO_LOG_DEBUG) { char s[128]; s[0] = '\0'; - snprintf(s, sizeof(s), "[Environ]: GET_VARIABLE %s:\n\t%s\n", var->key, var->value ? var->value : - msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NOT_AVAILABLE)); + snprintf(s, sizeof(s), "[Environ]: GET_VARIABLE %s:\n\t%s\n", + var->key, var->value ? var->value : + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NOT_AVAILABLE)); RARCH_LOG(s); } } break; - case RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE: - if (runloop_state.core_options) - *(bool*)data = runloop_state.core_options->updated; - else - *(bool*)data = false; - break; - /* SET_VARIABLES: Legacy path */ case RETRO_ENVIRONMENT_SET_VARIABLES: RARCH_LOG("[Environ]: SET_VARIABLES.\n"); if (runloop_state.core_options) retroarch_deinit_core_options(p_rarch, - path_get(RARCH_PATH_CORE_OPTIONS) - ); - retroarch_init_core_variables( - p_rarch, + path_get(RARCH_PATH_CORE_OPTIONS)); + + retroarch_init_core_variables(p_rarch, (const struct retro_variable *)data); + break; case RETRO_ENVIRONMENT_SET_CORE_OPTIONS: RARCH_LOG("[Environ]: SET_CORE_OPTIONS.\n"); - if (runloop_state.core_options) - retroarch_deinit_core_options(p_rarch, - path_get(RARCH_PATH_CORE_OPTIONS) - ); - rarch_init_core_options(p_rarch, - (const struct retro_core_option_definition*)data); + { + /* Parse core_option_definition array to + * create retro_core_options_v2 struct */ + struct retro_core_options_v2 *options_v2 = + core_option_manager_convert_v1( + (const struct retro_core_option_definition*)data); + if (runloop_state.core_options) + retroarch_deinit_core_options(p_rarch, + path_get(RARCH_PATH_CORE_OPTIONS)); + + if (options_v2) + { + /* Initialise core options */ + rarch_init_core_options(p_rarch, options_v2); + + /* Clean up */ + core_option_manager_free_converted(options_v2); + } + } break; case RETRO_ENVIRONMENT_SET_CORE_OPTIONS_INTL: RARCH_LOG("[Environ]: RETRO_ENVIRONMENT_SET_CORE_OPTIONS_INTL.\n"); { - struct retro_core_option_definition *option_defs = - core_option_manager_get_definitions((const struct retro_core_options_intl*)data); + /* Parse core_options_intl to create + * retro_core_options_v2 struct */ + struct retro_core_options_v2 *options_v2 = + core_option_manager_convert_v1_intl( + (const struct retro_core_options_intl*)data); if (runloop_state.core_options) retroarch_deinit_core_options(p_rarch, - path_get(RARCH_PATH_CORE_OPTIONS) - ); + path_get(RARCH_PATH_CORE_OPTIONS)); - /* Parse core_options_intl to create option definitions array */ - if (option_defs) + if (options_v2) { /* Initialise core options */ - rarch_init_core_options(p_rarch, option_defs); + rarch_init_core_options(p_rarch, options_v2); /* Clean up */ - free(option_defs); + core_option_manager_free_converted(options_v2); } + } + break; + case RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2: + RARCH_LOG("[Environ]: RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2.\n"); + + { + const struct retro_core_options_v2 *options_v2 = + (const struct retro_core_options_v2 *)data; + + if (runloop_state.core_options) + retroarch_deinit_core_options(p_rarch, + path_get(RARCH_PATH_CORE_OPTIONS)); + + if (options_v2) + rarch_init_core_options(p_rarch, options_v2); + } + break; + + case RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2_INTL: + RARCH_LOG("[Environ]: RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2_INTL.\n"); + + { + /* Parse retro_core_options_v2_intl to create + * retro_core_options_v2 struct */ + struct retro_core_options_v2 *options_v2 = + core_option_manager_convert_v2_intl( + (const struct retro_core_options_v2_intl*)data); + + if (runloop_state.core_options) + retroarch_deinit_core_options(p_rarch, + path_get(RARCH_PATH_CORE_OPTIONS)); + + if (options_v2) + { + /* Initialise core options */ + rarch_init_core_options(p_rarch, options_v2); + + /* Clean up */ + core_option_manager_free_converted(options_v2); + } } break; @@ -17344,10 +16625,11 @@ static bool rarch_environment_cb(unsigned cmd, void *data) RARCH_DBG("[Environ]: RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY.\n"); { - const struct retro_core_option_display *core_options_display = (const struct retro_core_option_display *)data; + const struct retro_core_option_display *core_options_display = + (const struct retro_core_option_display *)data; if (runloop_state.core_options && core_options_display) - core_option_manager_set_display( + core_option_manager_set_visible( runloop_state.core_options, core_options_display->key, core_options_display->visible); @@ -18661,8 +17943,8 @@ static bool rarch_environment_cb(unsigned cmd, void *data) case RETRO_ENVIRONMENT_GET_CORE_OPTIONS_VERSION: RARCH_LOG("[Environ]: GET_CORE_OPTIONS_VERSION.\n"); - /* Current API version is 1 */ - *(unsigned *)data = 1; + /* Current API version is 2 */ + *(unsigned *)data = 2; break; case RETRO_ENVIRONMENT_GET_TARGET_REFRESH_RATE: @@ -36708,7 +35990,7 @@ static void rarch_init_core_options_path( static void rarch_init_core_options( struct rarch_state *p_rarch, - const struct retro_core_option_definition *option_defs) + const struct retro_core_options_v2 *options_v2) { char options_path[PATH_MAX_LENGTH]; char src_options_path[PATH_MAX_LENGTH]; @@ -36719,12 +36001,13 @@ static void rarch_init_core_options( /* Get core options file path */ rarch_init_core_options_path(p_rarch, - options_path, sizeof(options_path), - src_options_path, sizeof(src_options_path)); + options_path, sizeof(options_path), + src_options_path, sizeof(src_options_path)); if (!string_is_empty(options_path)) runloop_state.core_options = - core_option_manager_new(options_path, src_options_path, option_defs); + core_option_manager_new(options_path, + src_options_path, options_v2); } void retroarch_init_task_queue(void) @@ -37024,8 +36307,8 @@ static void retroarch_deinit_core_options(struct rarch_state *p_rarch, if (conf_tmp) { core_option_manager_flush( - conf_tmp, - runloop_state.core_options); + runloop_state.core_options, + conf_tmp); RARCH_LOG("[Core Options]: Saved %s-specific core options to \"%s\"\n", runloop_state.game_options_active ? "game" : "folder", path_core_options); config_file_write(conf_tmp, path_core_options, true); @@ -37038,8 +36321,8 @@ static void retroarch_deinit_core_options(struct rarch_state *p_rarch, { const char *path = runloop_state.core_options->conf_path; core_option_manager_flush( - runloop_state.core_options->conf, - runloop_state.core_options); + runloop_state.core_options, + runloop_state.core_options->conf); RARCH_LOG("[Core Options]: Saved core options file to \"%s\"\n", path); config_file_write(runloop_state.core_options->conf, path, true); } @@ -40056,7 +39339,7 @@ bool core_options_create_override(bool game_specific) goto error; /* Write config file */ - core_option_manager_flush(conf, runloop_state.core_options); + core_option_manager_flush(runloop_state.core_options, conf); if (!config_file_write(conf, options_path, true)) goto error; diff --git a/retroarch_fwd_decls.h b/retroarch_fwd_decls.h index 3ed1cfdfce..b947fc8786 100644 --- a/retroarch_fwd_decls.h +++ b/retroarch_fwd_decls.h @@ -51,7 +51,7 @@ static void retroarch_init_core_variables( const struct retro_variable *vars); static void rarch_init_core_options( struct rarch_state *p_rarch, - const struct retro_core_option_definition *option_defs); + const struct retro_core_options_v2 *options_v2); #ifdef HAVE_RUNAHEAD #if defined(HAVE_DYNAMIC) || defined(HAVE_DYLIB) static bool secondary_core_create(struct rarch_state *p_rarch, diff --git a/ui/drivers/qt/qt_dialogs.cpp b/ui/drivers/qt/qt_dialogs.cpp index 8d8ca36f1c..441337320e 100644 --- a/ui/drivers/qt/qt_dialogs.cpp +++ b/ui/drivers/qt/qt_dialogs.cpp @@ -1163,7 +1163,7 @@ void CoreOptionsDialog::buildLayout() for (j = 0; j < opts; j++) { QString desc = - core_option_manager_get_desc(coreopts, j); + core_option_manager_get_desc(coreopts, j, false); QString val = core_option_manager_get_val(coreopts, j); QComboBox *combo_box = NULL;