sm64coopdx/src/pc/configfile.c
MysterD bed036bcfb Added coop settings to saved config file
Saves and loads last join IP/port attempt
Made host's port configurable in config file
Made host's save slot configurable in config file
Made host's player interaction setting configurable in config file

Indicated when the client was trying to connect on the join menu.
Fixed join menu display error where the port would disappear
2020-09-06 03:32:34 -07:00

358 lines
15 KiB
C

// 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>
#include "platform.h"
#include "configfile.h"
#include "cliopts.h"
#include "gfx/gfx_screen_config.h"
#include "gfx/gfx_window_manager_api.h"
#include "controller/controller_api.h"
#include "fs/fs.h"
#define ARRAY_LEN(arr) (sizeof(arr) / sizeof(arr[0]))
enum ConfigOptionType {
CONFIG_TYPE_BOOL,
CONFIG_TYPE_UINT,
CONFIG_TYPE_FLOAT,
CONFIG_TYPE_BIND,
CONFIG_TYPE_STRING,
};
struct ConfigOption {
const char *name;
enum ConfigOptionType type;
union {
bool *boolValue;
unsigned int *uintValue;
float* floatValue;
char* stringValue;
};
};
/*
*Config options and default values
*/
// Video/audio stuff
ConfigWindow configWindow = {
.x = WAPI_WIN_CENTERPOS,
.y = WAPI_WIN_CENTERPOS,
.w = DESIRED_SCREEN_WIDTH,
.h = DESIRED_SCREEN_HEIGHT,
.vsync = 1,
.reset = false,
.fullscreen = false,
.exiting_fullscreen = false,
.settings_changed = false,
};
unsigned int configFiltering = 1; // 0=force nearest, 1=linear, (TODO) 2=three-point
unsigned int configMasterVolume = 30; // 0 - MAX_VOLUME
unsigned int configMusicVolume = MAX_VOLUME;
unsigned int configSfxVolume = MAX_VOLUME;
unsigned int configEnvVolume = MAX_VOLUME;
// 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] = { 0x002A, 0x1009, 0x1104 };
unsigned int configKeyR[MAX_BINDS] = { 0x0036, 0x100A, 0x101B };
unsigned int configKeyZ[MAX_BINDS] = { 0x0025, 0x1007, 0x101A };
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 };
unsigned int configStickDeadzone = 16; // 16*DEADZONE_STEP=4960 (the original default deadzone)
unsigned int configRumbleStrength = 50;
#ifdef EXTERNAL_DATA
bool configPrecacheRes = true;
#endif
#ifdef BETTERCAMERA
// BetterCamera settings
unsigned int configCameraXSens = 10;
unsigned int configCameraYSens = 10;
unsigned int configCameraAggr = 0;
unsigned int configCameraPan = 0;
unsigned int configCameraDegrade = 50; // 0 - 100%
bool configCameraInvertX = false;
bool configCameraInvertY = true;
bool configEnableCamera = true;
bool configCameraAnalog = true;
bool configCameraMouse = false;
#endif
bool configSkipIntro = 0;
bool configHUD = true;
#ifdef DISCORDRPC
bool configDiscordRPC = true;
#endif
// coop-specific
char configJoinIp[MAX_CONFIG_STRING] = "";
unsigned int configJoinPort = DEFAULT_PORT;
unsigned int configHostPort = DEFAULT_PORT;
unsigned int configHostSaveSlot = 1;
unsigned int configPlayerInteraction = 1;
static const struct ConfigOption options[] = {
{.name = "fullscreen", .type = CONFIG_TYPE_BOOL, .boolValue = &configWindow.fullscreen},
{.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},
{.name = "vsync", .type = CONFIG_TYPE_BOOL, .boolValue = &configWindow.vsync},
{.name = "texture_filtering", .type = CONFIG_TYPE_UINT, .uintValue = &configFiltering},
{.name = "master_volume", .type = CONFIG_TYPE_UINT, .uintValue = &configMasterVolume},
{.name = "music_volume", .type = CONFIG_TYPE_UINT, .uintValue = &configMusicVolume},
{.name = "sfx_volume", .type = CONFIG_TYPE_UINT, .uintValue = &configSfxVolume},
{.name = "env_volume", .type = CONFIG_TYPE_UINT, .uintValue = &configEnvVolume},
{.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},
{.name = "stick_deadzone", .type = CONFIG_TYPE_UINT, .uintValue = &configStickDeadzone},
{.name = "rumble_strength", .type = CONFIG_TYPE_UINT, .uintValue = &configRumbleStrength},
#ifdef EXTERNAL_DATA
{.name = "precache", .type = CONFIG_TYPE_BOOL, .boolValue = &configPrecacheRes},
#endif
#ifdef BETTERCAMERA
{.name = "bettercam_enable", .type = CONFIG_TYPE_BOOL, .boolValue = &configEnableCamera},
{.name = "bettercam_analog", .type = CONFIG_TYPE_BOOL, .boolValue = &configCameraAnalog},
{.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},
{.name = "bettercam_degrade", .type = CONFIG_TYPE_UINT, .uintValue = &configCameraDegrade},
#endif
{.name = "skip_intro", .type = CONFIG_TYPE_BOOL, .boolValue = &configSkipIntro},
#ifdef DISCORDRPC
{.name = "discordrpc_enable", .type = CONFIG_TYPE_BOOL, .boolValue = &configDiscordRPC},
#endif
// coop-specific
{.name = "coop_join_ip", .type = CONFIG_TYPE_STRING, .stringValue = (char*)&configJoinIp},
{.name = "coop_join_port", .type = CONFIG_TYPE_UINT , .uintValue = &configJoinPort},
{.name = "coop_host_port", .type = CONFIG_TYPE_UINT , .uintValue = &configHostPort},
{.name = "coop_host_save_slot", .type = CONFIG_TYPE_UINT , .uintValue = &configHostSaveSlot},
{.name = "coop_player_interaction", .type = CONFIG_TYPE_UINT , .uintValue = &configPlayerInteraction},
};
// 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(fs_file_t *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 (fs_readline(file, buffer + offset, bufferSize - offset) == 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 (fs_eof(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;
}
// Gets the config file path and caches it
const char *configfile_name(void) {
return (gCLIOpts.ConfigFile[0]) ? gCLIOpts.ConfigFile : CONFIGFILE_DEFAULT;
}
// Loads the config file specified by 'filename'
void configfile_load(const char *filename) {
fs_file_t *file;
char *line;
printf("Loading configuration from '%s'\n", filename);
file = fs_open(filename);
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[1 + MAX_BINDS];
int numTokens;
while (isspace(*p))
p++;
if (!*p || *p == '#') // comment or empty line
continue;
numTokens = tokenize_string(p, sizeof(tokens) / sizeof(tokens[0]), tokens);
if (numTokens != 0) {
if (numTokens >= 2) {
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
*option->boolValue = false;
break;
case CONFIG_TYPE_UINT:
sscanf(tokens[1], "%u", option->uintValue);
break;
case CONFIG_TYPE_BIND:
for (int i = 0; i < MAX_BINDS && i < numTokens - 1; ++i)
sscanf(tokens[i + 1], "%x", option->uintValue + i);
break;
case CONFIG_TYPE_FLOAT:
sscanf(tokens[1], "%f", option->floatValue);
break;
case CONFIG_TYPE_STRING:
memset(option->stringValue, '\0', MAX_CONFIG_STRING);
strncpy(option->stringValue, tokens[1], MAX_CONFIG_STRING);
break;
default:
assert(0); // bad type
}
printf("option: '%s', value:", tokens[0]);
for (int i = 1; i < numTokens; ++i) printf(" '%s'", tokens[i]);
printf("\n");
}
} else
puts("error: expected value");
}
free(line);
}
fs_close(file);
}
// Writes the config file to 'filename'
void configfile_save(const char *filename) {
FILE *file;
printf("Saving configuration to '%s'\n", filename);
file = fopen(fs_get_write_path(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;
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;
case CONFIG_TYPE_STRING:
fprintf(file, "%s %s\n", option->name, option->stringValue);
break;
default:
assert(0); // unknown type
}
}
fclose(file);
}