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}