#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <ctype.h>
#ifdef __linux__
#include <sys/stat.h>
#endif
#include "configfile.h"
#include "../game/settings.h"
#include "../game/main.h"
#define ARRAY_LEN(arr) (sizeof(arr) / sizeof(arr[0]))
static const struct ConfigOption options[] = {
{ .name = "DISPLAY", .type = CONFIG_TYPE_SECTION },
{ .name = "fullscreen", .type = CONFIG_TYPE_BOOL, .boolValue = (bool*)&configFullscreen },
{ .name = "default_monitor", .type = CONFIG_TYPE_UINT, .uintValue = &configDefaultMonitor },
{ .name = "vsync", .type = CONFIG_TYPE_BOOL, .boolValue = (bool*)&configVSync },
{ .name = "window_width", .type = CONFIG_TYPE_UINT, .uintValue = &configWindowWidth },
{ .name = "window_height", .type = CONFIG_TYPE_UINT, .uintValue = &configWindowHeight },
{ .name = "fullscreen_display_mode", .type = CONFIG_TYPE_UINT, .uintValue = &configFullscreenDisplayMode },
{ .name = "graphics_backend", .type = CONFIG_TYPE_UINT, .uintValue = &configGraphicsBackend },
{ .name = "AUDIO", .type = CONFIG_TYPE_SECTION },
{ .name = "overall_volume", .type = CONFIG_TYPE_FLOAT, .floatValue = &configOverallVolume },
{ .name = "music_volume", .type = CONFIG_TYPE_FLOAT, .floatValue = &configSeqVolume[0] },
{ .name = "jingle_volume", .type = CONFIG_TYPE_FLOAT, .floatValue = &configSeqVolume[1] },
{ .name = "sound_volume", .type = CONFIG_TYPE_FLOAT, .floatValue = &configSeqVolume[2] },
{ .name = "GRAPHICS", .type = CONFIG_TYPE_SECTION },
{ .name = "internal_resolution", .type = CONFIG_TYPE_FLOAT, .floatValue = &configInternalResolution },
{ .name = "aspect_ratio", .type = CONFIG_TYPE_UINT, .uintValue = &configAspectRatio },
{ .name = "frame_rate", .type = CONFIG_TYPE_UINT, .uintValue = &configFrameRate },
{ .name = "anti_aliasing", .type = CONFIG_TYPE_UINT, .uintValue = &configAntiAliasing },
{ .name = "draw_distance", .type = CONFIG_TYPE_FLOAT, .floatValue = &configDrawDistanceMultiplier },
{ .name = "level_of_detail", .type = CONFIG_TYPE_UINT, .uintValue = &configLevelOfDetail },
{ .name = "texture_filtering", .type = CONFIG_TYPE_SINT, .sintValue = &configTextureFiltering },
{ .name = "anisotropic_filtering", .type = CONFIG_TYPE_UINT, .uintValue = &configAnisotropicFiltering },
{ .name = "noise_type", .type = CONFIG_TYPE_UINT, .uintValue = &configNoiseType },
{ .name = "n64_blur", .type = CONFIG_TYPE_BOOL, .boolValue = (bool*)&configN64Blur },
{ .name = "CONTROLS", .type = CONFIG_TYPE_SECTION },
{ .name = "improved_controls", .type = CONFIG_TYPE_BOOL, .boolValue = (bool*)&configImprovedControls },
{ .name = "improved_swimming", .type = CONFIG_TYPE_BOOL, .boolValue = (bool*)&configImprovedSwimming },
{ .name = "improved_hanging", .type = CONFIG_TYPE_BOOL, .boolValue = (bool*)&configImprovedHanging },
{ .name = "enemy_bouncing", .type = CONFIG_TYPE_BOOL, .boolValue = (bool*)&configEnemyBouncing },
{ .name = "dpad_controls", .type = CONFIG_TYPE_BOOL, .boolValue = (bool*)&configDpadControls },
{ .name = "full_air_control", .type = CONFIG_TYPE_BOOL, .boolValue = (bool*)&configFullAirControl },
{ .name = "GAMEPLAY", .type = CONFIG_TYPE_SECTION },
{ .name = "fix_gameplay_bugs", .type = CONFIG_TYPE_UINT, .uintValue = &configApplyBugFixes },
{ .name = "save_lives_to_save_file", .type = CONFIG_TYPE_BOOL, .boolValue = (bool*)&configSaveLives },
{ .name = "make_items_respawn", .type = CONFIG_TYPE_BOOL, .boolValue = (bool*)&configRespawnCertainItems },
{ .name = "remove_inconvenient_warps", .type = CONFIG_TYPE_BOOL, .boolValue = (bool*)&configRemoveAnnoyingWarps },
{ .name = "improved_powerups", .type = CONFIG_TYPE_BOOL, .boolValue = (bool*)&configBetterPowerups },
{ .name = "improved_enemies", .type = CONFIG_TYPE_BOOL, .boolValue = (bool*)&configBetterEnemies },
{ .name = "improved_npcs", .type = CONFIG_TYPE_BOOL, .boolValue = (bool*)&configTalkNPCs },
{ .name = "improved_blast_away_the_wall", .type = CONFIG_TYPE_BOOL, .boolValue = (bool*)&configBetterBlastAwayTheWall },
{ .name = "bring_mips_back", .type = CONFIG_TYPE_BOOL, .boolValue = (bool*)&configBringMipsBack },
{ .name = "disable_fall_damage", .type = CONFIG_TYPE_BOOL, .boolValue = (bool*)&configDisableFallDamage },
{ .name = "allow_leaving_the_course_at_any_time", .type = CONFIG_TYPE_BOOL, .boolValue = (bool*)&configLeaveAnyTime },
{ .name = "PROGRESSION", .type = CONFIG_TYPE_SECTION },
{ .name = "tie_bowsers_sub_to_missions", .type = CONFIG_TYPE_BOOL, .boolValue = (bool*)&configBowsersSub },
{ .name = "always_stay_in_course", .type = CONFIG_TYPE_UINT, .uintValue = &configStayInCourse },
{ .name = "skip_mission_select", .type = CONFIG_TYPE_BOOL, .boolValue = (bool*)&configSkipMissionSelect },
{ .name = "auto_switch_to_the_next_mission", .type = CONFIG_TYPE_BOOL, .boolValue = (bool*)&configSwitchToNextMission },
{ .name = "skip_cutscenes", .type = CONFIG_TYPE_BOOL, .boolValue = (bool*)&configSkipCutscenes },
{ .name = "CAMERA", .type = CONFIG_TYPE_SECTION },
{ .name = "default_camera_mode", .type = CONFIG_TYPE_UINT, .uintValue = &configDefaultCameraMode },
{ .name = "alternate_camera_mode", .type = CONFIG_TYPE_UINT, .uintValue = &configAlternateCameraMode },
{ .name = "horizontal_analog_camera", .type = CONFIG_TYPE_BOOL, .boolValue = (bool*)&configImprovedCamera },
{ .name = "vertical_analog_camera", .type = CONFIG_TYPE_BOOL, .boolValue = (bool*)&configVerticalCamera },
{ .name = "improved_cbutton_camera", .type = CONFIG_TYPE_BOOL, .boolValue = (bool*)&configImprovedCButtonCamera },
{ .name = "center_camera_button", .type = CONFIG_TYPE_BOOL, .boolValue = (bool*)&configCenterCameraButton },
{ .name = "invert_horizontal_camera_controls", .type = CONFIG_TYPE_BOOL, .boolValue = (bool*)&configInvertedCamera },
{ .name = "invert_vertical_camera_controls", .type = CONFIG_TYPE_BOOL, .boolValue = (bool*)&configInvertedVerticalCamera },
{ .name = "analog_camera_speed", .type = CONFIG_TYPE_FLOAT, .floatValue = &configCameraSpeed },
{ .name = "additional_camera_distance", .type = CONFIG_TYPE_FLOAT, .floatValue = &configAdditionalCameraDistance },
{ .name = "additional_fov", .type = CONFIG_TYPE_FLOAT, .floatValue = &configAdditionalFOV },
{ .name = "HUD AND UI", .type = CONFIG_TYPE_SECTION },
{ .name = "fix_text_typos", .type = CONFIG_TYPE_BOOL, .boolValue = (bool*)&configFixTextTypos },
{ .name = "add_quit_option", .type = CONFIG_TYPE_BOOL, .boolValue = (bool*)&configQuitOption },
{ .name = "hud_layout", .type = CONFIG_TYPE_UINT, .uintValue = &configHudLayout },
{ .name = "4by3_hud", .type = CONFIG_TYPE_BOOL, .boolValue = (bool*)&config4by3Hud },
{ .name = "show_the_collected_stars", .type = CONFIG_TYPE_BOOL, .boolValue = (bool*)&configHudStars },
{ .name = "add_zeroes_to_counters", .type = CONFIG_TYPE_BOOL, .boolValue = (bool*)&configAddZeroes },
{ .name = "always_show_the_100_coin_star", .type = CONFIG_TYPE_BOOL, .boolValue = (bool*)&configShow100CoinStar },
{ .name = "always_show_the_health_meter", .type = CONFIG_TYPE_BOOL, .boolValue = (bool*)&configAlwaysShowHealth },
{ .name = "hud_filtering", .type = CONFIG_TYPE_BOOL, .boolValue = (bool*)&configHUDFiltering },
{ .name = "hide_hud", .type = CONFIG_TYPE_BOOL, .boolValue = (bool*)&configHideHud },
{ .name = "EXTRA MOVES", .type = CONFIG_TYPE_SECTION },
{ .name = "wall_sliding", .type = CONFIG_TYPE_BOOL, .boolValue = (bool*)&configWallSliding },
{ .name = "ground_pound_jump", .type = CONFIG_TYPE_BOOL, .boolValue = (bool*)&configGroundPoundJump },
{ .name = "sunshine_dive_hop", .type = CONFIG_TYPE_BOOL, .boolValue = (bool*)&configSunshineDive },
{ .name = "odyssey_ground_pound_dive", .type = CONFIG_TYPE_BOOL, .boolValue = (bool*)&configOdysseyDive },
{ .name = "odyssey_rolling", .type = CONFIG_TYPE_BOOL, .boolValue = (bool*)&configRolling },
{ .name = "flashback_ground_pound", .type = CONFIG_TYPE_BOOL, .boolValue = (bool*)&configFlashbackGroundPound },
{ .name = "RESTORATIONS", .type = CONFIG_TYPE_SECTION },
{ .name = "enable_the_unused_pyramid_cutscene", .type = CONFIG_TYPE_BOOL, .boolValue = (bool*)&configUnusedPyramidCutscene },
{ .name = "restore_unused_sound_effects", .type = CONFIG_TYPE_BOOL, .boolValue = (bool*)&configRestoreUnusedSounds },
{ .name = "restore_mother_penguins_sad_eyes", .type = CONFIG_TYPE_BOOL, .boolValue = (bool*)&configPenguinSadEyes },
{ .name = "replace_triple_jump_with_twirl", .type = CONFIG_TYPE_BOOL, .boolValue = (bool*)&configTwirlTripleJump },
{ .name = "use_beta_like_camera", .type = CONFIG_TYPE_BOOL, .boolValue = (bool*)&configBetaLikeCamera },
{ .name = "make_mario_sparkle_at_course_start", .type = CONFIG_TYPE_BOOL, .boolValue = (bool*)&configSpawnSparkles },
{ .name = "replace_keys_with_stars_when_collected", .type = CONFIG_TYPE_BOOL, .boolValue = (bool*)&configReplaceKeysWithStars },
{ .name = "BONUS MODES", .type = CONFIG_TYPE_SECTION },
{ .name = "casual_mode", .type = CONFIG_TYPE_BOOL, .boolValue = (bool*)&configCasualMode },
{ .name = "infinite_lives_mode", .type = CONFIG_TYPE_UINT, .uintValue = &configLifeMode },
{ .name = "encore_mode", .type = CONFIG_TYPE_UINT, .uintValue = &configEncoreMode },
{ .name = "invisible_mode", .type = CONFIG_TYPE_BOOL, .boolValue = (bool*)&configInvisibleMode },
{ .name = "no_healing_mode", .type = CONFIG_TYPE_BOOL, .boolValue = (bool*)&configNoHealingMode },
{ .name = "green_demon_mode", .type = CONFIG_TYPE_UINT, .uintValue = &configGreenDemon },
{ .name = "hard_mode", .type = CONFIG_TYPE_BOOL, .boolValue = (bool*)&configHardSave },
{ .name = "daredevil_mode", .type = CONFIG_TYPE_BOOL, .boolValue = (bool*)&configDaredevilSave },
{ .name = "permadeath_mode", .type = CONFIG_TYPE_BOOL, .boolValue = (bool*)&configHardcoreSave },
{ .name = "COLORS", .type = CONFIG_TYPE_SECTION },
{ .name = "color_palette", .type = CONFIG_TYPE_UINT, .uintValue = &configColorPalette },
{ .name = "color_cap_main_r", .type = CONFIG_TYPE_UINT, .uintValue = &configColorCap[0][0] },
{ .name = "color_cap_main_g", .type = CONFIG_TYPE_UINT, .uintValue = &configColorCap[0][1] },
{ .name = "color_cap_main_b", .type = CONFIG_TYPE_UINT, .uintValue = &configColorCap[0][2] },
{ .name = "color_cap_shading_r", .type = CONFIG_TYPE_UINT, .uintValue = &configColorCap[1][0] },
{ .name = "color_cap_shading_g", .type = CONFIG_TYPE_UINT, .uintValue = &configColorCap[1][1] },
{ .name = "color_cap_shading_b", .type = CONFIG_TYPE_UINT, .uintValue = &configColorCap[1][2] },
{ .name = "color_shirt_main_r", .type = CONFIG_TYPE_UINT, .uintValue = &configColorShirt[0][0] },
{ .name = "color_shirt_main_g", .type = CONFIG_TYPE_UINT, .uintValue = &configColorShirt[0][1] },
{ .name = "color_shirt_main_b", .type = CONFIG_TYPE_UINT, .uintValue = &configColorShirt[0][2] },
{ .name = "color_shirt_shading_r", .type = CONFIG_TYPE_UINT, .uintValue = &configColorShirt[1][0] },
{ .name = "color_shirt_shading_g", .type = CONFIG_TYPE_UINT, .uintValue = &configColorShirt[1][1] },
{ .name = "color_shirt_shading_b", .type = CONFIG_TYPE_UINT, .uintValue = &configColorShirt[1][2] },
{ .name = "color_overalls_main_r", .type = CONFIG_TYPE_UINT, .uintValue = &configColorOveralls[0][0] },
{ .name = "color_overalls_main_g", .type = CONFIG_TYPE_UINT, .uintValue = &configColorOveralls[0][1] },
{ .name = "color_overalls_main_b", .type = CONFIG_TYPE_UINT, .uintValue = &configColorOveralls[0][2] },
{ .name = "color_overalls_shading_r", .type = CONFIG_TYPE_UINT, .uintValue = &configColorOveralls[1][0] },
{ .name = "color_overalls_shading_g", .type = CONFIG_TYPE_UINT, .uintValue = &configColorOveralls[1][1] },
{ .name = "color_overalls_shading_b", .type = CONFIG_TYPE_UINT, .uintValue = &configColorOveralls[1][2] },
{ .name = "color_gloves_main_r", .type = CONFIG_TYPE_UINT, .uintValue = &configColorGloves[0][0] },
{ .name = "color_gloves_main_g", .type = CONFIG_TYPE_UINT, .uintValue = &configColorGloves[0][1] },
{ .name = "color_gloves_main_b", .type = CONFIG_TYPE_UINT, .uintValue = &configColorGloves[0][2] },
{ .name = "color_gloves_shading_r", .type = CONFIG_TYPE_UINT, .uintValue = &configColorGloves[1][0] },
{ .name = "color_gloves_shading_g", .type = CONFIG_TYPE_UINT, .uintValue = &configColorGloves[1][1] },
{ .name = "color_gloves_shading_b", .type = CONFIG_TYPE_UINT, .uintValue = &configColorGloves[1][2] },
{ .name = "color_shoes_main_r", .type = CONFIG_TYPE_UINT, .uintValue = &configColorShoes[0][0] },
{ .name = "color_shoes_main_g", .type = CONFIG_TYPE_UINT, .uintValue = &configColorShoes[0][1] },
{ .name = "color_shoes_main_b", .type = CONFIG_TYPE_UINT, .uintValue = &configColorShoes[0][2] },
{ .name = "color_shoes_shading_r", .type = CONFIG_TYPE_UINT, .uintValue = &configColorShoes[1][0] },
{ .name = "color_shoes_shading_g", .type = CONFIG_TYPE_UINT, .uintValue = &configColorShoes[1][1] },
{ .name = "color_shoes_shading_b", .type = CONFIG_TYPE_UINT, .uintValue = &configColorShoes[1][2] },
{ .name = "color_skin_main_r", .type = CONFIG_TYPE_UINT, .uintValue = &configColorSkin[0][0] },
{ .name = "color_skin_main_g", .type = CONFIG_TYPE_UINT, .uintValue = &configColorSkin[0][1] },
{ .name = "color_skin_main_b", .type = CONFIG_TYPE_UINT, .uintValue = &configColorSkin[0][2] },
{ .name = "color_skin_shading_r", .type = CONFIG_TYPE_UINT, .uintValue = &configColorSkin[1][0] },
{ .name = "color_skin_shading_g", .type = CONFIG_TYPE_UINT, .uintValue = &configColorSkin[1][1] },
{ .name = "color_skin_shading_b", .type = CONFIG_TYPE_UINT, .uintValue = &configColorSkin[1][2] },
{ .name = "color_hair_main_r", .type = CONFIG_TYPE_UINT, .uintValue = &configColorHair[0][0] },
{ .name = "color_hair_main_g", .type = CONFIG_TYPE_UINT, .uintValue = &configColorHair[0][1] },
{ .name = "color_hair_main_b", .type = CONFIG_TYPE_UINT, .uintValue = &configColorHair[0][2] },
{ .name = "color_hair_shading_r", .type = CONFIG_TYPE_UINT, .uintValue = &configColorHair[1][0] },
{ .name = "color_hair_shading_g", .type = CONFIG_TYPE_UINT, .uintValue = &configColorHair[1][1] },
{ .name = "color_hair_shading_b", .type = CONFIG_TYPE_UINT, .uintValue = &configColorHair[1][2] },
{ .name = "show_cap_logo", .type = CONFIG_TYPE_BOOL, .boolValue = (bool*)&configShowCapLogo },
{ .name = "CHEATS", .type = CONFIG_TYPE_SECTION },
{ .name = "level_select", .type = CONFIG_TYPE_BOOL, .boolValue = (bool*)&gDebugLevelSelect },
{ .name = "debug_movement_mode", .type = CONFIG_TYPE_BOOL, .boolValue = (bool*)&configDebugMovementMode },
{ .name = "debug_cap_changer", .type = CONFIG_TYPE_BOOL, .boolValue = (bool*)&configDebugCapChanger },
{ .name = "debug_object_spawner", .type = CONFIG_TYPE_BOOL, .boolValue = (bool*)&configDebugObjectSpawner },
{ .name = "moon_jump", .type = CONFIG_TYPE_UINT, .uintValue = &configMoonJump },
{ .name = "blj_everywhere", .type = CONFIG_TYPE_UINT, .uintValue = &configBLJEverywhere },
{ .name = "god_mode", .type = CONFIG_TYPE_BOOL, .boolValue = (bool*)&configGodMode },
{ .name = "hyperspeed_mode", .type = CONFIG_TYPE_BOOL, .boolValue = (bool*)&configHyperspeedMode },
{ .name = "easy_bowser_throws", .type = CONFIG_TYPE_BOOL, .boolValue = (bool*)&configEasyBowserThrows },
{ .name = "make_secrets_visible", .type = CONFIG_TYPE_BOOL, .boolValue = (bool*)&configVisibleSecrets },
{ .name = "no_cannon_limits", .type = CONFIG_TYPE_BOOL, .boolValue = (bool*)&configFlexibleCannons },
{ .name = "coins_required_for_the_coin_stars", .type = CONFIG_TYPE_UINT, .uintValue = &configCoinStarCoins },
{ .name = "FOR FUN", .type = CONFIG_TYPE_SECTION },
{ .name = "rock_paper_scissors", .type = CONFIG_TYPE_BOOL, .boolValue = (bool*)&configRockPaperScissors },
{ .name = "mad_penguin", .type = CONFIG_TYPE_BOOL, .boolValue = (bool*)&configAngryPenguin },
{ .name = "paper_mode", .type = CONFIG_TYPE_BOOL, .boolValue = (bool*)&configPaperMode },
{ .name = "fx_mode", .type = CONFIG_TYPE_BOOL, .boolValue = (bool*)&configFXMode },
{ .name = "disable_lighting", .type = CONFIG_TYPE_BOOL, .boolValue = (bool*)&configDisableLighting },
{ .name = "ADVANCED", .type = CONFIG_TYPE_SECTION },
{ .name = "show_debug_display", .type = CONFIG_TYPE_BOOL, .boolValue = (bool*)&gShowDebugText },
{ .name = "show_debug_profiler", .type = CONFIG_TYPE_BOOL, .boolValue = (bool*)&gShowProfiler },
{ .name = "custom_camera_distance", .type = CONFIG_TYPE_FLOAT, .floatValue = &configCustomCameraDistance },
{ .name = "zoomed_out_custom_camera_distance", .type = CONFIG_TYPE_FLOAT, .floatValue = &configCustomCameraDistanceZoomedOut },
{ .name = "CONTROLLER MAPPING", .type = CONFIG_TYPE_SECTION },
{ .name = "button_a", .type = CONFIG_TYPE_UINT, .uintValue = &configButtonA },
{ .name = "button_b", .type = CONFIG_TYPE_UINT, .uintValue = &configButtonB },
{ .name = "button_start", .type = CONFIG_TYPE_UINT, .uintValue = &configButtonStart },
{ .name = "button_l", .type = CONFIG_TYPE_UINT, .uintValue = &configButtonL },
{ .name = "button_r", .type = CONFIG_TYPE_UINT, .uintValue = &configButtonR },
{ .name = "button_z", .type = CONFIG_TYPE_UINT, .uintValue = &configButtonZ },
{ .name = "button_cup", .type = CONFIG_TYPE_UINT, .uintValue = &configButtonCUp },
{ .name = "button_cdown", .type = CONFIG_TYPE_UINT, .uintValue = &configButtonCDown },
{ .name = "button_cleft", .type = CONFIG_TYPE_UINT, .uintValue = &configButtonCLeft },
{ .name = "button_cright", .type = CONFIG_TYPE_UINT, .uintValue = &configButtonCRight },
{ .name = "left_analog_stick_deadzone", .type = CONFIG_TYPE_UINT, .uintValue = &gControllerLeftDeadzone },
{ .name = "right_analog_stick_deadzone", .type = CONFIG_TYPE_UINT, .uintValue = &gControllerRightDeadzone },
{ .name = "rumble_strength", .type = CONFIG_TYPE_FLOAT, .floatValue = &configRumbleStrength },
{ .name = "block_non_xinput_controllers", .type = CONFIG_TYPE_BOOL, .boolValue = (bool*)&configBlockNonXinputControllers },
{ .name = "KEYBOARD MAPPING", .type = CONFIG_TYPE_SECTION },
{ .name = "key_a", .type = CONFIG_TYPE_UINT, .uintValue = &configKeyA },
{ .name = "key_b", .type = CONFIG_TYPE_UINT, .uintValue = &configKeyB },
{ .name = "key_start", .type = CONFIG_TYPE_UINT, .uintValue = &configKeyStart },
{ .name = "key_l", .type = CONFIG_TYPE_UINT, .uintValue = &configKeyL },
{ .name = "key_r", .type = CONFIG_TYPE_UINT, .uintValue = &configKeyR },
{ .name = "key_z", .type = CONFIG_TYPE_UINT, .uintValue = &configKeyZ },
{ .name = "key_cup", .type = CONFIG_TYPE_UINT, .uintValue = &configKeyCUp },
{ .name = "key_cdown", .type = CONFIG_TYPE_UINT, .uintValue = &configKeyCDown },
{ .name = "key_cleft", .type = CONFIG_TYPE_UINT, .uintValue = &configKeyCLeft },
{ .name = "key_cright", .type = CONFIG_TYPE_UINT, .uintValue = &configKeyCRight },
{ .name = "key_stickup", .type = CONFIG_TYPE_UINT, .uintValue = &configKeyStickUp },
{ .name = "key_stickdown", .type = CONFIG_TYPE_UINT, .uintValue = &configKeyStickDown },
{ .name = "key_stickleft", .type = CONFIG_TYPE_UINT, .uintValue = &configKeyStickLeft },
{ .name = "key_stickright", .type = CONFIG_TYPE_UINT, .uintValue = &configKeyStickRight },
{ .name = "key_walktrigger", .type = CONFIG_TYPE_UINT, .uintValue = &configKeyWalk },
{ .name = "MOUSE", .type = CONFIG_TYPE_SECTION },
{ .name = "mouse_support", .type = CONFIG_TYPE_BOOL, .boolValue = (bool*)&configMouseCam },
{ .name = "mouse_sensitivity", .type = CONFIG_TYPE_FLOAT, .floatValue = &configMouseSensitivity },
{ .name = "left_mouse_button_action", .type = CONFIG_TYPE_UINT, .uintValue = &configMouseLeft },
{ .name = "right_mouse_button_action", .type = CONFIG_TYPE_UINT, .uintValue = &configMouseRight },
{ .name = "middle_mouse_button_action", .type = CONFIG_TYPE_UINT, .uintValue = &configMouseMiddle },
{ .name = "mouse_wheel_up_action", .type = CONFIG_TYPE_UINT, .uintValue = &configMouseWheelUp },
{ .name = "mouse_wheel_down_action", .type = CONFIG_TYPE_UINT, .uintValue = &configMouseWheelDown },
};
static char *read_file_line(FILE *file) {
char *buffer;
size_t bufferSize = 8;
size_t offset = 0;
buffer = malloc(bufferSize);
while (1) {
if (fgets(buffer + offset, bufferSize - offset, file) == NULL) {
free(buffer);
return NULL;
}
offset = strlen(buffer);
assert(offset > 0);
if (buffer[offset - 1] == '\n') {
buffer[offset - 1] = '\0';
break;
}
if (feof(file))
break;
bufferSize *= 2;
buffer = realloc(buffer, bufferSize);
assert(buffer != NULL);
}
return buffer;
}
static char *skip_whitespace(char *str) {
while (isspace(*str) || *str == '=' || *str == '\"')
str++;
return str;
}
static char *word_split(char *str) {
assert(!isspace(*str));
if (*str == '\"')
str++;
while (!isspace(*str) && *str != '\0' && *str != '=' && *str != '\"')
str++;
if (*str == '\0')
return str;
*(str++) = '\0';
return skip_whitespace(str);
}
static unsigned int tokenize_string(char *str, int maxTokens, char **tokens) {
int count = 0;
str = skip_whitespace(str);
while (str[0] != '\0' && count < maxTokens) {
if ((str[count] == ';') || (str[count] == '#'))
break;
if (str[count] == '\"') {
str[count]++;
}
tokens[count] = str;
str = word_split(str);
count++;
}
return count;
}
void configfile_load(const char *filename) {
FILE *file;
char *line;
printf("Loading configuration from '%s'\n", filename);
file = fopen(filename, "r");
if (file == NULL) {
printf("Config file '%s' not found. Creating it.\n", filename);
configfile_save(filename);
return;
}
while ((line = read_file_line(file)) != NULL) {
char *p = line;
char *tokens[2];
int numTokens;
while (isspace(*p))
p++;
if ((*p == '[') || (*p == '\n'))
continue;
numTokens = tokenize_string(p, 2, 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 if (strcmp(tokens[1], "false") == 0)
*option->boolValue = false;
break;
case CONFIG_TYPE_UINT:
sscanf(tokens[1], "%u", option->uintValue);
break;
case CONFIG_TYPE_SINT:
sscanf(tokens[1], "%d", option->sintValue);
break;
case CONFIG_TYPE_FLOAT:
sscanf(tokens[1], "%f", option->floatValue);
break;
default:
assert(0);
}
printf("option: '%s', value: '%s'\n", tokens[0], tokens[1]);
}
} else
puts("error: expected value");
}
free(line);
}
fclose(file);
}
void configfile_save(const char *filename) {
FILE *file;
char *dir = malloc(128);
printf("Saving configuration to '%s'\n", filename);
#ifdef __linux__
strcat(dir, "/");
char* copy = strdup(filename);
strcat(dir, strtok(copy + 1, "/"));
free(copy);
mkdir(dir, 0777);
#endif
file = fopen(filename, "w");
if (file == NULL) {
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_SINT:
fprintf(file, "%s = \"%d\"\n", option->name, *option->sintValue);
break;
case CONFIG_TYPE_FLOAT:
fprintf(file, "%s = \"%f\"\n", option->name, *option->floatValue);
break;
case CONFIG_TYPE_SECTION:
fprintf(file, "[%s]\n", option->name);
break;
default:
assert(0);
}
}
fclose(file);
}