+ si->internal.default_graphics_set = getStringCopy(GFX_CLASSIC_SUBDIR);
+ si->internal.default_sounds_set = getStringCopy(SND_CLASSIC_SUBDIR);
+ si->internal.default_music_set = getStringCopy(MUS_CLASSIC_SUBDIR);
+
+ si->internal.fallback_graphics_file = getStringCopy(UNDEFINED_FILENAME);
+ si->internal.fallback_sounds_file = getStringCopy(UNDEFINED_FILENAME);
+ si->internal.fallback_music_file = getStringCopy(UNDEFINED_FILENAME);
+
+ si->internal.default_level_series = getStringCopy(UNDEFINED_LEVELSET);
+ si->internal.choose_from_top_leveldir = FALSE;
+ si->internal.show_scaling_in_title = TRUE;
+
+ si->internal.default_window_width = WIN_XSIZE_DEFAULT;
+ si->internal.default_window_height = WIN_YSIZE_DEFAULT;
+
+ si->debug.frame_delay[0] = DEFAULT_FRAME_DELAY_0;
+ si->debug.frame_delay[1] = DEFAULT_FRAME_DELAY_1;
+ si->debug.frame_delay[2] = DEFAULT_FRAME_DELAY_2;
+ si->debug.frame_delay[3] = DEFAULT_FRAME_DELAY_3;
+ si->debug.frame_delay[4] = DEFAULT_FRAME_DELAY_4;
+ si->debug.frame_delay[5] = DEFAULT_FRAME_DELAY_5;
+ si->debug.frame_delay[6] = DEFAULT_FRAME_DELAY_6;
+ si->debug.frame_delay[7] = DEFAULT_FRAME_DELAY_7;
+ si->debug.frame_delay[8] = DEFAULT_FRAME_DELAY_8;
+ si->debug.frame_delay[9] = DEFAULT_FRAME_DELAY_9;
+
+ si->debug.frame_delay_key[0] = DEFAULT_KEY_FRAME_DELAY_0;
+ si->debug.frame_delay_key[1] = DEFAULT_KEY_FRAME_DELAY_1;
+ si->debug.frame_delay_key[2] = DEFAULT_KEY_FRAME_DELAY_2;
+ si->debug.frame_delay_key[3] = DEFAULT_KEY_FRAME_DELAY_3;
+ si->debug.frame_delay_key[4] = DEFAULT_KEY_FRAME_DELAY_4;
+ si->debug.frame_delay_key[5] = DEFAULT_KEY_FRAME_DELAY_5;
+ si->debug.frame_delay_key[6] = DEFAULT_KEY_FRAME_DELAY_6;
+ si->debug.frame_delay_key[7] = DEFAULT_KEY_FRAME_DELAY_7;
+ si->debug.frame_delay_key[8] = DEFAULT_KEY_FRAME_DELAY_8;
+ si->debug.frame_delay_key[9] = DEFAULT_KEY_FRAME_DELAY_9;
+
+ si->debug.frame_delay_use_mod_key = DEFAULT_FRAME_DELAY_USE_MOD_KEY;
+ si->debug.frame_delay_game_only = DEFAULT_FRAME_DELAY_GAME_ONLY;
+
+ si->debug.show_frames_per_second = FALSE;
+
+ si->options.verbose = FALSE;
+
+#if defined(PLATFORM_ANDROID)
+ si->fullscreen = TRUE;
+#endif
+}
+
+static void setSetupInfoToDefaults_AutoSetup(struct SetupInfo *si)
+{
+ si->auto_setup.editor_zoom_tilesize = MINI_TILESIZE;
+}
+
+static void setSetupInfoToDefaults_EditorCascade(struct SetupInfo *si)
+{
+ si->editor_cascade.el_bd = TRUE;
+ si->editor_cascade.el_em = TRUE;
+ si->editor_cascade.el_emc = TRUE;
+ si->editor_cascade.el_rnd = TRUE;
+ si->editor_cascade.el_sb = TRUE;
+ si->editor_cascade.el_sp = TRUE;
+ si->editor_cascade.el_dc = TRUE;
+ si->editor_cascade.el_dx = TRUE;
+
+ si->editor_cascade.el_mm = TRUE;
+ si->editor_cascade.el_df = TRUE;
+
+ si->editor_cascade.el_chars = FALSE;
+ si->editor_cascade.el_steel_chars = FALSE;
+ si->editor_cascade.el_ce = FALSE;
+ si->editor_cascade.el_ge = FALSE;
+ si->editor_cascade.el_ref = FALSE;
+ si->editor_cascade.el_user = FALSE;
+ si->editor_cascade.el_dynamic = FALSE;
+}
+
+#define MAX_HIDE_SETUP_TOKEN_SIZE 20
+
+static char *getHideSetupToken(void *setup_value)
+{
+ static char hide_setup_token[MAX_HIDE_SETUP_TOKEN_SIZE];
+
+ if (setup_value != NULL)
+ snprintf(hide_setup_token, MAX_HIDE_SETUP_TOKEN_SIZE, "%p", setup_value);
+
+ return hide_setup_token;
+}
+
+void setHideSetupEntry(void *setup_value)
+{
+ char *hide_setup_token = getHideSetupToken(setup_value);
+
+ if (setup_value != NULL)
+ setHashEntry(hide_setup_hash, hide_setup_token, "");
+}
+
+boolean hideSetupEntry(void *setup_value)
+{
+ char *hide_setup_token = getHideSetupToken(setup_value);
+
+ return (setup_value != NULL &&
+ getHashEntry(hide_setup_hash, hide_setup_token) != NULL);
+}
+
+static void setSetupInfoFromTokenText(SetupFileHash *setup_file_hash,
+ struct TokenInfo *token_info,
+ int token_nr, char *token_text)
+{
+ char *token_hide_text = getStringCat2(token_text, ".hide");
+ char *token_hide_value = getHashEntry(setup_file_hash, token_hide_text);
+
+ // set the value of this setup option in the setup option structure
+ setSetupInfo(token_info, token_nr, getHashEntry(setup_file_hash, token_text));
+
+ // check if this setup option should be hidden in the setup menu
+ if (token_hide_value != NULL && get_boolean_from_string(token_hide_value))
+ setHideSetupEntry(token_info[token_nr].value);
+}
+
+static void setSetupInfoFromTokenInfo(SetupFileHash *setup_file_hash,
+ struct TokenInfo *token_info,
+ int token_nr)
+{
+ setSetupInfoFromTokenText(setup_file_hash, token_info, token_nr,
+ token_info[token_nr].text);
+}
+
+static void decodeSetupFileHash(SetupFileHash *setup_file_hash)
+{
+ int i, pnr;
+
+ if (!setup_file_hash)
+ return;
+
+ if (hide_setup_hash == NULL)
+ hide_setup_hash = newSetupFileHash();
+
+ for (i = 0; i < ARRAY_SIZE(global_setup_tokens); i++)
+ setSetupInfoFromTokenInfo(setup_file_hash, global_setup_tokens, i);
+
+ setup.touch.grid_initialized = TRUE;
+ for (i = 0; i < 2; i++)
+ {
+ int grid_xsize = setup.touch.grid_xsize[i];
+ int grid_ysize = setup.touch.grid_ysize[i];
+ int x, y;
+
+ // if virtual buttons are not loaded from setup file, repeat initializing
+ // virtual buttons grid with default values later when video is initialized
+ if (grid_xsize == -1 ||
+ grid_ysize == -1)
+ {
+ setup.touch.grid_initialized = FALSE;
+
+ continue;
+ }
+
+ for (y = 0; y < grid_ysize; y++)
+ {
+ char token_string[MAX_LINE_LEN];
+
+ sprintf(token_string, "touch.virtual_buttons.%d.%02d", i, y);
+
+ char *value_string = getHashEntry(setup_file_hash, token_string);
+
+ if (value_string == NULL)
+ continue;
+
+ for (x = 0; x < grid_xsize; x++)
+ {
+ char c = value_string[x];
+
+ setup.touch.grid_button[i][x][y] =
+ (c == '.' ? CHAR_GRID_BUTTON_NONE : c);
+ }
+ }
+ }
+
+ for (i = 0; i < ARRAY_SIZE(editor_setup_tokens); i++)
+ setSetupInfoFromTokenInfo(setup_file_hash, editor_setup_tokens, i);
+
+ for (i = 0; i < ARRAY_SIZE(shortcut_setup_tokens); i++)
+ setSetupInfoFromTokenInfo(setup_file_hash, shortcut_setup_tokens, i);
+
+ for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
+ {
+ char prefix[30];
+
+ sprintf(prefix, "%s%d", TOKEN_STR_PLAYER_PREFIX, pnr + 1);
+
+ setup_input = setup.input[pnr];
+ for (i = 0; i < ARRAY_SIZE(player_setup_tokens); i++)
+ {
+ char full_token[100];
+
+ sprintf(full_token, "%s%s", prefix, player_setup_tokens[i].text);
+ setSetupInfoFromTokenText(setup_file_hash, player_setup_tokens, i,
+ full_token);
+ }
+ setup.input[pnr] = setup_input;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(system_setup_tokens); i++)
+ setSetupInfoFromTokenInfo(setup_file_hash, system_setup_tokens, i);
+
+ for (i = 0; i < ARRAY_SIZE(internal_setup_tokens); i++)
+ setSetupInfoFromTokenInfo(setup_file_hash, internal_setup_tokens, i);
+
+ for (i = 0; i < ARRAY_SIZE(debug_setup_tokens); i++)
+ setSetupInfoFromTokenInfo(setup_file_hash, debug_setup_tokens, i);
+
+ for (i = 0; i < ARRAY_SIZE(options_setup_tokens); i++)
+ setSetupInfoFromTokenInfo(setup_file_hash, options_setup_tokens, i);
+
+ setHideRelatedSetupEntries();
+}
+
+static void decodeSetupFileHash_AutoSetup(SetupFileHash *setup_file_hash)
+{
+ int i;
+
+ if (!setup_file_hash)
+ return;
+
+ for (i = 0; i < ARRAY_SIZE(auto_setup_tokens); i++)
+ setSetupInfo(auto_setup_tokens, i,
+ getHashEntry(setup_file_hash,
+ auto_setup_tokens[i].text));
+}
+
+static void decodeSetupFileHash_EditorCascade(SetupFileHash *setup_file_hash)
+{
+ int i;
+
+ if (!setup_file_hash)
+ return;
+
+ for (i = 0; i < ARRAY_SIZE(editor_cascade_setup_tokens); i++)
+ setSetupInfo(editor_cascade_setup_tokens, i,
+ getHashEntry(setup_file_hash,
+ editor_cascade_setup_tokens[i].text));
+}
+
+void LoadSetupFromFilename(char *filename)
+{
+ SetupFileHash *setup_file_hash = loadSetupFileHash(filename);
+
+ if (setup_file_hash)
+ {
+ decodeSetupFileHash(setup_file_hash);
+
+ freeSetupFileHash(setup_file_hash);
+ }
+ else
+ {
+ Error(ERR_DEBUG, "using default setup values");
+ }
+}
+
+static void LoadSetup_SpecialPostProcessing(void)
+{
+ char *player_name_new;
+
+ // needed to work around problems with fixed length strings
+ player_name_new = get_corrected_login_name(setup.player_name);
+ free(setup.player_name);
+ setup.player_name = player_name_new;
+
+ // "scroll_delay: on(3) / off(0)" was replaced by scroll delay value
+ if (setup.scroll_delay == FALSE)
+ {
+ setup.scroll_delay_value = MIN_SCROLL_DELAY;
+ setup.scroll_delay = TRUE; // now always "on"
+ }
+
+ // make sure that scroll delay value stays inside valid range
+ setup.scroll_delay_value =
+ MIN(MAX(MIN_SCROLL_DELAY, setup.scroll_delay_value), MAX_SCROLL_DELAY);
+}
+
+void LoadSetup(void)
+{
+ char *filename;
+
+ // always start with reliable default values
+ setSetupInfoToDefaults(&setup);
+
+ // try to load setup values from default setup file
+ filename = getDefaultSetupFilename();
+
+ if (fileExists(filename))
+ LoadSetupFromFilename(filename);
+
+ // try to load setup values from user setup file
+ filename = getSetupFilename();
+
+ LoadSetupFromFilename(filename);
+
+ LoadSetup_SpecialPostProcessing();
+}
+
+void LoadSetup_AutoSetup(void)
+{
+ char *filename = getPath2(getSetupDir(), AUTOSETUP_FILENAME);
+ SetupFileHash *setup_file_hash = NULL;
+
+ // always start with reliable default values
+ setSetupInfoToDefaults_AutoSetup(&setup);
+
+ setup_file_hash = loadSetupFileHash(filename);
+
+ if (setup_file_hash)
+ {
+ decodeSetupFileHash_AutoSetup(setup_file_hash);
+
+ freeSetupFileHash(setup_file_hash);
+ }
+
+ free(filename);
+}
+
+void LoadSetup_EditorCascade(void)
+{
+ char *filename = getPath2(getSetupDir(), EDITORCASCADE_FILENAME);
+ SetupFileHash *setup_file_hash = NULL;
+
+ // always start with reliable default values
+ setSetupInfoToDefaults_EditorCascade(&setup);
+
+ setup_file_hash = loadSetupFileHash(filename);
+
+ if (setup_file_hash)
+ {
+ decodeSetupFileHash_EditorCascade(setup_file_hash);
+
+ freeSetupFileHash(setup_file_hash);
+ }
+
+ free(filename);
+}
+
+static void addGameControllerMappingToHash(SetupFileHash *mappings_hash,
+ char *mapping_line)
+{
+ char mapping_guid[MAX_LINE_LEN];
+ char *mapping_start, *mapping_end;
+
+ // get GUID from game controller mapping line: copy complete line
+ strncpy(mapping_guid, mapping_line, MAX_LINE_LEN - 1);
+ mapping_guid[MAX_LINE_LEN - 1] = '\0';
+
+ // get GUID from game controller mapping line: cut after GUID part
+ mapping_start = strchr(mapping_guid, ',');
+ if (mapping_start != NULL)
+ *mapping_start = '\0';
+
+ // cut newline from game controller mapping line
+ mapping_end = strchr(mapping_line, '\n');
+ if (mapping_end != NULL)
+ *mapping_end = '\0';
+
+ // add mapping entry to game controller mappings hash
+ setHashEntry(mappings_hash, mapping_guid, mapping_line);
+}
+
+static void LoadSetup_ReadGameControllerMappings(SetupFileHash *mappings_hash,
+ char *filename)
+{
+ FILE *file;
+
+ if (!(file = fopen(filename, MODE_READ)))
+ {
+ Error(ERR_WARN, "cannot read game controller mappings file '%s'", filename);
+
+ return;
+ }
+
+ while (!feof(file))
+ {
+ char line[MAX_LINE_LEN];
+
+ if (!fgets(line, MAX_LINE_LEN, file))
+ break;
+
+ addGameControllerMappingToHash(mappings_hash, line);
+ }
+
+ fclose(file);
+}
+
+void SaveSetup(void)
+{
+ char *filename = getSetupFilename();
+ FILE *file;
+ int i, pnr;
+
+ InitUserDataDirectory();
+
+ if (!(file = fopen(filename, MODE_WRITE)))
+ {
+ Error(ERR_WARN, "cannot write setup file '%s'", filename);
+ return;
+ }
+
+ fprintFileHeader(file, SETUP_FILENAME);
+
+ for (i = 0; i < ARRAY_SIZE(global_setup_tokens); i++)
+ {
+ // just to make things nicer :)
+ if (global_setup_tokens[i].value == &setup.sound ||
+ global_setup_tokens[i].value == &setup.graphics_set ||
+ global_setup_tokens[i].value == &setup.volume_simple ||
+ global_setup_tokens[i].value == &setup.network_mode ||
+ global_setup_tokens[i].value == &setup.touch.control_type ||
+ global_setup_tokens[i].value == &setup.touch.grid_xsize[0] ||
+ global_setup_tokens[i].value == &setup.touch.grid_xsize[1])
+ fprintf(file, "\n");
+
+ fprintf(file, "%s\n", getSetupLine(global_setup_tokens, "", i));
+ }
+
+ for (i = 0; i < 2; i++)
+ {
+ int grid_xsize = setup.touch.grid_xsize[i];
+ int grid_ysize = setup.touch.grid_ysize[i];
+ int x, y;
+
+ fprintf(file, "\n");
+
+ for (y = 0; y < grid_ysize; y++)
+ {
+ char token_string[MAX_LINE_LEN];
+ char value_string[MAX_LINE_LEN];
+
+ sprintf(token_string, "touch.virtual_buttons.%d.%02d", i, y);
+
+ for (x = 0; x < grid_xsize; x++)
+ {
+ char c = setup.touch.grid_button[i][x][y];
+
+ value_string[x] = (c == CHAR_GRID_BUTTON_NONE ? '.' : c);
+ }
+
+ value_string[grid_xsize] = '\0';
+
+ fprintf(file, "%s\n", getFormattedSetupEntry(token_string, value_string));
+ }
+ }
+
+ fprintf(file, "\n");
+ for (i = 0; i < ARRAY_SIZE(editor_setup_tokens); i++)
+ fprintf(file, "%s\n", getSetupLine(editor_setup_tokens, "", i));
+
+ fprintf(file, "\n");
+ for (i = 0; i < ARRAY_SIZE(shortcut_setup_tokens); i++)
+ fprintf(file, "%s\n", getSetupLine(shortcut_setup_tokens, "", i));
+
+ for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
+ {
+ char prefix[30];
+
+ sprintf(prefix, "%s%d", TOKEN_STR_PLAYER_PREFIX, pnr + 1);
+ fprintf(file, "\n");
+
+ setup_input = setup.input[pnr];
+ for (i = 0; i < ARRAY_SIZE(player_setup_tokens); i++)
+ fprintf(file, "%s\n", getSetupLine(player_setup_tokens, prefix, i));
+ }
+
+ fprintf(file, "\n");
+ for (i = 0; i < ARRAY_SIZE(system_setup_tokens); i++)
+ fprintf(file, "%s\n", getSetupLine(system_setup_tokens, "", i));
+
+ // (internal setup values not saved to user setup file)
+
+ fprintf(file, "\n");
+ for (i = 0; i < ARRAY_SIZE(debug_setup_tokens); i++)
+ fprintf(file, "%s\n", getSetupLine(debug_setup_tokens, "", i));
+
+ fprintf(file, "\n");
+ for (i = 0; i < ARRAY_SIZE(options_setup_tokens); i++)
+ fprintf(file, "%s\n", getSetupLine(options_setup_tokens, "", i));
+
+ fclose(file);
+
+ SetFilePermissions(filename, PERMS_PRIVATE);
+}
+
+void SaveSetup_AutoSetup(void)
+{
+ char *filename = getPath2(getSetupDir(), AUTOSETUP_FILENAME);
+ FILE *file;
+ int i;
+
+ InitUserDataDirectory();
+
+ if (!(file = fopen(filename, MODE_WRITE)))
+ {
+ Error(ERR_WARN, "cannot write auto setup file '%s'", filename);
+ free(filename);
+ return;
+ }
+
+ fprintFileHeader(file, AUTOSETUP_FILENAME);