// configfile.c - handles loading and saving the configuration options #include #include #include #include #include #include #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" #include "mods/mods.h" #include "network/ban_list.h" #include "crash_handler.h" #include "network/moderator_list.h" #include "debuglog.h" #include "djui/djui_hud_utils.h" #include "game/save_file.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, CONFIG_TYPE_U64, CONFIG_TYPE_COLOR, }; struct ConfigOption { const char *name; enum ConfigOptionType type; union { bool *boolValue; unsigned int *uintValue; float* floatValue; char* stringValue; u64* u64Value; u8 (*colorValue)[3]; }; int maxStringLength; }; struct FunctionConfigOption { const char *name; void (*read)(char**, int); void (*write)(FILE*); }; /* *Config options and default values */ static_assert(NUM_SAVE_FILES == 4); // update this if more save slots are added char configSaveNames[4][MAX_SAVE_NAME_STRING] = { "Super Mario 64", "Super Mario 64", "Super Mario 64", "Super Mario 64" }; // Video/audio stuff ConfigWindow configWindow = { .x = WAPI_WIN_CENTERPOS, .y = WAPI_WIN_CENTERPOS, .w = DESIRED_SCREEN_WIDTH, .h = DESIRED_SCREEN_HEIGHT, .vsync = 0, .reset = false, .fullscreen = false, .exiting_fullscreen = false, .settings_changed = false, .msaa = 0, }; unsigned int configFiltering = 2; // 0=force nearest, 1=linear, 2=three-point unsigned int configMasterVolume = 80; // 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, 0x1001, 0x1101 }; unsigned int configKeyX[MAX_BINDS] = { 0x0017, 0x1002, VK_INVALID }; unsigned int configKeyY[MAX_BINDS] = { 0x0032, 0x1003, VK_INVALID }; 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 configKeyChat[MAX_BINDS] = { 0x001C, VK_INVALID, VK_INVALID }; unsigned int configKeyPlayerList[MAX_BINDS] = { 0x000F, 0x1004, VK_INVALID }; unsigned int configKeyDUp[MAX_BINDS] = { 0x0147, 0x100b, VK_INVALID }; unsigned int configKeyDDown[MAX_BINDS] = { 0x014f, 0x100c, VK_INVALID }; unsigned int configKeyDLeft[MAX_BINDS] = { 0x0153, 0x100d, VK_INVALID }; unsigned int configKeyDRight[MAX_BINDS] = { 0x0151, 0x100e, VK_INVALID }; unsigned int configKeyConsole[MAX_BINDS] = { 0x0029, 0x003B, VK_INVALID }; unsigned int configKeyPrevPage[MAX_BINDS] = { 0x0016, VK_INVALID, VK_INVALID }; unsigned int configKeyNextPage[MAX_BINDS] = { 0x0018, VK_INVALID, VK_INVALID }; unsigned int configKeyDisconnect[MAX_BINDS] = { VK_INVALID, VK_INVALID, VK_INVALID }; unsigned int configStickDeadzone = 16; // 16*DEADZONE_STEP=4960 (the original default deadzone) unsigned int configRumbleStrength = 50; // better camera settings unsigned int configCameraXSens = 50; unsigned int configCameraYSens = 50; unsigned int configCameraAggr = 0; unsigned int configCameraPan = 0; unsigned int configCameraDegrade = 50; // 0 - 100% bool configCameraInvertX = false; bool configCameraInvertY = true; bool configEnableCamera = false; bool configCameraAnalog = false; bool configCameraMouse = false; // coop-specific bool configSkipIntro = 0; bool configBubbleDeath = true; bool configPauseAnywhere = false; unsigned int configAmountofPlayers = 16; char configJoinIp[MAX_CONFIG_STRING] = ""; unsigned int configJoinPort = DEFAULT_PORT; unsigned int configHostPort = DEFAULT_PORT; unsigned int configHostSaveSlot = 1; unsigned int configPlayerInteraction = 1; unsigned int configPlayerKnockbackStrength = 25; unsigned int configStayInLevelAfterStar = 0; bool configNametags = true; unsigned int configBouncyLevelBounds = 0; unsigned int configNetworkSystem = 0; char configPlayerName[MAX_PLAYER_STRING] = ""; unsigned int configPlayerModel = 0; bool configMenuStaffRoll = true; unsigned int configMenuLevel = 0; bool configMenuSound = false; bool configMenuRandom = false; bool configMenuDemos = false; struct PlayerPalette configPlayerPalette = {{{ 0x00, 0x00, 0xff }, { 0xff, 0x00, 0x00 }, { 0xff, 0xff, 0xff }, { 0x72, 0x1c, 0x0e }, { 0x73, 0x06, 0x00 }, { 0xfe, 0xc1, 0x79 }, { 0xff, 0x00, 0x00 }}}; struct PlayerPalette configCustomPalette = {{{ 0x00, 0x00, 0xff }, { 0xff, 0x00, 0x00 }, { 0xff, 0xff, 0xff }, { 0x72, 0x1c, 0x0e }, { 0x73, 0x06, 0x00 }, { 0xfe, 0xc1, 0x79 }, { 0xff, 0x00, 0x00 }}}; bool configShowFPS = false; bool configUncappedFramerate = false; unsigned int configFrameLimit = 144; unsigned int configDrawDistance = 4; bool configDisablePopups = false; bool configUseStandardKeyBindingsChat = false; bool configLuaProfiler = false; #ifdef DEVELOPMENT bool configCtxProfiler = false; #endif unsigned int configInterpolationMode = 1; unsigned int configGamepadNumber = 0; bool configBackgroundGamepad = true; bool configDisableGamepads = false; bool configDebugPrint = false; bool configDebugInfo = false; bool configDebugError = false; char configLanguage[MAX_CONFIG_STRING] = ""; bool configForce4By3 = false; char configCoopNetIp[MAX_CONFIG_STRING] = DEFAULT_COOPNET_IP; unsigned int configCoopNetPort = DEFAULT_COOPNET_PORT; char configPassword[MAX_CONFIG_STRING] = ""; char configDestId[MAX_CONFIG_STRING] = "0"; bool configFadeoutDistantSounds = false; unsigned int configDjuiTheme = DJUI_THEME_DARK; bool configDjuiThemeCenter = true; unsigned int configDjuiThemeFont = FONT_NORMAL; unsigned int configDjuiScale = 0; bool configGlobalPlayerModels = true; char configLastVersion[MAX_CONFIG_STRING] = ""; 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 = "msaa", .type = CONFIG_TYPE_UINT, .uintValue = &configWindow.msaa}, {.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_x", .type = CONFIG_TYPE_BIND, .uintValue = configKeyX}, {.name = "key_y", .type = CONFIG_TYPE_BIND, .uintValue = configKeyY}, {.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 = "key_chat", .type = CONFIG_TYPE_BIND, .uintValue = configKeyChat}, {.name = "key_playerlist", .type = CONFIG_TYPE_BIND, .uintValue = configKeyPlayerList}, {.name = "key_dup", .type = CONFIG_TYPE_BIND, .uintValue = configKeyDUp}, {.name = "key_ddown", .type = CONFIG_TYPE_BIND, .uintValue = configKeyDDown}, {.name = "key_dleft", .type = CONFIG_TYPE_BIND, .uintValue = configKeyDLeft}, {.name = "key_dright", .type = CONFIG_TYPE_BIND, .uintValue = configKeyDRight}, {.name = "key_prev", .type = CONFIG_TYPE_BIND, .uintValue = configKeyPrevPage}, {.name = "key_next", .type = CONFIG_TYPE_BIND, .uintValue = configKeyNextPage}, {.name = "key_disconnect", .type = CONFIG_TYPE_BIND, .uintValue = configKeyDisconnect}, {.name = "key_console", .type = CONFIG_TYPE_BIND, .uintValue = configKeyConsole}, {.name = "stick_deadzone", .type = CONFIG_TYPE_UINT, .uintValue = &configStickDeadzone}, {.name = "rumble_strength", .type = CONFIG_TYPE_UINT, .uintValue = &configRumbleStrength}, {.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}, {.name = "skip_intro", .type = CONFIG_TYPE_BOOL, .boolValue = &configSkipIntro}, {.name = "pause_anywhere", .type = CONFIG_TYPE_BOOL, .boolValue = &configPauseAnywhere}, // debug {.name = "debug_offset", .type = CONFIG_TYPE_U64 , .u64Value = &gPcDebug.bhvOffset}, {.name = "debug_tags", .type = CONFIG_TYPE_U64 , .u64Value = gPcDebug.tags}, // coop-specific {.name = "show_fps", .type = CONFIG_TYPE_BOOL , .boolValue = &configShowFPS}, {.name = "uncapped_framerate", .type = CONFIG_TYPE_BOOL , .boolValue = &configUncappedFramerate}, {.name = "frame_limit" , .type = CONFIG_TYPE_UINT , .uintValue = &configFrameLimit}, {.name = "amount_of_players", .type = CONFIG_TYPE_UINT , .uintValue = &configAmountofPlayers}, {.name = "bubble_death", .type = CONFIG_TYPE_BOOL , .boolValue = &configBubbleDeath}, {.name = "coop_draw_distance", .type = CONFIG_TYPE_UINT , .uintValue = &configDrawDistance}, {.name = "coop_host_port", .type = CONFIG_TYPE_UINT , .uintValue = &configHostPort}, {.name = "coop_host_save_slot", .type = CONFIG_TYPE_UINT , .uintValue = &configHostSaveSlot}, {.name = "coop_join_ip", .type = CONFIG_TYPE_STRING, .stringValue = (char*)&configJoinIp, .maxStringLength = MAX_CONFIG_STRING}, {.name = "coop_join_port", .type = CONFIG_TYPE_UINT , .uintValue = &configJoinPort}, {.name = "coop_network_system", .type = CONFIG_TYPE_UINT , .uintValue = &configNetworkSystem}, {.name = "coop_player_interaction", .type = CONFIG_TYPE_UINT , .uintValue = &configPlayerInteraction}, {.name = "coop_player_knockback_strength", .type = CONFIG_TYPE_UINT , .uintValue = &configPlayerKnockbackStrength}, {.name = "coop_nametags", .type = CONFIG_TYPE_BOOL , .boolValue = &configNametags}, {.name = "coop_bouncy_bounds", .type = CONFIG_TYPE_UINT , .uintValue = &configBouncyLevelBounds}, {.name = "coop_player_model", .type = CONFIG_TYPE_UINT , .uintValue = &configPlayerModel}, {.name = "coop_player_name", .type = CONFIG_TYPE_STRING, .stringValue = (char*)&configPlayerName, .maxStringLength = MAX_PLAYER_STRING}, {.name = "coop_menu_staff_roll", .type = CONFIG_TYPE_BOOL , .boolValue = &configMenuStaffRoll}, {.name = "coop_menu_level", .type = CONFIG_TYPE_UINT , .uintValue = &configMenuLevel}, {.name = "coop_menu_sound", .type = CONFIG_TYPE_BOOL , .boolValue = &configMenuSound}, {.name = "coop_menu_random", .type = CONFIG_TYPE_BOOL , .boolValue = &configMenuRandom}, {.name = "coop_menu_demos", .type = CONFIG_TYPE_BOOL , .boolValue = &configMenuDemos}, {.name = "coop_player_palette_pants", .type = CONFIG_TYPE_COLOR , .colorValue = &configPlayerPalette.parts[PANTS]}, {.name = "coop_player_palette_shirt", .type = CONFIG_TYPE_COLOR , .colorValue = &configPlayerPalette.parts[SHIRT]}, {.name = "coop_player_palette_gloves", .type = CONFIG_TYPE_COLOR , .colorValue = &configPlayerPalette.parts[GLOVES]}, {.name = "coop_player_palette_shoes", .type = CONFIG_TYPE_COLOR , .colorValue = &configPlayerPalette.parts[SHOES]}, {.name = "coop_player_palette_hair", .type = CONFIG_TYPE_COLOR , .colorValue = &configPlayerPalette.parts[HAIR]}, {.name = "coop_player_palette_skin", .type = CONFIG_TYPE_COLOR , .colorValue = &configPlayerPalette.parts[SKIN]}, {.name = "coop_player_palette_cap", .type = CONFIG_TYPE_COLOR , .colorValue = &configPlayerPalette.parts[CAP]}, {.name = "coop_custom_palette_pants", .type = CONFIG_TYPE_COLOR , .colorValue = &configCustomPalette.parts[PANTS]}, {.name = "coop_custom_palette_shirt", .type = CONFIG_TYPE_COLOR , .colorValue = &configCustomPalette.parts[SHIRT]}, {.name = "coop_custom_palette_gloves", .type = CONFIG_TYPE_COLOR , .colorValue = &configCustomPalette.parts[GLOVES]}, {.name = "coop_custom_palette_shoes", .type = CONFIG_TYPE_COLOR , .colorValue = &configCustomPalette.parts[SHOES]}, {.name = "coop_custom_palette_hair", .type = CONFIG_TYPE_COLOR , .colorValue = &configCustomPalette.parts[HAIR]}, {.name = "coop_custom_palette_skin", .type = CONFIG_TYPE_COLOR , .colorValue = &configCustomPalette.parts[SKIN]}, {.name = "coop_custom_palette_cap", .type = CONFIG_TYPE_COLOR , .colorValue = &configCustomPalette.parts[CAP]}, {.name = "coop_stay_in_level_after_star", .type = CONFIG_TYPE_UINT , .uintValue = &configStayInLevelAfterStar}, {.name = "coop_global_player_models", .type = CONFIG_TYPE_BOOL , .boolValue = &configGlobalPlayerModels}, {.name = "disable_popups", .type = CONFIG_TYPE_BOOL , .boolValue = &configDisablePopups}, {.name = "use_standard_key_bindings_chat", .type = CONFIG_TYPE_BOOL , .boolValue = &configUseStandardKeyBindingsChat}, {.name = "lua_profiler", .type = CONFIG_TYPE_BOOL , .boolValue = &configLuaProfiler}, #ifdef DEVELOPMENT {.name = "ctx_profiler", .type = CONFIG_TYPE_BOOL , .boolValue = &configCtxProfiler}, #endif {.name = "interpolation_mode", .type = CONFIG_TYPE_UINT , .uintValue = &configInterpolationMode}, {.name = "gamepad_number", .type = CONFIG_TYPE_UINT , .uintValue = &configGamepadNumber}, {.name = "background_gamepad", .type = CONFIG_TYPE_UINT , .boolValue = &configBackgroundGamepad}, {.name = "disable_gamepads", .type = CONFIG_TYPE_BOOL , .boolValue = &configDisableGamepads}, {.name = "debug_print", .type = CONFIG_TYPE_BOOL , .boolValue = &configDebugPrint}, {.name = "debug_info", .type = CONFIG_TYPE_BOOL , .boolValue = &configDebugInfo}, {.name = "debug_error", .type = CONFIG_TYPE_BOOL , .boolValue = &configDebugError}, {.name = "language", .type = CONFIG_TYPE_STRING, .stringValue = (char*)&configLanguage, .maxStringLength = MAX_CONFIG_STRING}, {.name = "force_4by3", .type = CONFIG_TYPE_BOOL , .boolValue = &configForce4By3}, {.name = "coopnet_ip", .type = CONFIG_TYPE_STRING, .stringValue = (char*)&configCoopNetIp, .maxStringLength = MAX_CONFIG_STRING}, {.name = "coopnet_port", .type = CONFIG_TYPE_UINT , .uintValue = &configCoopNetPort}, {.name = "coopnet_password", .type = CONFIG_TYPE_STRING, .stringValue = (char*)&configPassword, .maxStringLength = MAX_CONFIG_STRING}, {.name = "coopnet_dest", .type = CONFIG_TYPE_STRING, .stringValue = (char*)&configDestId, .maxStringLength = MAX_CONFIG_STRING}, {.name = "fade_distant_sounds", .type = CONFIG_TYPE_BOOL , .boolValue = &configFadeoutDistantSounds}, {.name = "djui_theme", .type = CONFIG_TYPE_UINT , .uintValue = &configDjuiTheme}, {.name = "djui_theme_center", .type = CONFIG_TYPE_BOOL , .boolValue = &configDjuiThemeCenter}, {.name = "djui_theme_font", .type = CONFIG_TYPE_UINT , .uintValue = &configDjuiThemeFont}, {.name = "djui_scale", .type = CONFIG_TYPE_UINT , .uintValue = &configDjuiScale}, {.name = "last_version", .type = CONFIG_TYPE_STRING, .stringValue = (char*)&configLastVersion, .maxStringLength = MAX_CONFIG_STRING} }; // FunctionConfigOption functions struct QueuedFile { char* path; struct QueuedFile *next; }; static struct QueuedFile *sQueuedEnableModsHead = NULL; void enable_queued_mods(void) { while (sQueuedEnableModsHead) { struct QueuedFile *next = sQueuedEnableModsHead->next; mods_enable(sQueuedEnableModsHead->path); free(sQueuedEnableModsHead->path); free(sQueuedEnableModsHead); sQueuedEnableModsHead = next; } } static void enable_mod_read(char** tokens, UNUSED int numTokens) { char combined[256] = { 0 }; for (int i = 1; i < numTokens; i++) { if (i != 1) { strncat(combined, " ", 255); } strncat(combined, tokens[i], 255); } struct QueuedFile* queued = malloc(sizeof(struct QueuedFile)); queued->path = strdup(combined); queued->next = NULL; if (!sQueuedEnableModsHead) { sQueuedEnableModsHead = queued; } else { struct QueuedFile* tail = sQueuedEnableModsHead; while (tail->next) { tail = tail->next; } tail->next = queued; } } static void enable_mod_write(FILE* file) { for (unsigned int i = 0; i < gLocalMods.entryCount; i++) { struct Mod* mod = gLocalMods.entries[i]; if (mod == NULL) { continue; } if (!mod->enabled) { continue; } fprintf(file, "%s %s\n", "enable-mod:", mod->relativePath); } } static void ban_read(char** tokens, UNUSED int numTokens) { ban_list_add(tokens[1], true); } static void ban_write(FILE* file) { for (unsigned int i = 0; i < gBanCount; i++) { if (gBanAddresses == NULL) { break; } if (gBanAddresses[i] == NULL) { continue; } if (!gBanPerm[i]) { continue; } fprintf(file, "%s %s\n", "ban:", gBanAddresses[i]); } } static void moderator_read(char** tokens, UNUSED int numTokens) { moderator_list_add(tokens[1], true); } static void moderator_write(FILE* file) { for (unsigned int i = 0; i < gModeratorCount; i++) { if (gModeratorAddresses == NULL) { break; } if (gModeratorAddresses[i] == NULL) { continue; } if (!gModerator[i]) { continue; } fprintf(file, "%s %s\n", "moderator:", gModeratorAddresses[i]); } } static struct QueuedFile *sQueuedEnableDynosPacksHead = NULL; void enable_queued_dynos_packs(void) { while (sQueuedEnableDynosPacksHead) { int packCount = dynos_pack_get_count(); const char *path = sQueuedEnableDynosPacksHead->path; for (int i = 0; i < packCount; i++) { const char* pack = dynos_pack_get_name(i); if (!strcmp(path, pack)) { dynos_pack_set_enabled(i, true); break; } } struct QueuedFile *next = sQueuedEnableDynosPacksHead->next; free(sQueuedEnableDynosPacksHead->path); free(sQueuedEnableDynosPacksHead); sQueuedEnableDynosPacksHead = next; } } static void dynos_pack_read(char** tokens, int numTokens) { if (numTokens < 2) { return; } if (strcmp(tokens[numTokens-1], "false") == 0) { return; } // Only accept enabled packs. Default is disabled. (old coop config compatibility) char fullPackName[256] = { 0 }; for (int i = 1; i < numTokens; i++) { if (i == numTokens - 1 && strcmp(tokens[i], "true") == 0) { break; } // old coop config compatibility if (i != 1) { strncat(fullPackName, " ", 255); } strncat(fullPackName, tokens[i], 255); } struct QueuedFile* queued = malloc(sizeof(struct QueuedFile)); queued->path = strdup(fullPackName); queued->next = NULL; if (!sQueuedEnableDynosPacksHead) { sQueuedEnableDynosPacksHead = queued; } else { struct QueuedFile* tail = sQueuedEnableDynosPacksHead; while (tail->next) { tail = tail->next; } tail->next = queued; } } static void dynos_pack_write(FILE* file) { int packCount = dynos_pack_get_count(); for (int i = 0; i < packCount; i++) { if (dynos_pack_get_enabled(i)) { const char* pack = dynos_pack_get_name(i); fprintf(file, "%s %s\n", "dynos-pack:", pack); } } } static void save_name_read(char** tokens, int numTokens) { if (numTokens < 2) { return; } char fullSaveName[MAX_SAVE_NAME_STRING] = { 0 }; int index = 0; for (int i = 1; i < numTokens; i++) { if (i == 1) { index = atoi(tokens[i]); } else { if (i > 2) { strncat(fullSaveName, " ", MAX_SAVE_NAME_STRING - 1); } strncat(fullSaveName, tokens[i], MAX_SAVE_NAME_STRING - 1); } } strncpy(configSaveNames[index], fullSaveName, MAX_SAVE_NAME_STRING); } static void save_name_write(FILE* file) { for (int i = 0; i < NUM_SAVE_FILES; i++) { fprintf(file, "%s %d %s\n", "save-name:", i, configSaveNames[i]); } } static const struct FunctionConfigOption functionOptions[] = { { .name = "enable-mod:", .read = enable_mod_read, .write = enable_mod_write }, { .name = "ban:", .read = ban_read, .write = ban_write }, { .name = "moderator:", .read = moderator_read, .write = moderator_write }, { .name = "dynos-pack:", .read = dynos_pack_read, .write = dynos_pack_write }, { .name = "save-name:", .read = save_name_read, .write = save_name_write } }; // 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, bool* error) { char *buffer; size_t bufferSize = 8; size_t offset = 0; // offset in buffer to write *error = false; buffer = malloc(bufferSize); buffer[0] = '\0'; 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); if (offset <= 0) { LOG_ERROR("Configfile offset <= 0"); *error = true; return NULL; } // 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; } const char *configfile_backup_name(void) { return CONFIGFILE_BACKUP; } // Loads the config file specified by 'filename' static void configfile_load_internal(const char *filename, bool* error) { fs_file_t *file; char *line; unsigned int temp; *error = false; #ifdef DEVELOPMENT printf("Loading configuration from '%s'\n", filename); #endif 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, error)) != NULL && !*error) { char *p = line; char *tokens[20]; int numTokens; // skip whitespace while (isspace(*p)) p++; // skip comment or empty line if (!*p || *p == '#') { free(line); continue; } numTokens = tokenize_string(p, sizeof(tokens) / sizeof(tokens[0]), tokens); if (numTokens != 0) { if (numTokens >= 2) { const struct ConfigOption *option = NULL; // find functionOption for (unsigned int i = 0; i < ARRAY_LEN(functionOptions); i++) { if (strcmp(tokens[0], functionOptions[i].name) == 0) { functionOptions[i].read(tokens, numTokens); goto NEXT_OPTION; } } // find option 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) { #ifdef DEVELOPMENT printf("unknown option '%s'\n", tokens[0]); #endif } 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', option->maxStringLength); snprintf(option->stringValue, option->maxStringLength, "%s", tokens[1]); break; case CONFIG_TYPE_U64: sscanf(tokens[1], "%llu", option->u64Value); break; case CONFIG_TYPE_COLOR: for (int i = 0; i < 3 && i < numTokens - 1; ++i) { sscanf(tokens[i + 1], "%x", &temp); (*option->colorValue)[i] = temp; } break; default: LOG_ERROR("Configfile read bad type '%d': %s", (int)option->type, line); goto NEXT_OPTION; } #ifdef DEVELOPMENT printf("option: '%s', value:", tokens[0]); for (int i = 1; i < numTokens; ++i) printf(" '%s'", tokens[i]); printf("\n"); #endif } } else { #ifdef DEVELOPMENT printf("error: expected value\n"); #endif } } NEXT_OPTION: free(line); line = NULL; } if (line) { free(line); } fs_close(file); if (configFrameLimit < 30) { configFrameLimit = 30; } if (configFrameLimit > 3000) { configFrameLimit = 3000; } if (configPlayerModel >= CT_MAX) { configPlayerModel = 0; } if (configDjuiTheme >= DJUI_THEME_MAX) { configDjuiTheme = 0; } if (!strcmp(configLastVersion, "")) { strncpy(configLastVersion, get_version(), MAX_CONFIG_STRING); } #ifndef COOPNET configNetworkSystem = NS_SOCKET; #endif } void configfile_load(void) { bool configReadError = false; #ifdef DEVELOPMENT configfile_load_internal(configfile_name(), &configReadError); #else configfile_load_internal(configfile_name(), &configReadError); if (configReadError) { configfile_load_internal(configfile_backup_name(), &configReadError); } else { configfile_save(configfile_backup_name()); } #endif } // Writes the config file to 'filename' void configfile_save(const char *filename) { FILE *file; file = fopen(fs_get_write_path(filename), "w"); if (file == NULL) { // error return; } printf("Saving configuration to '%s'\n", filename); 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; case CONFIG_TYPE_U64: fprintf(file, "%s %llu\n", option->name, *option->u64Value); break; case CONFIG_TYPE_COLOR: fprintf(file, "%s %02x %02x %02x\n", option->name, (*option->colorValue)[0], (*option->colorValue)[1], (*option->colorValue)[2]); break; default: LOG_ERROR("Configfile wrote bad type '%d': %s", (int)option->type, option->name); break; } } // save function options for (unsigned int i = 0; i < ARRAY_LEN(functionOptions); i++) { functionOptions[i].write(file); } fclose(file); }