xemu/ui/xemu-settings.c

425 lines
13 KiB
C

/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <stdlib.h>
#include <SDL_filesystem.h>
#include <string.h>
#include <stddef.h>
#include <assert.h>
#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
// [audio]
int use_dsp; // Boolean
// [display]
int scale;
int ui_scale;
// [input]
char *controller_1_guid;
char *controller_2_guid;
char *controller_3_guid;
char *controller_4_guid;
// [network]
int net_enabled; // Boolean
int net_backend;
char *net_local_addr;
char *net_remote_addr;
// [misc]
char *user_token;
};
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 },
};
static const struct enum_str_map net_backend_map[XEMU_NET_BACKEND__COUNT+1] = {
{ XEMU_NET_BACKEND_USER, "user" },
{ XEMU_NET_BACKEND_SOCKET_UDP, "udp" },
{ 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_AUDIO_USE_DSP] = { CONFIG_TYPE_BOOL, "audio", "use_dsp", offsetof(struct xemu_settings, use_dsp), { .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_DISPLAY_UI_SCALE] = { CONFIG_TYPE_INT, "display", "ui_scale", offsetof(struct xemu_settings, ui_scale), { .default_int = 1 } },
[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 = "" } },
[XEMU_SETTINGS_NETWORK_ENABLED] = { CONFIG_TYPE_BOOL, "network", "enabled", offsetof(struct xemu_settings, net_enabled), { .default_bool = 0 } },
[XEMU_SETTINGS_NETWORK_BACKEND] = { CONFIG_TYPE_ENUM, "network", "backend", offsetof(struct xemu_settings, net_backend), { .default_int = XEMU_NET_BACKEND_USER }, net_backend_map },
[XEMU_SETTINGS_NETWORK_LOCAL_ADDR] = { CONFIG_TYPE_STRING, "network", "local_addr", offsetof(struct xemu_settings, net_local_addr), { .default_str = "0.0.0.0:9368" } },
[XEMU_SETTINGS_NETWORK_REMOTE_ADDR] = { CONFIG_TYPE_STRING, "network", "remote_addr", offsetof(struct xemu_settings, net_remote_addr), { .default_str = "1.2.3.4:9368" } },
[XEMU_SETTINGS_MISC_USER_TOKEN] = { CONFIG_TYPE_STRING, "misc", "user_token", offsetof(struct xemu_settings, user_token), { .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;
}
char *base = SDL_GetPrefPath("xemu", "xemu");
assert(base != NULL);
size_t base_len = strlen(base);
size_t filename_len = strlen(filename);
size_t final_len = base_len + filename_len;
final_len += 1; // Terminating null byte
char *path = malloc(final_len);
assert(path != NULL);
// Copy base part
memcpy(path, base, base_len);
free(base);
// Copy filename part
memcpy(path+base_len, filename, strlen(filename));
path[final_len-1] = '\0';
settings_path = path;
fprintf(stderr, "%s: config path: %s\n", __func__, settings_path);
return settings_path;
}
const char *xemu_settings_get_default_eeprom_path(void)
{
static char *eeprom_path = NULL;
if (eeprom_path != NULL) {
return eeprom_path;
}
char *base = SDL_GetPrefPath("xemu", "xemu");
assert(base != NULL);
size_t base_len = strlen(base);
const char *name = "eeprom.bin";
size_t name_len = strlen(name);
size_t final_len = base_len + name_len;
final_len += 1; // Terminating null byte
char *path = malloc(final_len);
assert(path != NULL);
// Copy base part
memcpy(path, base, base_len);
free(base);
// Copy name part
memcpy(path+base_len, name, name_len);
path[final_len-1] = '\0';
eeprom_path = path;
return eeprom_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;
}
}
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;
}