main
config.c
1/*
2 Copyright 2013 Michael Pavone
3 This file is part of BlastEm.
4 BlastEm is free software distributed under the terms of the GNU General Public License version 3 or greater. See COPYING for full license text.
5*/
6#include "tern.h"
7#include "util.h"
8#include "paths.h"
9#include <stdio.h>
10#include <stdlib.h>
11#include <string.h>
12
13static tern_node * parse_config_int(char **state, int started, int *line)
14{
15 char *config_data, *curline;
16 tern_node * head = NULL;
17 config_data = started ? NULL : *state;
18 while ((curline = strtok_r(config_data, "\n", state)))
19 {
20
21 config_data = NULL;
22 curline = strip_ws(curline);
23 int len = strlen(curline);
24 if (!len) {
25 (*line)++;
26 continue;
27 }
28 if (curline[0] == '#') {
29 (*line)++;
30 continue;
31 }
32 if (curline[0] == '}') {
33 if (started) {
34 return head;
35 }
36 fatal_error("unexpected } on line %d\n", *line);
37 }
38
39 char * end = curline + len - 1;
40 if (*end == '{') {
41 *end = 0;
42 curline = strip_ws(curline);
43 (*line)++;
44 head = tern_insert_node(head, curline, parse_config_int(state, 1, line));
45 } else {
46 char * val = strip_ws(split_keyval(curline));
47 char * key = curline;
48 if (*val) {
49 head = tern_insert_ptr(head, key, strdup(val));
50 } else {
51 fprintf(stderr, "Key %s is missing a value on line %d\n", key, *line);
52 }
53 (*line)++;
54 }
55 }
56 return head;
57}
58
59tern_node *parse_config(char * config_data)
60{
61 int line = 1;
62 return parse_config_int(&config_data, 0, &line);
63}
64
65typedef struct {
66 char *buf;
67 uint32_t capacity;
68 uint32_t size;
69 uint32_t indent;
70} serialize_state;
71
72static void ensure_buf_capacity(uint32_t ensure, serialize_state *state)
73{
74 if (ensure + state->size > state->capacity) {
75 state->capacity = state->capacity * 2;
76 state->buf = realloc(state->buf, state->capacity);
77 }
78}
79
80static void indent(serialize_state *state)
81{
82 memset(state->buf + state->size, '\t', state->indent);
83 state->size += state->indent;
84}
85
86static void serialize_config_int(tern_node *config, serialize_state *state);
87
88static void serialize_iter(char *key, tern_val val, uint8_t valtype, void *data)
89{
90 serialize_state *state = data;
91 uint32_t keylen = strlen(key);
92 uint32_t vallen = 0;
93 if (valtype == TVAL_PTR) {
94 vallen = strlen(val.ptrval);
95 }
96 ensure_buf_capacity(state->indent + keylen + 2 + vallen, state);
97 state->buf[state->size++] = '\n';
98 indent(state);
99 memcpy(state->buf + state->size, key, keylen);
100 state->size += keylen;
101 state->buf[state->size++] = ' ';
102 if (valtype == TVAL_PTR) {
103 memcpy(state->buf + state->size, val.ptrval, vallen);
104 state->size += vallen;
105 } else {
106 serialize_config_int(val.ptrval, state);
107 }
108}
109
110static void serialize_config_int(tern_node *config, serialize_state *state)
111{
112 ensure_buf_capacity(1, state);
113 state->buf[state->size++] = '{';
114 state->indent++;
115
116 tern_foreach(config, serialize_iter, state);
117
118 --state->indent;
119 ensure_buf_capacity(2 + state->indent, state);
120 state->buf[state->size++] = '\n';
121 indent(state);
122 state->buf[state->size++] = '}';
123}
124
125char *serialize_config(tern_node *config, uint32_t *size_out)
126{
127 serialize_state state = {
128 .size = 0,
129 .capacity = 1024,
130 .indent = 0
131 };
132 state.buf = malloc(state.capacity);
133 tern_foreach(config, serialize_iter, &state);
134 //serialize_config_int(config, &state);
135 *size_out = state.size;
136 return state.buf;
137}
138
139tern_node *parse_config_file(char *config_path)
140{
141 tern_node * ret = NULL;
142 FILE * config_file = fopen(config_path, "rb");
143 if (!config_file) {
144 goto open_fail;
145 }
146 long config_size = file_size(config_file);
147 if (!config_size) {
148 goto config_empty;
149 }
150 char *config_data = calloc(config_size + 1, 1);
151 if (fread(config_data, 1, config_size, config_file) != config_size) {
152 goto config_read_fail;
153 }
154
155 ret = parse_config(config_data);
156config_read_fail:
157 free(config_data);
158config_empty:
159 fclose(config_file);
160open_fail:
161 return ret;
162}
163
164uint8_t serialize_config_file(tern_node *config, char *path)
165{
166 FILE *f = fopen(path, "w");
167 if (!f) {
168 return 0;
169 }
170 uint32_t buf_size;
171 char *buffer = serialize_config(config, &buf_size);
172 uint8_t ret = buf_size == fwrite(buffer, 1, buf_size, f);
173 free(buffer);
174 fclose(f);
175 return ret;
176}
177
178tern_node *parse_bundled_config(char *config_name)
179{
180 tern_node *ret = NULL;
181 if (!strcmp("default.cfg", config_name) || !strcmp("blastem.cfg", config_name)) {
182 char const *confdir = get_config_dir();
183 if (confdir) {
184 char *confpath = path_append(confdir, config_name);
185 ret = parse_config_file(confpath);
186 free(confpath);
187 if (ret) {
188 return ret;
189 }
190 }
191 }
192 uint32_t confsize;
193 char *confdata = read_bundled_file(config_name, &confsize);
194 if (confdata) {
195 confdata[confsize] = 0;
196 ret = parse_config(confdata);
197 free(confdata);
198 }
199 return ret;
200}
201
202tern_node *load_overrideable_config(char *name, char *bundled_name)
203{
204 char const *confdir = get_config_dir();
205 char *confpath = NULL;
206 tern_node *ret;
207 if (confdir) {
208 confpath = path_append(confdir, name);
209 ret = parse_config_file(confpath);
210 if (ret) {
211 free(confpath);
212 return ret;
213 }
214 }
215
216 ret = parse_bundled_config(bundled_name);
217 if (ret) {
218 free(confpath);
219 return ret;
220 }
221 return NULL;
222}
223
224tern_node *load_config()
225{
226 tern_node *ret = load_overrideable_config("blastem.cfg", "default.cfg");
227 if (ret) {
228 return ret;
229 }
230
231 if (get_config_dir()) {
232 fatal_error("Failed to find a config file at %s or a bundled default.cfg in the BlastEm data directory\n", get_config_dir());
233 } else {
234 fatal_error("Failed to find a bundled default.cfg in the BlastEm data directory and the config directory path could not be determined\n");
235 }
236 //this will never get reached, but the compiler doesn't know that. Let's make it happy
237 return NULL;
238}
239
240void persist_config_at(tern_node *config, char *fname)
241{
242 char const *confdir = get_config_dir();
243 if (!confdir) {
244 fatal_error("Failed to locate config file directory\n");
245 }
246 ensure_dir_exists(confdir);
247 char *confpath = path_append(confdir, fname);
248 if (!serialize_config_file(config, confpath)) {
249 fatal_error("Failed to write config to %s\n", confpath);
250 }
251 free(confpath);
252}
253
254void persist_config(tern_node *config)
255{
256 persist_config_at(config, "blastem.cfg");
257}
258
259char **get_extension_list(tern_node *config, uint32_t *num_exts_out)
260{
261 char *ext_filter = strdup(tern_find_path_default(config, "ui\0extensions\0", (tern_val){.ptrval = "bin gen md smd sms gg"}, TVAL_PTR).ptrval);
262 uint32_t num_exts = 0, ext_storage = 5;
263 char **ext_list = malloc(sizeof(char *) * ext_storage);
264 char *cur_filter = ext_filter;
265 while (*cur_filter)
266 {
267 if (num_exts == ext_storage) {
268 ext_storage *= 2;
269 ext_list = realloc(ext_list, sizeof(char *) * ext_storage);
270 }
271 ext_list[num_exts++] = cur_filter;
272 cur_filter = split_keyval(cur_filter);
273 }
274 *num_exts_out = num_exts;
275 return ext_list;
276}
277
278#define DEFAULT_LOWPASS_CUTOFF 3390
279uint32_t get_lowpass_cutoff(tern_node *config)
280{
281 char * lowpass_cutoff_str = tern_find_path(config, "audio\0lowpass_cutoff\0", TVAL_PTR).ptrval;
282 return lowpass_cutoff_str ? atoi(lowpass_cutoff_str) : DEFAULT_LOWPASS_CUTOFF;
283}