diff --git a/libretro-common/include/libretro.h b/libretro-common/include/libretro.h index 8daec83601..8b223bfbb9 100644 --- a/libretro-common/include/libretro.h +++ b/libretro-common/include/libretro.h @@ -614,7 +614,7 @@ enum retro_mod * Afterward 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. - * + * * 'data' points to an array of retro_variable structs * terminated by a { NULL, NULL } element. * retro_variable::key should be namespaced to not collide @@ -1106,6 +1106,118 @@ enum retro_mod * It will return a bitmask of all the digital buttons. */ +#define RETRO_ENVIRONMENT_GET_CORE_OPTIONS_VERSION 52 + /* unsigned * -- + * Unsigned value is the API version number of the core options + * interface supported by the frontend. If callback return false, + * API version is assumed to be 0. + * + * In legacy code, core options are set by passing an array of + * retro_variable structs to RETRO_ENVIRONMENT_SET_VARIABLES. + * This may be still be done regardless of the core options + * interface version. + * + * If version is 1 however, core options may instead be set by + * passing an array of retro_core_option_definition structs to + * RETRO_ENVIRONMENT_SET_CORE_OPTIONS, or a 2D array of + * 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. + */ + +#define RETRO_ENVIRONMENT_SET_CORE_OPTIONS 53 + /* const struct retro_core_option_definition ** -- + * 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_ENHANCED_CORE_OPTIONS + * returns an API version of 1. + * This should be called instead of RETRO_ENVIRONMENT_SET_VARIABLES. + * 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. + * + * 'data' points to an array of retro_core_option_definition structs + * terminated by a { NULL, NULL, NULL, {{0}} } element. + * retro_core_option_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'. + * retro_core_option_definition::desc should contain a human readable + * description of the key. + * retro_core_option_definition::info should contain any additional human + * readable information text that a typical user may need to + * understand the functionality of the option. + * retro_variable::values is an array of retro_core_option_value + * structs terminated by a { NULL, NULL } element. + * > retro_variable::values[index].value is an expected option + * value. + * > retro_variable::values[index].label is a human readable + * label used when displaying the value on screen. If NULL, + * the value itself is used. + * + * The number of possible options 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. + * + * First entry should be treated as a default. + * + * Example entry: + * { + * "foo_option", + * "Speed hack coprocessor X", + * "Provides increased performance at the expense of reduced accuracy", + * { + * { "false", NULL }, + * { "true", NULL }, + * { "unstable", "Turbo (Unstable)" }, + * { NULL, NULL }, + * } + * } + * + * 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_INTL 54 + /* const struct retro_core_options_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_ENHANCED_CORE_OPTIONS + * returns an API version of 1. + * This should be called instead of RETRO_ENVIRONMENT_SET_VARIABLES. + * 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. + * + * This is fundamentally the same as RETRO_ENVIRONMENT_SET_CORE_OPTIONS, + * with the addition of localisation support. The description of the + * RETRO_ENVIRONMENT_SET_CORE_OPTIONS callback should be consulted + * for further details. + * + * 'data' points to a retro_core_options_intl struct. + * + * retro_core_options_intl::us is a pointer to an array of + * retro_core_option_definition structs defining the US English + * core options implementation. It must point to a valid array. + * + * retro_core_options_intl::local is a pointer to an array of + * retro_core_option_definition structs defining core options for + * the current frontend language. It may be NULL (in which case + * retro_core_options_intl::us is used by the frontend). Any items + * missing from this array will be read from retro_core_options_intl::us + * instead. + */ + /* VFS functionality */ /* File paths: @@ -2351,6 +2463,49 @@ struct retro_variable const char *value; }; +/* Maximum number of values permitted for a core option + * NOTE: This may be increased on a core-by-core basis + * if required (doing so has no effect on the frontend) */ +#define RETRO_NUM_CORE_OPTION_VALUES_MAX 128 + +struct retro_core_option_value +{ + /* Expected option value */ + const char *value; + + /* Human-readable value label. If NULL, value itself + * will be displayed by the frontend */ + const char *label; +}; + +struct retro_core_option_definition +{ + /* Variable to query in RETRO_ENVIRONMENT_GET_VARIABLE. */ + const char *key; + + /* Human-readable core option description (used as menu label) */ + const char *desc; + + /* Human-readable core option information (used as menu sublabel) */ + const char *info; + + /* Array of retro_core_option_value structs, terminated by NULL */ + struct retro_core_option_value values[RETRO_NUM_CORE_OPTION_VALUES_MAX]; +}; + +struct retro_core_options_intl +{ + /* Pointer to an array of retro_core_option_definition structs + * - US English implementation + * - Must point to a valid array */ + struct retro_core_option_definition *us; + + /* Pointer to an array of retro_core_option_definition structs + * - Implementation for current frontend language + * - May be NULL */ + struct retro_core_option_definition *local; +}; + struct retro_game_info { const char *path; /* Path to game, UTF-8 encoded. diff --git a/managers/core_option_manager.h b/managers/core_option_manager.h index 82c7a49122..e9161d4b9a 100644 --- a/managers/core_option_manager.h +++ b/managers/core_option_manager.h @@ -29,20 +29,22 @@ RETRO_BEGIN_DECLS struct core_option { - char *desc; - char *key; - struct string_list *vals; - size_t index; + char *desc; + char *info; + char *key; + struct string_list *vals; + struct string_list *val_labels; + size_t index; }; struct core_option_manager { - config_file_t *conf; - char conf_path[PATH_MAX_LENGTH]; + config_file_t *conf; + char conf_path[PATH_MAX_LENGTH]; - struct core_option *opts; - size_t size; - bool updated; + struct core_option *opts; + size_t size; + bool updated; }; typedef struct core_option_manager core_option_manager_t; @@ -68,6 +70,18 @@ void core_option_manager_set_default(core_option_manager_t *opt, size_t idx); const char *core_option_manager_get_desc(core_option_manager_t *opt, size_t idx); +/** + * 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); + /** * core_option_manager_get_val: * @opt : options manager handle @@ -80,6 +94,18 @@ const char *core_option_manager_get_desc(core_option_manager_t *opt, 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. + * + * Returns: Value label for an option. + **/ +const char *core_option_manager_get_val_label(core_option_manager_t *opt, + size_t idx); + void core_option_manager_set_val(core_option_manager_t *opt, size_t idx, size_t val_idx); diff --git a/menu/cbs/menu_cbs_get_value.c b/menu/cbs/menu_cbs_get_value.c index f33f010f60..1233df65ac 100644 --- a/menu/cbs/menu_cbs_get_value.c +++ b/menu/cbs/menu_cbs_get_value.c @@ -1109,13 +1109,19 @@ static void menu_action_setting_disp_set_label_core_options(file_list_t* list, if (rarch_ctl(RARCH_CTL_CORE_OPTIONS_LIST_GET, &coreopts)) { - core_opt = core_option_manager_get_val(coreopts, + core_opt = core_option_manager_get_val_label(coreopts, type - MENU_SETTINGS_CORE_OPTION_START); strlcpy(s, "", len); if (core_opt) + { + if (string_is_equal(core_opt, msg_hash_to_str(MENU_ENUM_LABEL_ENABLED))) + core_opt = msg_hash_to_str(MENU_ENUM_LABEL_VALUE_ON); + else if (string_is_equal(core_opt, msg_hash_to_str(MENU_ENUM_LABEL_DISABLED))) + core_opt = msg_hash_to_str(MENU_ENUM_LABEL_VALUE_OFF); strlcpy(s, core_opt, len); + } } strlcpy(s2, path, len2); diff --git a/menu/cbs/menu_cbs_sublabel.c b/menu/cbs/menu_cbs_sublabel.c index d94ec570d5..70032bbbc7 100644 --- a/menu/cbs/menu_cbs_sublabel.c +++ b/menu/cbs/menu_cbs_sublabel.c @@ -23,6 +23,7 @@ #include "../menu_cbs.h" #include "../../retroarch.h" +#include "../../managers/core_option_manager.h" #ifdef HAVE_CHEEVOS #include "../../cheevos-new/cheevos.h" @@ -1014,6 +1015,26 @@ static int action_bind_sublabel_playlist_entry( return 0; } +static int action_bind_sublabel_core_option( + file_list_t *list, + unsigned type, unsigned i, + const char *label, const char *path, + char *s, size_t len) +{ + core_option_manager_t *opt = NULL; + const char *info = NULL; + + 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); + + if (!string_is_empty(info)) + strlcpy(s, info, len); + + return 0; +} + static int action_bind_sublabel_generic( file_list_t *list, unsigned type, unsigned i, @@ -1102,6 +1123,12 @@ int menu_cbs_init_bind_sublabel(menu_file_list_cbs_t *cbs, } #endif + if (type >= MENU_SETTINGS_CORE_OPTION_START) + { + BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_core_option); + return 0; + } + if (cbs->enum_idx != MSG_UNKNOWN) { settings_t *settings; /* config_get_ptr is called only when needed */ diff --git a/menu/menu_displaylist.c b/menu/menu_displaylist.c index 2fd28c3f96..f9caada9b4 100644 --- a/menu/menu_displaylist.c +++ b/menu/menu_displaylist.c @@ -8050,6 +8050,10 @@ bool menu_displaylist_ctl(enum menu_displaylist_ctl_state type, bool checked_found = false; unsigned checked = 0; + /* Note: Although we display value labels here, + * most logic is performed using values. This seems + * more appropriate somehow... */ + if (settings->bools.game_specific_options) { val = core_option_manager_get_val(coreopts, i-1); @@ -8065,19 +8069,26 @@ bool menu_displaylist_ctl(enum menu_displaylist_ctl_state type, unsigned k; for (k = 0; k < option->vals->size; k++) { - const char *str = option->vals->elems[k].data; + const char *val_str = option->vals->elems[k].data; + const char *val_label_str = option->val_labels->elems[k].data; - if (!string_is_empty(str)) + if (!string_is_empty(val_label_str)) { char val_d[256]; snprintf(val_d, sizeof(val_d), "%d", i); + + 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); + menu_entries_append_enum(info->list, - str, + val_label_str, val_d, MENU_ENUM_LABEL_NO_ITEMS, MENU_SETTING_DROPDOWN_SETTING_CORE_OPTIONS_ITEM, k, 0); - if (!checked_found && string_is_equal(str, val)) + if (!checked_found && string_is_equal(val_str, val)) { checked = k; checked_found = true; @@ -8093,6 +8104,8 @@ bool menu_displaylist_ctl(enum menu_displaylist_ctl_state type, } } + if (tmp_str_list) + string_list_free(tmp_str_list); } else { @@ -8135,6 +8148,9 @@ bool menu_displaylist_ctl(enum menu_displaylist_ctl_state type, if (checked_found) menu_entries_set_checked(info->list, checked, true); } + + if (tmp_str_list) + string_list_free(tmp_str_list); } break; case ST_INT: @@ -8373,6 +8389,10 @@ bool menu_displaylist_ctl(enum menu_displaylist_ctl_state type, unsigned checked = 0; const char *val = core_option_manager_get_val(coreopts, i-1); + /* Note: Although we display value labels here, + * most logic is performed using values. This seems + * more appropriate somehow... */ + i--; option = (struct core_option*)&coreopts->opts[i]; @@ -8382,20 +8402,26 @@ bool menu_displaylist_ctl(enum menu_displaylist_ctl_state type, unsigned k; for (k = 0; k < option->vals->size; k++) { - const char *str = option->vals->elems[k].data; + const char *val_str = option->vals->elems[k].data; + const char *val_label_str = option->val_labels->elems[k].data; - if (!string_is_empty(str)) + if (!string_is_empty(val_label_str)) { char val_d[256]; snprintf(val_d, sizeof(val_d), "%d", i); - + + 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); + menu_entries_append_enum(info->list, - str, + val_label_str, val_d, MENU_ENUM_LABEL_NO_ITEMS, MENU_SETTING_DROPDOWN_SETTING_CORE_OPTIONS_ITEM_SPECIAL, k, 0); - if (!checked_found && string_is_equal(str, val)) + if (!checked_found && string_is_equal(val_str, val)) { checked = k; checked_found = true; @@ -8409,6 +8435,8 @@ bool menu_displaylist_ctl(enum menu_displaylist_ctl_state type, } } + if (tmp_str_list) + string_list_free(tmp_str_list); } else { @@ -8450,6 +8478,9 @@ bool menu_displaylist_ctl(enum menu_displaylist_ctl_state type, if (checked_found) menu_entries_set_checked(info->list, checked, true); } + + if (tmp_str_list) + string_list_free(tmp_str_list); } break; case ST_INT: diff --git a/retroarch.c b/retroarch.c index 70a895e65d..9d5572779b 100644 --- a/retroarch.c +++ b/retroarch.c @@ -1713,6 +1713,13 @@ static bool core_option_manager_parse_variable( if (!option->vals) goto error; + /* Legacy core option interface has no concept of + * value labels - use actual values for display purposes */ + option->val_labels = string_list_clone(option->vals); + + if (!option->val_labels) + goto error; + if (config_get_string(opt->conf, option->key, &config_val)) { size_t i; @@ -1738,6 +1745,77 @@ error: 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) +{ + size_t i; + union string_list_elem_attr attr; + size_t num_vals = 0; + char *config_val = NULL; + struct core_option *option = (struct core_option*)&opt->opts[idx]; + const struct retro_core_option_value *values = option_def->values; + + 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 */ + while (true) + { + if (!string_is_empty(values[num_vals].value)) + num_vals++; + else + break; + } + + 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; + + /* Extract value/label pairs */ + for (i = 0; i < num_vals; i++) + { + /* We know that 'value' is valid */ + string_list_append(option->vals, values[i].value, attr); + + /* Value 'label' may be NULL */ + if (!string_is_empty(values[i].label)) + string_list_append(option->val_labels, values[i].label, attr); + else + string_list_append(option->val_labels, values[i].value, attr); + } + + /* Set current config value */ + if (config_get_string(opt->conf, option->key, &config_val)) + { + for (i = 0; i < option->vals->size; i++) + { + if (string_is_equal(option->vals->elems[i].data, config_val)) + { + option->index = i; + break; + } + } + + free(config_val); + } + + return true; +} + /** * core_option_manager_free: * @opt : options manager handle @@ -1755,13 +1833,18 @@ static void core_option_manager_free(core_option_manager_t *opt) { 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; } @@ -1800,15 +1883,16 @@ static void core_option_manager_get(core_option_manager_t *opt, } /** - * core_option_manager_new: + * core_option_manager_new_vars: * @conf_path : Filesystem path to write core option config file to. * @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(const char *conf_path, +static core_option_manager_t *core_option_manager_new_vars(const char *conf_path, const struct retro_variable *vars) { const struct retro_variable *var; @@ -1855,6 +1939,68 @@ error: return NULL; } +/** + * core_option_manager_new: + * @conf_path : Filesystem path to write core option config file to. + * @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 struct retro_core_option_definition *option_defs) +{ + const struct retro_core_option_definition *option_def; + size_t size = 0; + core_option_manager_t *opt = (core_option_manager_t*) + calloc(1, sizeof(*opt)); + + if (!opt) + return NULL; + + if (!string_is_empty(conf_path)) + opt->conf = config_file_new(conf_path); + if (!opt->conf) + opt->conf = config_file_new(NULL); + + strlcpy(opt->conf_path, conf_path, sizeof(opt->conf_path)); + + if (!opt->conf) + goto error; + + /* 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)) + goto error; + } + + return opt; + +error: + core_option_manager_free(opt); + return NULL; +} + /** * core_option_manager_flush: * @opt : options manager handle @@ -1921,9 +2067,30 @@ const char *core_option_manager_get_desc( { 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 @@ -1938,10 +2105,32 @@ 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; +} + void core_option_manager_set_val(core_option_manager_t *opt, size_t idx, size_t val_idx) { @@ -1949,6 +2138,8 @@ void core_option_manager_set_val(core_option_manager_t *opt, if (!opt) return; + if (idx >= opt->size) + return; option = (struct core_option*)&opt->opts[idx]; option->index = val_idx % option->vals->size; @@ -1967,11 +2158,148 @@ 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 = 0; opt->updated = true; } +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 */ + while (true) + { + if (!string_is_empty(option_defs_us[num_options].key)) + num_options++; + else + break; + } + + 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; + + /* Try to find corresponding entry in local defs array */ + if (option_defs_local) + { + size_t index = 0; + + while (true) + { + const char *local_key = option_defs_local[index].key; + + if (!string_is_empty(local_key)) + { + 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; + } + else + index++; + } + else + break; + } + } + + /* 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) */ + while (true) + { + if (!string_is_empty(option_defs_us[i].values[num_values].value)) + num_values++; + else + break; + } + + /* 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; + + while (true) + { + const char *local_value = local_values[value_index].value; + + if (!string_is_empty(local_value)) + { + if (string_is_equal(value, local_value)) + { + local_label = local_values[value_index].label; + break; + } + else + value_index++; + } + else + break; + } + } + + /* 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; +} + /* DYNAMIC LIBRETRO CORE */ @@ -2569,14 +2897,31 @@ bool rarch_environment_cb(unsigned cmd, void *data) *(bool*)data = false; break; + /* SET_VARIABLES: Legacy path */ case RETRO_ENVIRONMENT_SET_VARIABLES: RARCH_LOG("Environ SET_VARIABLES.\n"); + rarch_ctl(RARCH_CTL_CORE_OPTIONS_DEINIT, NULL); + rarch_ctl(RARCH_CTL_CORE_VARIABLES_INIT, data); + + break; + + case RETRO_ENVIRONMENT_SET_CORE_OPTIONS: + RARCH_LOG("Environ SET_CORE_OPTIONS.\n"); + rarch_ctl(RARCH_CTL_CORE_OPTIONS_DEINIT, NULL); rarch_ctl(RARCH_CTL_CORE_OPTIONS_INIT, data); break; + case RETRO_ENVIRONMENT_SET_CORE_OPTIONS_INTL: + RARCH_LOG("Environ RETRO_ENVIRONMENT_SET_CORE_OPTIONS_INTL.\n"); + + rarch_ctl(RARCH_CTL_CORE_OPTIONS_DEINIT, NULL); + rarch_ctl(RARCH_CTL_CORE_OPTIONS_INTL_INIT, data); + + break; + case RETRO_ENVIRONMENT_SET_MESSAGE: { const struct retro_message *msg = (const struct retro_message*)data; @@ -3432,6 +3777,11 @@ bool rarch_environment_cb(unsigned cmd, void *data) /* Just falldown, the function will return true */ break; + case RETRO_ENVIRONMENT_GET_CORE_OPTIONS_VERSION: + /* Current API version is 1 */ + *(unsigned *)data = 1; + break; + case RETRO_ENVIRONMENT_GET_TARGET_REFRESH_RATE: { /* Try to use the polled refresh rate first. */ @@ -18045,6 +18395,42 @@ static void runloop_task_msg_queue_push( runloop_msg_queue_push(msg, prio, duration, flush, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); } +static void rarch_init_core_options( + const struct retro_core_option_definition *option_defs) +{ + settings_t *settings = configuration_settings; + char *game_options_path = NULL; + + if (settings->bools.game_specific_options && + rarch_game_specific_options(&game_options_path)) + { + runloop_game_options_active = true; + runloop_core_options = + core_option_manager_new(game_options_path, option_defs); + free(game_options_path); + } + else + { + char buf[PATH_MAX_LENGTH]; + const char *options_path = settings ? settings->paths.path_core_options : NULL; + + buf[0] = '\0'; + + if (string_is_empty(options_path) && !path_is_empty(RARCH_PATH_CONFIG)) + { + fill_pathname_resolve_relative(buf, path_get(RARCH_PATH_CONFIG), + file_path_str(FILE_PATH_CORE_OPTIONS_CONFIG), sizeof(buf)); + options_path = buf; + } + + runloop_game_options_active = false; + + if (!string_is_empty(options_path)) + runloop_core_options = + core_option_manager_new(options_path, option_defs); + } +} + bool rarch_ctl(enum rarch_ctl_state state, void *data) { static bool has_set_username = false; @@ -18539,7 +18925,7 @@ bool rarch_ctl(enum rarch_ctl_state state, void *data) } } break; - case RARCH_CTL_CORE_OPTIONS_INIT: + case RARCH_CTL_CORE_VARIABLES_INIT: { settings_t *settings = configuration_settings; char *game_options_path = NULL; @@ -18551,7 +18937,7 @@ bool rarch_ctl(enum rarch_ctl_state state, void *data) { runloop_game_options_active = true; runloop_core_options = - core_option_manager_new(game_options_path, vars); + core_option_manager_new_vars(game_options_path, vars); free(game_options_path); } else @@ -18572,11 +18958,39 @@ bool rarch_ctl(enum rarch_ctl_state state, void *data) if (!string_is_empty(options_path)) runloop_core_options = - core_option_manager_new(options_path, vars); + core_option_manager_new_vars(options_path, vars); } - } break; + case RARCH_CTL_CORE_OPTIONS_INIT: + { + const struct retro_core_option_definition *option_defs = + (const struct retro_core_option_definition*)data; + + rarch_init_core_options(option_defs); + } + break; + + case RARCH_CTL_CORE_OPTIONS_INTL_INIT: + { + const struct retro_core_options_intl *core_options_intl = + (const struct retro_core_options_intl*)data; + + /* Parse core_options_intl to create option definitions array */ + struct retro_core_option_definition *option_defs = + core_option_manager_get_definitions(core_options_intl); + + if (option_defs) + { + /* Initialise core options */ + rarch_init_core_options(option_defs); + + /* Clean up */ + free(option_defs); + } + } + break; + case RARCH_CTL_CORE_OPTIONS_DEINIT: { if (!runloop_core_options) diff --git a/retroarch.h b/retroarch.h index 678598816a..ca7fa3e84a 100644 --- a/retroarch.h +++ b/retroarch.h @@ -180,7 +180,9 @@ enum rarch_ctl_state RARCH_CTL_CORE_OPTIONS_LIST_GET, RARCH_CTL_CORE_OPTION_PREV, RARCH_CTL_CORE_OPTION_NEXT, + RARCH_CTL_CORE_VARIABLES_INIT, RARCH_CTL_CORE_OPTIONS_INIT, + RARCH_CTL_CORE_OPTIONS_INTL_INIT, RARCH_CTL_CORE_OPTIONS_DEINIT, /* System info */