Mercurial > repos > blastem
view config.c @ 2316:523ab225815b
Allow dpad to increment/decrement property fields
author | Michael Pavone <pavone@retrodev.com> |
---|---|
date | Sun, 02 Apr 2023 23:21:04 -0700 |
parents | 62f316b76e9a |
children | af3075c1e421 |
line wrap: on
line source
/* Copyright 2013 Michael Pavone This file is part of BlastEm. BlastEm is free software distributed under the terms of the GNU General Public License version 3 or greater. See COPYING for full license text. */ #include "tern.h" #include "util.h" #include "paths.h" #include "config.h" #include <stdio.h> #include <stdlib.h> #include <string.h> #ifdef __MINGW64_VERSION_MAJOR #define MINGW_W64_VERSION (__MINGW64_VERSION_MAJOR * 1000 + __MINGW64_VERSION_MINOR) #else #define MINGW_W64_VERSION 0 #endif #if defined(_WIN32) && (MINGW_W64_VERSION < 3003) char * strtok_r(char * input, char * sep, char ** state) { if (input) { *state = input; } char * ret = *state; while (**state && **state != *sep) { ++*state; } if (**state) { **state = 0; ++*state; return ret; } return NULL; } #endif static tern_node * parse_config_int(char **state, int started, int *line) { char *config_data, *curline; tern_node * head = NULL; config_data = started ? NULL : *state; while ((curline = strtok_r(config_data, "\n", state))) { config_data = NULL; curline = strip_ws(curline); int len = strlen(curline); if (!len) { (*line)++; continue; } if (curline[0] == '#') { (*line)++; continue; } if (curline[0] == '}') { if (started) { return head; } fatal_error("unexpected } on line %d\n", *line); } char * end = curline + len - 1; if (*end == '{') { *end = 0; curline = strip_ws(curline); (*line)++; head = tern_insert_node(head, curline, parse_config_int(state, 1, line)); } else { char * val = strip_ws(split_keyval(curline)); char * key = curline; if (*val) { head = tern_insert_ptr(head, key, strdup(val)); } else { fprintf(stderr, "Key %s is missing a value on line %d\n", key, *line); } (*line)++; } } return head; } tern_node *parse_config(char * config_data) { int line = 1; return parse_config_int(&config_data, 0, &line); } typedef struct { char *buf; uint32_t capacity; uint32_t size; uint32_t indent; } serialize_state; static void ensure_buf_capacity(uint32_t ensure, serialize_state *state) { if (ensure + state->size > state->capacity) { state->capacity = state->capacity * 2; state->buf = realloc(state->buf, state->capacity); } } static void indent(serialize_state *state) { memset(state->buf + state->size, '\t', state->indent); state->size += state->indent; } static void serialize_config_int(tern_node *config, serialize_state *state); static void serialize_iter(char *key, tern_val val, uint8_t valtype, void *data) { serialize_state *state = data; uint32_t keylen = strlen(key); uint32_t vallen = 0; if (valtype == TVAL_PTR) { vallen = strlen(val.ptrval); } ensure_buf_capacity(state->indent + keylen + 2 + vallen, state); state->buf[state->size++] = '\n'; indent(state); memcpy(state->buf + state->size, key, keylen); state->size += keylen; state->buf[state->size++] = ' '; if (valtype == TVAL_PTR) { memcpy(state->buf + state->size, val.ptrval, vallen); state->size += vallen; } else { serialize_config_int(val.ptrval, state); } } static void serialize_config_int(tern_node *config, serialize_state *state) { ensure_buf_capacity(1, state); state->buf[state->size++] = '{'; state->indent++; tern_foreach(config, serialize_iter, state); --state->indent; ensure_buf_capacity(2 + state->indent, state); state->buf[state->size++] = '\n'; indent(state); state->buf[state->size++] = '}'; } char *serialize_config(tern_node *config, uint32_t *size_out) { serialize_state state = { .size = 0, .capacity = 1024, .indent = 0 }; state.buf = malloc(state.capacity); tern_foreach(config, serialize_iter, &state); //serialize_config_int(config, &state); *size_out = state.size; return state.buf; } tern_node *parse_config_file(char *config_path) { tern_node * ret = NULL; FILE * config_file = fopen(config_path, "rb"); if (!config_file) { goto open_fail; } long config_size = file_size(config_file); if (!config_size) { goto config_empty; } char *config_data = calloc(config_size + 1, 1); if (fread(config_data, 1, config_size, config_file) != config_size) { goto config_read_fail; } ret = parse_config(config_data); config_read_fail: free(config_data); config_empty: fclose(config_file); open_fail: return ret; } uint8_t serialize_config_file(tern_node *config, char *path) { FILE *f = fopen(path, "w"); if (!f) { return 0; } uint32_t buf_size; char *buffer = serialize_config(config, &buf_size); uint8_t ret = buf_size == fwrite(buffer, 1, buf_size, f); free(buffer); fclose(f); return ret; } tern_node *parse_bundled_config(char *config_name) { tern_node *ret = NULL; #ifdef CONFIG_PATH if (!strcmp("default.cfg", config_name) || !strcmp("blastem.cfg", config_name)) { char *confpath = path_append(CONFIG_PATH, config_name); ret = parse_config_file(confpath); free(confpath); } else { #endif uint32_t confsize; char *confdata = read_bundled_file(config_name, &confsize); if (confdata) { confdata[confsize] = 0; ret = parse_config(confdata); free(confdata); } #ifdef CONFIG_PATH } #endif return ret; } tern_node *load_overrideable_config(char *name, char *bundled_name, uint8_t *used_config_dir) { char const *confdir = get_config_dir(); char *confpath = NULL; tern_node *ret; if (confdir) { confpath = path_append(confdir, name); ret = parse_config_file(confpath); } free(confpath); if (used_config_dir) { *used_config_dir = ret != NULL; } if (!ret) { ret = parse_bundled_config(name); if (!ret) { ret = parse_bundled_config(bundled_name); } } return ret; } static tern_node *dupe_tree(tern_node *head) { if (!head) { return head; } tern_node *out = calloc(1, sizeof(tern_node)); out->left = dupe_tree(head->left); out->right = dupe_tree(head->right); out->el = head->el; out->valtype = head->valtype; if (out->el) { out->straight.next = dupe_tree(head->straight.next); } else if (out->valtype == TVAL_NODE) { out->straight.value.ptrval = dupe_tree(head->straight.value.ptrval); } else if (out->valtype == TVAL_PTR) { out->straight.value.ptrval = strdup(head->straight.value.ptrval); } else { out->straight.value = head->straight.value; } return out; } static void migrate_pads(char *key, tern_val val, uint8_t valtype, void *data) { tern_node **pads = data; if (valtype != TVAL_NODE) { return; } tern_node *existing = tern_find_node(*pads, key); if (existing) { return; } *pads = tern_insert_node(*pads, key, dupe_tree(val.ptrval)); } static void update_menu_binding(char *key, tern_val val, uint8_t valtype, void *data) { tern_node **key_bindings = data; if (valtype != TVAL_PTR) { return; } if (strcmp(val.ptrval, "ui.exit")) { return; } *key_bindings = tern_insert_ptr(*key_bindings, key, strdup("ui.menu")); } static void update_pad_menu_binding(char *key, tern_val val, uint8_t valtype, void *data) { tern_node **pads = data; if (valtype != TVAL_NODE) { return; } tern_node *buttons = tern_find_node(val.ptrval, "buttons"); if (buttons) { tern_foreach(buttons, update_menu_binding, &buttons); val.ptrval = tern_insert_node(val.ptrval, "buttons", buttons); } tern_node *axes = tern_find_node(val.ptrval, "axes"); if (axes) { tern_foreach(axes, update_menu_binding, &axes); val.ptrval = tern_insert_node(val.ptrval, "axes", axes); } *pads = tern_insert_node(*pads, key, val.ptrval); } #define CONFIG_VERSION 7 static tern_node *migrate_config(tern_node *config, int from_version) { tern_node *def_config = parse_bundled_config("default.cfg"); switch(from_version) { case 0: { //Add CD image formats to ui.extensions uint32_t num_exts; char **ext_list = get_extension_list(config, &num_exts); char *old = num_exts ? ext_list[0] : NULL; uint32_t new_size = num_exts + 2; uint8_t need_cue = 1, need_iso = 1; for (uint32_t i = 0; i < num_exts; i++) { if (!strcmp(ext_list[i], "cue")) { need_cue = 0; new_size--; } else if (!strcmp(ext_list[i], "iso")) { need_iso = 0; new_size--; } } if (new_size != num_exts) { ext_list = realloc(ext_list, sizeof(char*) * new_size); if (need_cue) { ext_list[num_exts++] = "cue"; } if (need_iso) { ext_list[num_exts++] = "iso"; } } char *combined = alloc_join(new_size, (char const **)ext_list, ' '); config = tern_insert_path(config, "ui\0extensions\0", (tern_val){.ptrval = combined}, TVAL_PTR); //Copy default pad configs if missing tern_node *pads = tern_find_path(config, "bindings\0pads\0", TVAL_NODE).ptrval; tern_node *def_pads = tern_find_path(def_config, "bindings\0pads\0", TVAL_NODE).ptrval; tern_foreach(def_pads, migrate_pads, &pads); config = tern_insert_path(config, "bindings\0pads\0", (tern_val){.ptrval = pads}, TVAL_NODE); } case 1: { char *l_bind = tern_find_path(config, "bindings\0keys\0l\0", TVAL_PTR).ptrval; if (!l_bind) { config = tern_insert_path(config, "bindings\0keys\0l\0", (tern_val){.ptrval = strdup("ui.load_state")}, TVAL_PTR); } } case 2: { tern_node *sms = tern_find_node(config, "sms"); char *model = tern_find_path_default(sms, "system\0model\0", (tern_val){.ptrval = "md1va3"}, TVAL_PTR).ptrval; char *io1 = tern_find_path_default(sms, "io\0devices\0""1\0", (tern_val){.ptrval = "gamepad2.1"}, TVAL_PTR).ptrval; char *io2 = tern_find_path_default(sms, "io\0devices\0""1\0", (tern_val){.ptrval = "gamepad2.2"}, TVAL_PTR).ptrval; sms = tern_insert_path(sms, "system\0model\0", (tern_val){.ptrval = strdup(model)}, TVAL_PTR); sms = tern_insert_path(sms, "io\0devices\0""1\0", (tern_val){.ptrval = strdup(io1)}, TVAL_PTR); sms = tern_insert_path(sms, "io\0devices\0""2\0", (tern_val){.ptrval = strdup(io2)}, TVAL_PTR); config = tern_insert_node(config, "sms", sms); } case 3: { char *tap11 = tern_find_path_default(config, "io\0sega_multitap.1\0""1\0", (tern_val){.ptrval = "gamepad6.2"}, TVAL_PTR).ptrval; char *tap12 = tern_find_path_default(config, "io\0sega_multitap.1\0""2\0", (tern_val){.ptrval = "gamepad6.3"}, TVAL_PTR).ptrval; char *tap13 = tern_find_path_default(config, "io\0sega_multitap.1\0""3\0", (tern_val){.ptrval = "gamepad6.4"}, TVAL_PTR).ptrval; char *tap14 = tern_find_path_default(config, "io\0sega_multitap.1\0""4\0", (tern_val){.ptrval = "gamepad6.5"}, TVAL_PTR).ptrval; config = tern_insert_path(config, "io\0sega_multitap.1\0""1\0", (tern_val){.ptrval = strdup(tap11)}, TVAL_PTR); config = tern_insert_path(config, "io\0sega_multitap.1\0""2\0", (tern_val){.ptrval = strdup(tap12)}, TVAL_PTR); config = tern_insert_path(config, "io\0sega_multitap.1\0""3\0", (tern_val){.ptrval = strdup(tap13)}, TVAL_PTR); config = tern_insert_path(config, "io\0sega_multitap.1\0""4\0", (tern_val){.ptrval = strdup(tap14)}, TVAL_PTR); } case 4: { char *tap11 = tern_find_path_default(config, "io\0ea_multitap\0""1\0", (tern_val){.ptrval = "gamepad6.1"}, TVAL_PTR).ptrval; char *tap12 = tern_find_path_default(config, "io\0ea_multitap\0""2\0", (tern_val){.ptrval = "gamepad6.2"}, TVAL_PTR).ptrval; char *tap13 = tern_find_path_default(config, "io\0ea_multitap\0""3\0", (tern_val){.ptrval = "gamepad6.3"}, TVAL_PTR).ptrval; char *tap14 = tern_find_path_default(config, "io\0ea_multitap\0""4\0", (tern_val){.ptrval = "gamepad6.4"}, TVAL_PTR).ptrval; config = tern_insert_path(config, "io\0ea_multitap\0""1\0", (tern_val){.ptrval = strdup(tap11)}, TVAL_PTR); config = tern_insert_path(config, "io\0ea_multitap\0""2\0", (tern_val){.ptrval = strdup(tap12)}, TVAL_PTR); config = tern_insert_path(config, "io\0ea_multitap\0""3\0", (tern_val){.ptrval = strdup(tap13)}, TVAL_PTR); config = tern_insert_path(config, "io\0ea_multitap\0""4\0", (tern_val){.ptrval = strdup(tap14)}, TVAL_PTR); } case 5: { char *binding_o = tern_find_path_default(config, "bindings\0keys\0o\0", (tern_val){.ptrval = "ui.oscilloscope"}, TVAL_PTR).ptrval; config = tern_insert_path(config, "bindings\0keys\0o\0", (tern_val){.ptrval = strdup(binding_o)}, TVAL_PTR); } case 6: { tern_node *key_bindings = tern_find_path(config, "bindings\0keys\0", TVAL_NODE).ptrval; if (key_bindings) { tern_foreach(key_bindings, update_menu_binding, &key_bindings); config = tern_insert_path(config, "bindings\0keys\0", (tern_val){.ptrval = key_bindings}, TVAL_NODE); } tern_node *pad_bindings = tern_find_path(config, "bindings\0pads\0", TVAL_NODE).ptrval; if (pad_bindings) { tern_foreach(pad_bindings, update_pad_menu_binding, &pad_bindings); config = tern_insert_path(config, "bindings\0pads\0", (tern_val){.ptrval = pad_bindings}, TVAL_NODE); } } } char buffer[16]; sprintf(buffer, "%d", CONFIG_VERSION); return tern_insert_ptr(config, "version", strdup(buffer)); } static uint8_t app_config_in_config_dir; tern_node *load_config() { tern_node *ret = load_overrideable_config("blastem.cfg", "default.cfg", &app_config_in_config_dir); if (!ret) { if (get_config_dir()) { fatal_error("Failed to find a config file at %s or in the blastem executable directory\n", get_config_dir()); } else { fatal_error("Failed to find a config file in the BlastEm executable directory and the config directory path could not be determined\n"); } } int config_version = atoi(tern_find_ptr_default(ret, "version", "0")); if (config_version < CONFIG_VERSION) { migrate_config(ret, config_version); } return ret; } void persist_config_at(tern_node *app_config, tern_node *to_save, char *fname) { char*use_exe_dir = tern_find_path_default(app_config, "ui\0config_in_exe_dir\0", (tern_val){.ptrval = "off"}, TVAL_PTR).ptrval; char *confpath; if (!strcmp(use_exe_dir, "on")) { confpath = path_append(get_exe_dir(), fname); if (app_config == to_save && app_config_in_config_dir) { //user switched to "portable" configs this session and there is an //existing config file in the user-specific config directory //delete it so we don't end up loading it next time char *oldpath = path_append(get_config_dir(), fname); delete_file(oldpath); free(oldpath); } } else { char const *confdir = get_config_dir(); if (!confdir) { fatal_error("Failed to locate config file directory\n"); } ensure_dir_exists(confdir); confpath = path_append(confdir, fname); } if (!serialize_config_file(to_save, confpath)) { fatal_error("Failed to write config to %s\n", confpath); } free(confpath); } void persist_config(tern_node *config) { persist_config_at(config, config, "blastem.cfg"); } void delete_custom_config_at(char *fname) { char *confpath = path_append(get_exe_dir(), fname); delete_file(confpath); free(confpath); confpath = path_append(get_config_dir(), fname); delete_file(confpath); free(confpath); } void delete_custom_config(void) { delete_custom_config_at("blastem.cfg"); } char **get_extension_list(tern_node *config, uint32_t *num_exts_out) { char *ext_filter = strdup(tern_find_path_default(config, "ui\0extensions\0", (tern_val){.ptrval = "bin gen md smd sms gg cue iso"}, TVAL_PTR).ptrval); uint32_t num_exts = 0, ext_storage = 5; char **ext_list = malloc(sizeof(char *) * ext_storage); char *cur_filter = ext_filter; while (*cur_filter) { if (num_exts == ext_storage) { ext_storage *= 2; ext_list = realloc(ext_list, sizeof(char *) * ext_storage); } ext_list[num_exts++] = cur_filter; cur_filter = split_keyval(cur_filter); } *num_exts_out = num_exts; return ext_list; } #define DEFAULT_LOWPASS_CUTOFF 3390 uint32_t get_lowpass_cutoff(tern_node *config) { char * lowpass_cutoff_str = tern_find_path(config, "audio\0lowpass_cutoff\0", TVAL_PTR).ptrval; return lowpass_cutoff_str ? atoi(lowpass_cutoff_str) : DEFAULT_LOWPASS_CUTOFF; } tern_node *get_systems_config(void) { static tern_node *systems; if (!systems) { systems = parse_bundled_config("systems.cfg"); } return systems; } tern_node *get_model(tern_node *config, system_type stype) { char *model = tern_find_path_default(config, stype == SYSTEM_SMS ? "sms\0system\0model\0" : "system\0model\0", (tern_val){.ptrval = "md1va3"}, TVAL_PTR).ptrval; return tern_find_node(get_systems_config(), model); }