fda8556a39
This fixes a nasty bug that manifests on windows when building without precompiled headers. util/stringops.h used to silently replace strdup with a macro that's compatible with mem_free(). This header would typically be included everywhere due to PCH, but without it the strdup from libc would sometimes be in scope. On most platforms mem_free() is equivalent to free(), but not on windows, because we have to use _aligned_free() there. Attempting to mem_free() the result of a libc strdup() would segfault in such a configuration. Avoid the footgun by banning strdup() entirely. Maybe redefining libc names isn't such a great idea, who knew?
214 lines
4.4 KiB
C
214 lines
4.4 KiB
C
/*
|
|
* This software is licensed under the terms of the MIT License.
|
|
* See COPYING for further information.
|
|
* ---
|
|
* Copyright (c) 2011-2024, Lukas Weber <laochailan@web.de>.
|
|
* Copyright (c) 2012-2024, Andrei Alexeyev <akari@taisei-project.org>.
|
|
*/
|
|
|
|
#include "kvparser.h"
|
|
|
|
#include "io.h"
|
|
#include "log.h"
|
|
#include "stringops.h"
|
|
#include "vfs/public.h"
|
|
|
|
bool parse_keyvalue_stream_cb(SDL_RWops *strm, KVCallback callback, void *data) {
|
|
static const char separator[] = "= ";
|
|
|
|
size_t bufsize = 256;
|
|
char *buffer = mem_alloc(bufsize);
|
|
int lineno = 0;
|
|
int errors = 0;
|
|
|
|
loopstart: while(SDL_RWgets_realloc(strm, &buffer, &bufsize)) {
|
|
char *ptr = buffer;
|
|
char *sep, *key, *val;
|
|
|
|
++lineno;
|
|
|
|
while(isspace(*ptr)) {
|
|
if(!*(++ptr)) {
|
|
// blank line
|
|
goto loopstart;
|
|
}
|
|
}
|
|
|
|
if(*ptr == '#') {
|
|
// comment
|
|
continue;
|
|
}
|
|
|
|
sep = strstr(ptr, separator);
|
|
|
|
if(!sep) {
|
|
++errors;
|
|
log_warn("Syntax error on line %i: missing separator", lineno);
|
|
continue;
|
|
}
|
|
|
|
// split it up
|
|
*sep = 0;
|
|
key = ptr;
|
|
val = sep + sizeof(separator) - 1;
|
|
|
|
// the separator may be preceeded by any kind of whitespace, so strip it from the key
|
|
while(isspace(*(ptr = strchr(key, 0) - 1))) {
|
|
*ptr = 0;
|
|
}
|
|
|
|
// strip any kind of line endings from the value
|
|
while(strchr("\r\n", *(ptr = strchr(val, 0) - 1))) {
|
|
*ptr = 0;
|
|
}
|
|
|
|
if(!callback(key, val, data)) {
|
|
++errors;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
mem_free(buffer);
|
|
return !errors;
|
|
}
|
|
|
|
bool parse_keyvalue_file_cb(const char *filename, KVCallback callback, void *data) {
|
|
SDL_RWops *strm = vfs_open(filename, VFS_MODE_READ);
|
|
|
|
if(!strm) {
|
|
log_error("VFS error: %s", vfs_get_error());
|
|
return false;
|
|
}
|
|
|
|
bool status = parse_keyvalue_stream_cb(strm, callback, data);
|
|
SDL_RWclose(strm);
|
|
return status;
|
|
}
|
|
|
|
static bool kvcallback_hashtable(const char *key, const char *val, void *data) {
|
|
ht_str2ptr_t *ht = data;
|
|
ht_set(ht, key, mem_strdup(val));
|
|
return true;
|
|
}
|
|
|
|
bool parse_keyvalue_stream(SDL_RWops *strm, ht_str2ptr_t *ht) {
|
|
return parse_keyvalue_stream_cb(strm, kvcallback_hashtable, ht);
|
|
}
|
|
|
|
bool parse_keyvalue_file(const char *filename, ht_str2ptr_t *ht) {
|
|
return parse_keyvalue_file_cb(filename, kvcallback_hashtable, ht);
|
|
}
|
|
|
|
static bool kvcallback_spec(const char *key, const char *val, void *data) {
|
|
KVSpec *spec = data;
|
|
|
|
for(KVSpec *s = spec; s->name; ++s) {
|
|
if(!strcmp(key, s->name)) {
|
|
if(s->out_str) {
|
|
stralloc(s->out_str, val);
|
|
}
|
|
|
|
if(s->out_int) {
|
|
*s->out_int = strtol(val, NULL, 0);
|
|
}
|
|
|
|
if(s->out_long) {
|
|
*s->out_long = strtol(val, NULL, 0);
|
|
}
|
|
|
|
if(s->out_float) {
|
|
*s->out_float = strtod(val, NULL);
|
|
}
|
|
|
|
if(s->out_double) {
|
|
*s->out_double = strtod(val, NULL);
|
|
}
|
|
|
|
if(s->out_bool) {
|
|
*s->out_bool = parse_bool(val, *s->out_bool);
|
|
}
|
|
|
|
if(s->callback) {
|
|
return s->callback(key, val, s->callback_data);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
log_warn("Unknown key '%s' with value '%s' ignored", key, val);
|
|
return true;
|
|
}
|
|
|
|
bool parse_keyvalue_stream_with_spec(SDL_RWops *strm, KVSpec *spec) {
|
|
return parse_keyvalue_stream_cb(strm, kvcallback_spec, spec);
|
|
}
|
|
|
|
bool parse_keyvalue_file_with_spec(const char *filename, KVSpec *spec) {
|
|
return parse_keyvalue_file_cb(filename, kvcallback_spec, spec);
|
|
}
|
|
|
|
bool parse_bool(const char *str, bool fallback) {
|
|
while(isspace(*str)) {
|
|
++str;
|
|
}
|
|
|
|
char buf[strlen(str) + 1], *bufp = buf;
|
|
|
|
while(*str && !isspace(*str)) {
|
|
*bufp++ = *str++;
|
|
}
|
|
|
|
*bufp = 0;
|
|
double numeric_val = strtod(buf, &bufp);
|
|
|
|
if(*buf && !*bufp) {
|
|
return (bool)numeric_val;
|
|
}
|
|
|
|
static const char *true_vals[] = { "on", "yes", "true", NULL };
|
|
static const char *false_vals[] = { "off", "no", "false", NULL };
|
|
|
|
for(const char **v = true_vals; *v; ++v) {
|
|
if(!SDL_strcasecmp(buf, *v)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
for(const char **v = false_vals; *v; ++v) {
|
|
if(!SDL_strcasecmp(buf, *v)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
log_warn("Bad value `%s`, assuming %s", buf, fallback ? "true" : "false");
|
|
return fallback;
|
|
}
|
|
|
|
bool kvparser_deprecation(const char *key, const char *val, void *data) {
|
|
const char *alt = data;
|
|
log_warn("'%s' is deprecated, use '%s' instead", key, alt);
|
|
return true;
|
|
}
|
|
|
|
bool kvparser_vec3(const char *key, const char *val, void *data) {
|
|
float *out = data;
|
|
char *p = (char*)val;
|
|
|
|
out[0] = strtof(p, &p);
|
|
if(!isspace(*p)) goto fail;
|
|
out[1] = strtof(p, &p);
|
|
if(!isspace(*p)) goto fail;
|
|
out[2] = strtof(p, &p);
|
|
|
|
while(isspace(*p))
|
|
++p;
|
|
|
|
if(*p == 0) {
|
|
return true;
|
|
}
|
|
|
|
fail:
|
|
log_error("Invalid vec3 value for '%s': %s", key, val);
|
|
return false;
|
|
}
|