2020-05-07 20:21:22 +02:00
|
|
|
// configfile.c - handles loading and saving the configuration options
|
|
|
|
#include <stdbool.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <assert.h>
|
|
|
|
#include <ctype.h>
|
2020-05-17 21:17:47 +02:00
|
|
|
#include <SDL2/SDL.h>
|
2020-05-07 20:21:22 +02:00
|
|
|
|
|
|
|
#include "configfile.h"
|
2020-05-17 21:17:47 +02:00
|
|
|
#include "gfx/gfx_screen_config.h"
|
2020-05-15 20:38:35 +02:00
|
|
|
#include "controller/controller_api.h"
|
2020-05-07 20:21:22 +02:00
|
|
|
|
|
|
|
#define ARRAY_LEN(arr) (sizeof(arr) / sizeof(arr[0]))
|
|
|
|
|
|
|
|
enum ConfigOptionType {
|
|
|
|
CONFIG_TYPE_BOOL,
|
|
|
|
CONFIG_TYPE_UINT,
|
|
|
|
CONFIG_TYPE_FLOAT,
|
2020-05-15 20:38:35 +02:00
|
|
|
CONFIG_TYPE_BIND,
|
2020-05-07 20:21:22 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
struct ConfigOption {
|
|
|
|
const char *name;
|
|
|
|
enum ConfigOptionType type;
|
|
|
|
union {
|
|
|
|
bool *boolValue;
|
|
|
|
unsigned int *uintValue;
|
|
|
|
float *floatValue;
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
|
|
*Config options and default values
|
|
|
|
*/
|
2020-05-15 20:38:35 +02:00
|
|
|
|
2020-05-16 15:09:27 +02:00
|
|
|
// Video/audio stuff
|
|
|
|
bool configFullscreen = false;
|
2020-05-17 21:17:47 +02:00
|
|
|
ConfigWindow configWindow = {
|
|
|
|
.x = SDL_WINDOWPOS_CENTERED,
|
|
|
|
.y = SDL_WINDOWPOS_CENTERED,
|
|
|
|
.w = DESIRED_SCREEN_WIDTH,
|
|
|
|
.h = DESIRED_SCREEN_HEIGHT,
|
2020-05-18 04:08:12 +02:00
|
|
|
.reset = false,
|
|
|
|
.vsync = false,
|
|
|
|
.exiting_fullscreen = false,
|
2020-05-17 21:17:47 +02:00
|
|
|
};
|
2020-05-16 15:09:27 +02:00
|
|
|
unsigned int configFiltering = 1; // 0=force nearest, 1=linear, (TODO) 2=three-point
|
|
|
|
unsigned int configMasterVolume = MAX_VOLUME; // 0 - MAX_VOLUME
|
|
|
|
|
2020-05-15 20:38:35 +02:00
|
|
|
// Keyboard mappings (VK_ values, by default keyboard/gamepad/mouse)
|
|
|
|
unsigned int configKeyA[MAX_BINDS] = { 0x0026, 0x1000, 0x1103 };
|
|
|
|
unsigned int configKeyB[MAX_BINDS] = { 0x0033, 0x1002, 0x1101 };
|
|
|
|
unsigned int configKeyStart[MAX_BINDS] = { 0x0039, 0x1006, VK_INVALID };
|
|
|
|
unsigned int configKeyL[MAX_BINDS] = { 0x0034, 0x1007, 0x1104 };
|
|
|
|
unsigned int configKeyR[MAX_BINDS] = { 0x0036, 0x100A, 0x1105 };
|
|
|
|
unsigned int configKeyZ[MAX_BINDS] = { 0x0025, 0x1009, 0x1102 };
|
|
|
|
unsigned int configKeyCUp[MAX_BINDS] = { 0x0148, VK_INVALID, VK_INVALID };
|
|
|
|
unsigned int configKeyCDown[MAX_BINDS] = { 0x0150, VK_INVALID, VK_INVALID };
|
|
|
|
unsigned int configKeyCLeft[MAX_BINDS] = { 0x014B, VK_INVALID, VK_INVALID };
|
|
|
|
unsigned int configKeyCRight[MAX_BINDS] = { 0x014D, VK_INVALID, VK_INVALID };
|
|
|
|
unsigned int configKeyStickUp[MAX_BINDS] = { 0x0011, VK_INVALID, VK_INVALID };
|
|
|
|
unsigned int configKeyStickDown[MAX_BINDS] = { 0x001F, VK_INVALID, VK_INVALID };
|
|
|
|
unsigned int configKeyStickLeft[MAX_BINDS] = { 0x001E, VK_INVALID, VK_INVALID };
|
|
|
|
unsigned int configKeyStickRight[MAX_BINDS] = { 0x0020, VK_INVALID, VK_INVALID };
|
|
|
|
|
2020-05-10 20:39:04 +02:00
|
|
|
#ifdef BETTERCAMERA
|
|
|
|
// BetterCamera settings
|
|
|
|
unsigned int configCameraXSens = 50;
|
|
|
|
unsigned int configCameraYSens = 50;
|
|
|
|
unsigned int configCameraAggr = 0;
|
|
|
|
unsigned int configCameraPan = 0;
|
2020-05-16 19:28:36 +02:00
|
|
|
unsigned int configCameraDegrade = 10; // 0 - 100%
|
2020-05-10 20:39:04 +02:00
|
|
|
bool configCameraInvertX = false;
|
|
|
|
bool configCameraInvertY = false;
|
|
|
|
bool configEnableCamera = false;
|
|
|
|
bool configCameraMouse = false;
|
|
|
|
#endif
|
2020-05-15 21:47:04 +02:00
|
|
|
unsigned int configSkipIntro = 0;
|
2020-05-07 20:21:22 +02:00
|
|
|
|
|
|
|
static const struct ConfigOption options[] = {
|
2020-05-10 20:39:04 +02:00
|
|
|
{.name = "fullscreen", .type = CONFIG_TYPE_BOOL, .boolValue = &configFullscreen},
|
2020-05-17 21:17:47 +02:00
|
|
|
{.name = "window_x", .type = CONFIG_TYPE_UINT, .uintValue = &configWindow.x},
|
|
|
|
{.name = "window_y", .type = CONFIG_TYPE_UINT, .uintValue = &configWindow.y},
|
|
|
|
{.name = "window_w", .type = CONFIG_TYPE_UINT, .uintValue = &configWindow.w},
|
|
|
|
{.name = "window_h", .type = CONFIG_TYPE_UINT, .uintValue = &configWindow.h},
|
2020-05-16 12:56:12 +02:00
|
|
|
{.name = "texture_filtering", .type = CONFIG_TYPE_UINT, .uintValue = &configFiltering},
|
2020-05-16 15:09:27 +02:00
|
|
|
{.name = "master_volume", .type = CONFIG_TYPE_UINT, .uintValue = &configMasterVolume},
|
2020-05-15 20:38:35 +02:00
|
|
|
{.name = "key_a", .type = CONFIG_TYPE_BIND, .uintValue = configKeyA},
|
|
|
|
{.name = "key_b", .type = CONFIG_TYPE_BIND, .uintValue = configKeyB},
|
|
|
|
{.name = "key_start", .type = CONFIG_TYPE_BIND, .uintValue = configKeyStart},
|
|
|
|
{.name = "key_l", .type = CONFIG_TYPE_BIND, .uintValue = configKeyL},
|
|
|
|
{.name = "key_r", .type = CONFIG_TYPE_BIND, .uintValue = configKeyR},
|
|
|
|
{.name = "key_z", .type = CONFIG_TYPE_BIND, .uintValue = configKeyZ},
|
|
|
|
{.name = "key_cup", .type = CONFIG_TYPE_BIND, .uintValue = configKeyCUp},
|
|
|
|
{.name = "key_cdown", .type = CONFIG_TYPE_BIND, .uintValue = configKeyCDown},
|
|
|
|
{.name = "key_cleft", .type = CONFIG_TYPE_BIND, .uintValue = configKeyCLeft},
|
|
|
|
{.name = "key_cright", .type = CONFIG_TYPE_BIND, .uintValue = configKeyCRight},
|
|
|
|
{.name = "key_stickup", .type = CONFIG_TYPE_BIND, .uintValue = configKeyStickUp},
|
|
|
|
{.name = "key_stickdown", .type = CONFIG_TYPE_BIND, .uintValue = configKeyStickDown},
|
|
|
|
{.name = "key_stickleft", .type = CONFIG_TYPE_BIND, .uintValue = configKeyStickLeft},
|
|
|
|
{.name = "key_stickright", .type = CONFIG_TYPE_BIND, .uintValue = configKeyStickRight},
|
2020-05-10 20:39:04 +02:00
|
|
|
#ifdef BETTERCAMERA
|
|
|
|
{.name = "bettercam_enable", .type = CONFIG_TYPE_BOOL, .boolValue = &configEnableCamera},
|
|
|
|
{.name = "bettercam_mouse_look", .type = CONFIG_TYPE_BOOL, .boolValue = &configCameraMouse},
|
|
|
|
{.name = "bettercam_invertx", .type = CONFIG_TYPE_BOOL, .boolValue = &configCameraInvertX},
|
|
|
|
{.name = "bettercam_inverty", .type = CONFIG_TYPE_BOOL, .boolValue = &configCameraInvertY},
|
|
|
|
{.name = "bettercam_xsens", .type = CONFIG_TYPE_UINT, .uintValue = &configCameraXSens},
|
|
|
|
{.name = "bettercam_ysens", .type = CONFIG_TYPE_UINT, .uintValue = &configCameraYSens},
|
|
|
|
{.name = "bettercam_aggression", .type = CONFIG_TYPE_UINT, .uintValue = &configCameraAggr},
|
|
|
|
{.name = "bettercam_pan_level", .type = CONFIG_TYPE_UINT, .uintValue = &configCameraPan},
|
2020-05-16 19:28:36 +02:00
|
|
|
{.name = "bettercam_degrade", .type = CONFIG_TYPE_UINT, .uintValue = &configCameraDegrade},
|
2020-05-10 20:39:04 +02:00
|
|
|
#endif
|
2020-05-15 21:47:04 +02:00
|
|
|
{.name = "skip_intro", .type = CONFIG_TYPE_UINT, .uintValue = &configSkipIntro}, // Add this back!
|
2020-05-07 20:21:22 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
// Reads an entire line from a file (excluding the newline character) and returns an allocated string
|
|
|
|
// Returns NULL if no lines could be read from the file
|
|
|
|
static char *read_file_line(FILE *file) {
|
|
|
|
char *buffer;
|
|
|
|
size_t bufferSize = 8;
|
|
|
|
size_t offset = 0; // offset in buffer to write
|
|
|
|
|
|
|
|
buffer = malloc(bufferSize);
|
|
|
|
while (1) {
|
|
|
|
// Read a line from the file
|
|
|
|
if (fgets(buffer + offset, bufferSize - offset, file) == NULL) {
|
|
|
|
free(buffer);
|
|
|
|
return NULL; // Nothing could be read.
|
|
|
|
}
|
|
|
|
offset = strlen(buffer);
|
|
|
|
assert(offset > 0);
|
|
|
|
|
|
|
|
// If a newline was found, remove the trailing newline and exit
|
|
|
|
if (buffer[offset - 1] == '\n') {
|
|
|
|
buffer[offset - 1] = '\0';
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (feof(file)) // EOF was reached
|
|
|
|
break;
|
|
|
|
|
|
|
|
// If no newline or EOF was reached, then the whole line wasn't read.
|
|
|
|
bufferSize *= 2; // Increase buffer size
|
|
|
|
buffer = realloc(buffer, bufferSize);
|
|
|
|
assert(buffer != NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
return buffer;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Returns the position of the first non-whitespace character
|
|
|
|
static char *skip_whitespace(char *str) {
|
|
|
|
while (isspace(*str))
|
|
|
|
str++;
|
|
|
|
return str;
|
|
|
|
}
|
|
|
|
|
|
|
|
// NULL-terminates the current whitespace-delimited word, and returns a pointer to the next word
|
|
|
|
static char *word_split(char *str) {
|
|
|
|
// Precondition: str must not point to whitespace
|
|
|
|
assert(!isspace(*str));
|
|
|
|
|
|
|
|
// Find either the next whitespace char or end of string
|
|
|
|
while (!isspace(*str) && *str != '\0')
|
|
|
|
str++;
|
|
|
|
if (*str == '\0') // End of string
|
|
|
|
return str;
|
|
|
|
|
|
|
|
// Terminate current word
|
|
|
|
*(str++) = '\0';
|
|
|
|
|
|
|
|
// Skip whitespace to next word
|
|
|
|
return skip_whitespace(str);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Splits a string into words, and stores the words into the 'tokens' array
|
|
|
|
// 'maxTokens' is the length of the 'tokens' array
|
|
|
|
// Returns the number of tokens parsed
|
|
|
|
static unsigned int tokenize_string(char *str, int maxTokens, char **tokens) {
|
|
|
|
int count = 0;
|
|
|
|
|
|
|
|
str = skip_whitespace(str);
|
|
|
|
while (str[0] != '\0' && count < maxTokens) {
|
|
|
|
tokens[count] = str;
|
|
|
|
str = word_split(str);
|
|
|
|
count++;
|
|
|
|
}
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Loads the config file specified by 'filename'
|
|
|
|
void configfile_load(const char *filename) {
|
|
|
|
FILE *file;
|
|
|
|
char *line;
|
|
|
|
|
|
|
|
printf("Loading configuration from '%s'\n", filename);
|
|
|
|
|
|
|
|
file = fopen(filename, "r");
|
|
|
|
if (file == NULL) {
|
|
|
|
// Create a new config file and save defaults
|
|
|
|
printf("Config file '%s' not found. Creating it.\n", filename);
|
|
|
|
configfile_save(filename);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Go through each line in the file
|
|
|
|
while ((line = read_file_line(file)) != NULL) {
|
|
|
|
char *p = line;
|
|
|
|
char *tokens[2];
|
|
|
|
int numTokens;
|
|
|
|
|
|
|
|
while (isspace(*p))
|
|
|
|
p++;
|
2020-05-15 20:38:35 +02:00
|
|
|
|
|
|
|
if (!*p || *p == '#') // comment or empty line
|
|
|
|
continue;
|
|
|
|
|
2020-05-07 20:21:22 +02:00
|
|
|
numTokens = tokenize_string(p, 2, tokens);
|
|
|
|
if (numTokens != 0) {
|
2020-05-15 20:38:35 +02:00
|
|
|
if (numTokens >= 2) {
|
2020-05-07 20:21:22 +02:00
|
|
|
const struct ConfigOption *option = NULL;
|
|
|
|
|
|
|
|
for (unsigned int i = 0; i < ARRAY_LEN(options); i++) {
|
|
|
|
if (strcmp(tokens[0], options[i].name) == 0) {
|
|
|
|
option = &options[i];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (option == NULL)
|
|
|
|
printf("unknown option '%s'\n", tokens[0]);
|
|
|
|
else {
|
|
|
|
switch (option->type) {
|
|
|
|
case CONFIG_TYPE_BOOL:
|
|
|
|
if (strcmp(tokens[1], "true") == 0)
|
|
|
|
*option->boolValue = true;
|
|
|
|
else if (strcmp(tokens[1], "false") == 0)
|
|
|
|
*option->boolValue = false;
|
|
|
|
break;
|
|
|
|
case CONFIG_TYPE_UINT:
|
|
|
|
sscanf(tokens[1], "%u", option->uintValue);
|
|
|
|
break;
|
2020-05-15 20:38:35 +02:00
|
|
|
case CONFIG_TYPE_BIND:
|
|
|
|
for (int i = 0; i < MAX_BINDS && i < numTokens - 1; ++i)
|
|
|
|
sscanf(tokens[i + 1], "%x", option->uintValue + i);
|
|
|
|
break;
|
2020-05-07 20:21:22 +02:00
|
|
|
case CONFIG_TYPE_FLOAT:
|
|
|
|
sscanf(tokens[1], "%f", option->floatValue);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
assert(0); // bad type
|
|
|
|
}
|
|
|
|
printf("option: '%s', value: '%s'\n", tokens[0], tokens[1]);
|
|
|
|
}
|
|
|
|
} else
|
|
|
|
puts("error: expected value");
|
|
|
|
}
|
|
|
|
free(line);
|
|
|
|
}
|
|
|
|
|
|
|
|
fclose(file);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Writes the config file to 'filename'
|
|
|
|
void configfile_save(const char *filename) {
|
|
|
|
FILE *file;
|
|
|
|
|
|
|
|
printf("Saving configuration to '%s'\n", filename);
|
|
|
|
|
|
|
|
file = fopen(filename, "w");
|
|
|
|
if (file == NULL) {
|
|
|
|
// error
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (unsigned int i = 0; i < ARRAY_LEN(options); i++) {
|
|
|
|
const struct ConfigOption *option = &options[i];
|
|
|
|
|
|
|
|
switch (option->type) {
|
|
|
|
case CONFIG_TYPE_BOOL:
|
|
|
|
fprintf(file, "%s %s\n", option->name, *option->boolValue ? "true" : "false");
|
|
|
|
break;
|
|
|
|
case CONFIG_TYPE_UINT:
|
|
|
|
fprintf(file, "%s %u\n", option->name, *option->uintValue);
|
|
|
|
break;
|
|
|
|
case CONFIG_TYPE_FLOAT:
|
|
|
|
fprintf(file, "%s %f\n", option->name, *option->floatValue);
|
|
|
|
break;
|
2020-05-15 20:38:35 +02:00
|
|
|
case CONFIG_TYPE_BIND:
|
|
|
|
fprintf(file, "%s ", option->name);
|
|
|
|
for (int i = 0; i < MAX_BINDS; ++i)
|
|
|
|
fprintf(file, "%04x ", option->uintValue[i]);
|
|
|
|
fprintf(file, "\n");
|
|
|
|
break;
|
2020-05-07 20:21:22 +02:00
|
|
|
default:
|
|
|
|
assert(0); // unknown type
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fclose(file);
|
|
|
|
}
|