diff --git a/ui/xemu-settings.c b/ui/xemu-settings.c new file mode 100644 index 0000000000..fee5a6bf7d --- /dev/null +++ b/ui/xemu-settings.c @@ -0,0 +1,370 @@ +/* + * xemu Settings Management + * + * Copyright (C) 2020 Matt Borgerson + * + * This program 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 Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 this program. If not, see . + */ + +#include +#include +#include +#include +#include + +#include "xemu-settings.h" +#include "inih/ini.c" // FIXME + +enum config_types { + CONFIG_TYPE_STRING, + CONFIG_TYPE_INT, + CONFIG_TYPE_BOOL, + CONFIG_TYPE_ENUM, + CONFIG_TYPE__MAX +}; + +struct xemu_settings { + // [system] + char *flash_path; + char *bootrom_path; + char *hdd_path; + char *dvd_path; + char *eeprom_path; + int memory; + int short_animation; // Boolean + + // [display] + int scale; + + // [input] + char *controller_1_guid; + char *controller_2_guid; + char *controller_3_guid; + char *controller_4_guid; +}; + +struct enum_str_map { + int value; + const char *str; +}; + +static const struct enum_str_map display_scale_map[DISPLAY_SCALE__COUNT+1] = { + { DISPLAY_SCALE_CENTER, "center" }, + { DISPLAY_SCALE_SCALE, "scale" }, + { DISPLAY_SCALE_STRETCH, "stretch" }, + { 0, NULL }, +}; + +struct config_offset_table { + enum config_types type; + const char *section; + const char *name; + ptrdiff_t offset; + union { + const char *default_str; + int default_int; + int default_bool; + }; + const struct enum_str_map *enum_map; +} config_items[XEMU_SETTINGS__COUNT] = { + // Please keep organized by section + [XEMU_SETTINGS_SYSTEM_FLASH_PATH] = { CONFIG_TYPE_STRING, "system", "flash_path", offsetof(struct xemu_settings, flash_path), { .default_str = "" } }, + [XEMU_SETTINGS_SYSTEM_BOOTROM_PATH] = { CONFIG_TYPE_STRING, "system", "bootrom_path", offsetof(struct xemu_settings, bootrom_path), { .default_str = "" } }, + [XEMU_SETTINGS_SYSTEM_HDD_PATH] = { CONFIG_TYPE_STRING, "system", "hdd_path", offsetof(struct xemu_settings, hdd_path), { .default_str = "" } }, + [XEMU_SETTINGS_SYSTEM_DVD_PATH] = { CONFIG_TYPE_STRING, "system", "dvd_path", offsetof(struct xemu_settings, dvd_path), { .default_str = "" } }, + [XEMU_SETTINGS_SYSTEM_EEPROM_PATH] = { CONFIG_TYPE_STRING, "system", "eeprom_path", offsetof(struct xemu_settings, eeprom_path), { .default_str = "" } }, + [XEMU_SETTINGS_SYSTEM_MEMORY] = { CONFIG_TYPE_INT, "system", "memory", offsetof(struct xemu_settings, memory), { .default_int = 64 } }, + [XEMU_SETTINGS_SYSTEM_SHORTANIM] = { CONFIG_TYPE_BOOL, "system", "shortanim", offsetof(struct xemu_settings, short_animation), { .default_bool = 0 } }, + + [XEMU_SETTINGS_DISPLAY_SCALE] = { CONFIG_TYPE_ENUM, "display", "scale", offsetof(struct xemu_settings, scale), { .default_int = DISPLAY_SCALE_SCALE }, display_scale_map }, + + [XEMU_SETTINGS_INPUT_CONTROLLER_1_GUID] = { CONFIG_TYPE_STRING, "input", "controller_1_guid", offsetof(struct xemu_settings, controller_1_guid), { .default_str = "" } }, + [XEMU_SETTINGS_INPUT_CONTROLLER_2_GUID] = { CONFIG_TYPE_STRING, "input", "controller_2_guid", offsetof(struct xemu_settings, controller_2_guid), { .default_str = "" } }, + [XEMU_SETTINGS_INPUT_CONTROLLER_3_GUID] = { CONFIG_TYPE_STRING, "input", "controller_3_guid", offsetof(struct xemu_settings, controller_3_guid), { .default_str = "" } }, + [XEMU_SETTINGS_INPUT_CONTROLLER_4_GUID] = { CONFIG_TYPE_STRING, "input", "controller_4_guid", offsetof(struct xemu_settings, controller_4_guid), { .default_str = "" } }, +}; + +static const char *settings_path; +static const char *filename = "xemu.ini"; +static struct xemu_settings *g_settings; +static int settings_failed_to_load = 0; + +static void *xemu_settings_get_field(enum xemu_settings_keys key, enum config_types type) +{ + assert(key < XEMU_SETTINGS__COUNT); + assert(config_items[key].type == type); + return (void *)((char*)g_settings + config_items[key].offset); +} + +int xemu_settings_set_string(enum xemu_settings_keys key, const char *str) +{ + char **field_str = (char **)xemu_settings_get_field(key, CONFIG_TYPE_STRING); + free(*field_str); + *field_str = strdup(str); + return 0; +} + +int xemu_settings_get_string(enum xemu_settings_keys key, const char **str) +{ + *str = *(const char **)xemu_settings_get_field(key, CONFIG_TYPE_STRING); + return 0; +} + +int xemu_settings_set_int(enum xemu_settings_keys key, int val) +{ + int *field_int = (int *)xemu_settings_get_field(key, CONFIG_TYPE_INT); + *field_int = val; + return 0; +} + +int xemu_settings_get_int(enum xemu_settings_keys key, int *val) +{ + *val = *(int *)xemu_settings_get_field(key, CONFIG_TYPE_INT); + return 0; +} + +int xemu_settings_set_bool(enum xemu_settings_keys key, int val) +{ + int *field_int = (int *)xemu_settings_get_field(key, CONFIG_TYPE_BOOL); + *field_int = val; + return 0; +} + +int xemu_settings_get_bool(enum xemu_settings_keys key, int *val) +{ + *val = *(int *)xemu_settings_get_field(key, CONFIG_TYPE_BOOL); + return 0; +} + +int xemu_settings_set_enum(enum xemu_settings_keys key, int val) +{ + int *field_int = (int *)xemu_settings_get_field(key, CONFIG_TYPE_ENUM); + *field_int = val; + return 0; +} + +int xemu_settings_get_enum(enum xemu_settings_keys key, int *val) +{ + *val = *(int *)xemu_settings_get_field(key, CONFIG_TYPE_ENUM); + return 0; +} + +const char *xemu_settings_get_path(void) +{ + if (settings_path != NULL) { + return settings_path; + } + + // Note: Ideally SDL_GetPrefPath should be used here to determine where the + // settings file should be stored. However, until xemu gains a proper + // installer, assume it will be run in "portable mode" such that everything + // needed to run is all in the same directory, or specified explicitly by + // the user via config file. +#if 0 + char *base = SDL_GetPrefPath("xemu", "xemu"); +#else + // char *base = SDL_GetBasePath(); + // if (base == NULL) { + // base = strdup("./"); + // } + char *base = strdup("./"); +#endif + assert(base != NULL); + size_t base_len = strlen(base); + size_t filename_len = strlen(filename); + size_t len = base_len + filename_len + 1; + char *path = malloc(len); + memcpy(path, base, base_len); + free(base); + memcpy(path+base_len, filename, strlen(filename)); + path[len-1] = '\0'; + settings_path = path; + + fprintf(stderr, "%s: config path: %s\n", __func__, settings_path); + + return settings_path; +} + +static int xemu_enum_str_to_int(const struct enum_str_map *map, const char *str, int *value) +{ + for (int i = 0; map[i].str != NULL; i++) { + if (strcmp(map[i].str, str) == 0) { + *value = map[i].value; + return 0; + } + } + + return -1; +} + +static int xemu_enum_int_to_str(const struct enum_str_map *map, int value, const char **str) +{ + for (int i = 0; map[i].str != NULL; i++) { + if (map[i].value == value) { + *str = map[i].str; + return 0; + } + } + + return -1; +} + + +static enum xemu_settings_keys xemu_key_from_name(const char *section, const char *name) +{ + for (int i = 0; i < XEMU_SETTINGS__COUNT; i++) { + if ((strcmp(section, config_items[i].section) == 0) && + (strcmp(name, config_items[i].name) == 0)) { + return i; // Found + } + } + + return XEMU_SETTINGS_INVALID; +} + +static int config_parse_callback(void *user, const char *section, const char *name, const char *value) +{ + // struct xemu_settings *settings = (struct xemu_settings *)user; + fprintf(stderr, "%s: [%s] %s = %s\n", __func__, section, name, value); + + enum xemu_settings_keys key = xemu_key_from_name(section, name); + + if (key == XEMU_SETTINGS_INVALID) { + fprintf(stderr, "Ignoring unknown key %s.%s\n", section, name); + return 1; + } + + if (config_items[key].type == CONFIG_TYPE_STRING) { + xemu_settings_set_string(key, value); + } else if (config_items[key].type == CONFIG_TYPE_INT) { + int int_val; + int converted = sscanf(value, "%d", &int_val); + if (converted != 1) { + fprintf(stderr, "Error parsing %s.%s as integer. Got '%s'\n", section, name, value); + return 0; + } + xemu_settings_set_int(key, int_val); + } else if (config_items[key].type == CONFIG_TYPE_BOOL) { + int int_val; + if (strcmp(value, "true") == 0) { + int_val = 1; + } else if (strcmp(value, "false") == 0) { + int_val = 0; + } else { + fprintf(stderr, "Error parsing %s.%s as boolean. Got '%s'\n", section, name, value); + return 0; + } + xemu_settings_set_bool(key, int_val); + } else if (config_items[key].type == CONFIG_TYPE_ENUM) { + int int_val; + int status = xemu_enum_str_to_int(config_items[key].enum_map, value, &int_val); + if (status != 0) { + fprintf(stderr, "Error parsing %s.%s as enum. Got '%s'\n", section, name, value); + return 0; + } + xemu_settings_set_enum(key, int_val); + } else { + // Unimplemented + assert(0); + } + + // Success + return 1; +} + +static void xemu_settings_init_default(struct xemu_settings *settings) +{ + memset(settings, 0, sizeof(struct xemu_settings)); + for (int i = 0; i < XEMU_SETTINGS__COUNT; i++) { + if (config_items[i].type == CONFIG_TYPE_STRING) { + xemu_settings_set_string(i, config_items[i].default_str); + } else if (config_items[i].type == CONFIG_TYPE_INT) { + xemu_settings_set_int(i, config_items[i].default_int); + } else if (config_items[i].type == CONFIG_TYPE_BOOL) { + xemu_settings_set_bool(i, config_items[i].default_bool); + } else if (config_items[i].type == CONFIG_TYPE_ENUM) { + xemu_settings_set_enum(i, config_items[i].default_int); + } else { + // Unimplemented + assert(0); + } + } +} + +int xemu_settings_did_fail_to_load(void) +{ + return settings_failed_to_load; +} + +void xemu_settings_load(void) +{ + // Should only call this once, at startup + assert(g_settings == NULL); + + g_settings = malloc(sizeof(struct xemu_settings)); + assert(g_settings != NULL); + xemu_settings_init_default(g_settings); + + // Parse configuration file + int status = ini_parse(xemu_settings_get_path(), + config_parse_callback, + g_settings); + if (status < 0) { + // fprintf(stderr, "Failed to load config! Using defaults\n"); + settings_failed_to_load = 1; + } + + printf("Config loaded!\n"); +} + +int xemu_settings_save(void) +{ + FILE *fd = fopen(xemu_settings_get_path(), "wb"); + assert(fd != NULL); + + const char *last_section = ""; + for (int i = 0; i < XEMU_SETTINGS__COUNT; i++) { + if (strcmp(last_section, config_items[i].section)) { + fprintf(fd, "[%s]\n", config_items[i].section); + last_section = config_items[i].section; + } + + fprintf(fd, "%s = ", config_items[i].name); + if (config_items[i].type == CONFIG_TYPE_STRING) { + const char *v; + xemu_settings_get_string(i, &v); + fprintf(fd, "%s\n", v); + } else if (config_items[i].type == CONFIG_TYPE_INT) { + int v; + xemu_settings_get_int(i, &v); + fprintf(fd, "%d\n", v); + } else if (config_items[i].type == CONFIG_TYPE_BOOL) { + int v; + xemu_settings_get_bool(i, &v); + fprintf(fd, "%s\n", !!(v) ? "true" : "false"); + } else if (config_items[i].type == CONFIG_TYPE_ENUM) { + int v; + xemu_settings_get_enum(i, &v); + const char *str = ""; + xemu_enum_int_to_str(config_items[i].enum_map, v, &str); + fprintf(fd, "%s\n", str); + } else { + // Unimplemented + assert(0); + } + } + + fclose(fd); + return 0; +} diff --git a/ui/xemu-settings.h b/ui/xemu-settings.h new file mode 100644 index 0000000000..4e851fe6da --- /dev/null +++ b/ui/xemu-settings.h @@ -0,0 +1,83 @@ +/* + * xemu Settings Management + * + * Primary storage for non-volatile user configuration. Basic key-value storage + * that gets saved to an INI file. All entries should be accessed through the + * appropriate getter/setter functions. + * + * Copyright (C) 2020 Matt Borgerson + * + * This program 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 Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 this program. If not, see . + */ + +#ifndef XEMU_SETTINGS_H +#define XEMU_SETTINGS_H + +#ifdef __cplusplus +extern "C" { +#endif + +enum xemu_settings_keys { + XEMU_SETTINGS_SYSTEM_FLASH_PATH, + XEMU_SETTINGS_SYSTEM_BOOTROM_PATH, + XEMU_SETTINGS_SYSTEM_HDD_PATH, + XEMU_SETTINGS_SYSTEM_EEPROM_PATH, + XEMU_SETTINGS_SYSTEM_DVD_PATH, + XEMU_SETTINGS_SYSTEM_MEMORY, + XEMU_SETTINGS_SYSTEM_SHORTANIM, + XEMU_SETTINGS_DISPLAY_SCALE, + XEMU_SETTINGS_INPUT_CONTROLLER_1_GUID, + XEMU_SETTINGS_INPUT_CONTROLLER_2_GUID, + XEMU_SETTINGS_INPUT_CONTROLLER_3_GUID, + XEMU_SETTINGS_INPUT_CONTROLLER_4_GUID, + XEMU_SETTINGS__COUNT, + XEMU_SETTINGS_INVALID = -1 +}; + +enum DISPLAY_SCALE +{ + DISPLAY_SCALE_CENTER, + DISPLAY_SCALE_SCALE, + DISPLAY_SCALE_STRETCH, + DISPLAY_SCALE__COUNT, + DISPLAY_SCALE_INVALID = -1 +}; + +// Determine whether settings were loaded or not +int xemu_settings_did_fail_to_load(void); + +// Get path of the config file on disk +const char *xemu_settings_get_path(void); + +// Load config file from disk, or load defaults +void xemu_settings_load(void); + +// Save config file to disk +int xemu_settings_save(void); + +// Config item setters/getters +int xemu_settings_set_string(enum xemu_settings_keys key, const char *str); +int xemu_settings_get_string(enum xemu_settings_keys key, const char **str); +int xemu_settings_set_int(enum xemu_settings_keys key, int val); +int xemu_settings_get_int(enum xemu_settings_keys key, int *val); +int xemu_settings_set_bool(enum xemu_settings_keys key, int val); +int xemu_settings_get_bool(enum xemu_settings_keys key, int *val); +int xemu_settings_set_enum(enum xemu_settings_keys key, int val); +int xemu_settings_get_enum(enum xemu_settings_keys key, int *val); + +#ifdef __cplusplus +} +#endif + +#endif