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;