+ // cut trailing whitespaces from token
+ for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
+ if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
+ *line_ptr = '\0';
+
+ // cut leading whitespaces from value
+ for (; *value; value++)
+ if (*value != ' ' && *value != '\t')
+ break;
+
+ *token_ptr = token;
+ *value_ptr = value;
+
+ return TRUE;
+}
+
+boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
+{
+ // while the internal (old) interface does not require a token/value
+ // separator (for downwards compatibility with existing files which
+ // don't use them), it is mandatory for the external (new) interface
+
+ return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
+}
+
+static boolean loadSetupFileData(void *setup_file_data, char *filename,
+ boolean top_recursion_level, boolean is_hash)
+{
+ static SetupFileHash *include_filename_hash = NULL;
+ char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
+ char *token, *value, *line_ptr;
+ void *insert_ptr = NULL;
+ boolean read_continued_line = FALSE;
+ File *file;
+ int line_nr = 0, token_count = 0, include_count = 0;
+
+#if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
+ token_value_separator_warning = FALSE;
+#endif
+
+#if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
+ token_already_exists_warning = FALSE;
+#endif
+
+ if (!(file = openFile(filename, MODE_READ)))
+ {
+#if DEBUG_NO_CONFIG_FILE
+ Debug("setup", "cannot open configuration file '%s'", filename);
+#endif
+
+ return FALSE;
+ }
+
+ // use "insert pointer" to store list end for constant insertion complexity
+ if (!is_hash)
+ insert_ptr = setup_file_data;
+
+ // on top invocation, create hash to mark included files (to prevent loops)
+ if (top_recursion_level)
+ include_filename_hash = newSetupFileHash();
+
+ // mark this file as already included (to prevent including it again)
+ setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
+
+ while (!checkEndOfFile(file))
+ {
+ // read next line of input file
+ if (!getStringFromFile(file, line, MAX_LINE_LEN))
+ break;
+
+ // check if line was completely read and is terminated by line break
+ if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
+ line_nr++;
+
+ // cut trailing line break (this can be newline and/or carriage return)
+ for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
+ if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
+ *line_ptr = '\0';
+
+ // copy raw input line for later use (mainly debugging output)
+ strcpy(line_raw, line);
+
+ if (read_continued_line)
+ {
+ // append new line to existing line, if there is enough space
+ if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
+ strcat(previous_line, line_ptr);
+
+ strcpy(line, previous_line); // copy storage buffer to line
+
+ read_continued_line = FALSE;
+ }
+
+ // if the last character is '\', continue at next line
+ if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
+ {
+ line[strlen(line) - 1] = '\0'; // cut off trailing backslash
+ strcpy(previous_line, line); // copy line to storage buffer
+
+ read_continued_line = TRUE;
+
+ continue;
+ }
+
+ if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
+ line_raw, line_nr, FALSE))
+ continue;
+
+ if (*token)
+ {
+ if (strEqual(token, "include"))
+ {
+ if (getHashEntry(include_filename_hash, value) == NULL)
+ {
+ char *basepath = getBasePath(filename);
+ char *basename = getBaseName(value);
+ char *filename_include = getPath2(basepath, basename);
+
+ loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
+
+ free(basepath);
+ free(basename);
+ free(filename_include);
+
+ include_count++;
+ }
+ else
+ {
+ Warn("ignoring already processed file '%s'", value);
+ }
+ }
+ else
+ {
+ if (is_hash)
+ {
+#if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
+ char *old_value =
+ getHashEntry((SetupFileHash *)setup_file_data, token);
+
+ if (old_value != NULL)
+ {
+ if (!token_already_exists_warning)
+ {
+ Debug("setup", "---");
+ Debug("setup", "duplicate token(s) found in config file:");
+ Debug("setup", "- config file: '%s'", filename);
+
+ token_already_exists_warning = TRUE;
+ }
+
+ Debug("setup", "- token: '%s' (in line %d)", token, line_nr);
+ Debug("setup", " old value: '%s'", old_value);
+ Debug("setup", " new value: '%s'", value);
+ }
+#endif
+
+ setHashEntry((SetupFileHash *)setup_file_data, token, value);
+ }
+ else
+ {
+ insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
+ }
+
+ token_count++;
+ }
+ }
+ }
+
+ closeFile(file);
+
+#if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
+ if (token_value_separator_warning)
+ Debug("setup", "---");
+#endif
+
+#if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
+ if (token_already_exists_warning)
+ Debug("setup", "---");
+#endif
+
+ if (token_count == 0 && include_count == 0)
+ Warn("configuration file '%s' is empty", filename);
+
+ if (top_recursion_level)
+ freeSetupFileHash(include_filename_hash);
+
+ return TRUE;
+}
+
+static int compareSetupFileData(const void *object1, const void *object2)
+{
+ const struct ConfigInfo *entry1 = (struct ConfigInfo *)object1;
+ const struct ConfigInfo *entry2 = (struct ConfigInfo *)object2;
+
+ return strcmp(entry1->token, entry2->token);
+}
+
+static void saveSetupFileHash(SetupFileHash *hash, char *filename)
+{
+ int item_count = hashtable_count(hash);
+ int item_size = sizeof(struct ConfigInfo);
+ struct ConfigInfo *sort_array = checked_malloc(item_count * item_size);
+ FILE *file;
+ int i = 0;
+
+ // copy string pointers from hash to array
+ BEGIN_HASH_ITERATION(hash, itr)
+ {
+ sort_array[i].token = HASH_ITERATION_TOKEN(itr);
+ sort_array[i].value = HASH_ITERATION_VALUE(itr);
+
+ i++;
+
+ if (i > item_count) // should never happen
+ break;
+ }
+ END_HASH_ITERATION(hash, itr)
+
+ // sort string pointers from hash in array
+ qsort(sort_array, item_count, item_size, compareSetupFileData);
+
+ if (!(file = fopen(filename, MODE_WRITE)))
+ {
+ Warn("cannot write configuration file '%s'", filename);
+
+ return;
+ }
+
+ fprintf(file, "%s\n\n", getFormattedSetupEntry("program.version",
+ program.version_string));
+ for (i = 0; i < item_count; i++)
+ fprintf(file, "%s\n", getFormattedSetupEntry(sort_array[i].token,
+ sort_array[i].value));
+ fclose(file);
+
+ checked_free(sort_array);
+}
+
+SetupFileList *loadSetupFileList(char *filename)
+{
+ SetupFileList *setup_file_list = newSetupFileList("", "");
+ SetupFileList *first_valid_list_entry;
+
+ if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
+ {
+ freeSetupFileList(setup_file_list);
+
+ return NULL;
+ }
+
+ first_valid_list_entry = setup_file_list->next;
+
+ // free empty list header
+ setup_file_list->next = NULL;
+ freeSetupFileList(setup_file_list);
+
+ return first_valid_list_entry;
+}
+
+SetupFileHash *loadSetupFileHash(char *filename)
+{
+ SetupFileHash *setup_file_hash = newSetupFileHash();
+
+ if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
+ {
+ freeSetupFileHash(setup_file_hash);
+
+ return NULL;
+ }
+
+ return setup_file_hash;
+}
+
+
+// ============================================================================
+// setup file stuff
+// ============================================================================
+
+#define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
+#define TOKEN_STR_LAST_PLAYED_MENU_USED "last_played_menu_used"
+#define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
+#define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
+#define TOKEN_STR_LAST_USER "last_user"
+
+// level directory info
+#define LEVELINFO_TOKEN_IDENTIFIER 0
+#define LEVELINFO_TOKEN_NAME 1
+#define LEVELINFO_TOKEN_NAME_SORTING 2
+#define LEVELINFO_TOKEN_AUTHOR 3
+#define LEVELINFO_TOKEN_YEAR 4
+#define LEVELINFO_TOKEN_PROGRAM_TITLE 5
+#define LEVELINFO_TOKEN_PROGRAM_COPYRIGHT 6
+#define LEVELINFO_TOKEN_PROGRAM_COMPANY 7
+#define LEVELINFO_TOKEN_IMPORTED_FROM 8
+#define LEVELINFO_TOKEN_IMPORTED_BY 9
+#define LEVELINFO_TOKEN_TESTED_BY 10
+#define LEVELINFO_TOKEN_LEVELS 11
+#define LEVELINFO_TOKEN_FIRST_LEVEL 12
+#define LEVELINFO_TOKEN_SORT_PRIORITY 13
+#define LEVELINFO_TOKEN_LATEST_ENGINE 14
+#define LEVELINFO_TOKEN_LEVEL_GROUP 15
+#define LEVELINFO_TOKEN_READONLY 16
+#define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 17
+#define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 18
+#define LEVELINFO_TOKEN_GRAPHICS_SET 19
+#define LEVELINFO_TOKEN_SOUNDS_SET_DEFAULT 20
+#define LEVELINFO_TOKEN_SOUNDS_SET_LOWPASS 21
+#define LEVELINFO_TOKEN_SOUNDS_SET 22
+#define LEVELINFO_TOKEN_MUSIC_SET 23
+#define LEVELINFO_TOKEN_FILENAME 24
+#define LEVELINFO_TOKEN_FILETYPE 25
+#define LEVELINFO_TOKEN_SPECIAL_FLAGS 26
+#define LEVELINFO_TOKEN_EMPTY_LEVEL_NAME 27
+#define LEVELINFO_TOKEN_FORCE_LEVEL_NAME 28
+#define LEVELINFO_TOKEN_HANDICAP 29
+#define LEVELINFO_TOKEN_TIME_LIMIT 30
+#define LEVELINFO_TOKEN_SKIP_LEVELS 31
+#define LEVELINFO_TOKEN_USE_EMC_TILES 32
+#define LEVELINFO_TOKEN_INFO_SCREENS_FROM_MAIN 33
+
+#define NUM_LEVELINFO_TOKENS 34
+
+static LevelDirTree ldi;
+
+static struct TokenInfo levelinfo_tokens[] =
+{
+ // level directory info
+ { TYPE_STRING, &ldi.identifier, "identifier" },
+ { TYPE_STRING, &ldi.name, "name" },
+ { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
+ { TYPE_STRING, &ldi.author, "author" },
+ { TYPE_STRING, &ldi.year, "year" },
+ { TYPE_STRING, &ldi.program_title, "program_title" },
+ { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
+ { TYPE_STRING, &ldi.program_company, "program_company" },
+ { TYPE_STRING, &ldi.imported_from, "imported_from" },
+ { TYPE_STRING, &ldi.imported_by, "imported_by" },
+ { TYPE_STRING, &ldi.tested_by, "tested_by" },
+ { TYPE_INTEGER, &ldi.levels, "levels" },
+ { TYPE_INTEGER, &ldi.first_level, "first_level" },
+ { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
+ { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
+ { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
+ { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
+ { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
+ { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
+ { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
+ { TYPE_STRING, &ldi.sounds_set_default,"sounds_set.default" },
+ { TYPE_STRING, &ldi.sounds_set_lowpass,"sounds_set.lowpass" },
+ { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
+ { TYPE_STRING, &ldi.music_set, "music_set" },
+ { TYPE_STRING, &ldi.level_filename, "filename" },
+ { TYPE_STRING, &ldi.level_filetype, "filetype" },
+ { TYPE_STRING, &ldi.special_flags, "special_flags" },
+ { TYPE_STRING, &ldi.empty_level_name, "empty_level_name" },
+ { TYPE_BOOLEAN, &ldi.force_level_name, "force_level_name" },
+ { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
+ { TYPE_BOOLEAN, &ldi.time_limit, "time_limit" },
+ { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" },
+ { TYPE_BOOLEAN, &ldi.use_emc_tiles, "use_emc_tiles" },
+ { TYPE_BOOLEAN, &ldi.info_screens_from_main, "info_screens_from_main" }
+};
+
+static struct TokenInfo artworkinfo_tokens[] =
+{
+ // artwork directory info
+ { TYPE_STRING, &ldi.identifier, "identifier" },
+ { TYPE_STRING, &ldi.subdir, "subdir" },
+ { TYPE_STRING, &ldi.name, "name" },
+ { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
+ { TYPE_STRING, &ldi.author, "author" },
+ { TYPE_STRING, &ldi.program_title, "program_title" },
+ { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
+ { TYPE_STRING, &ldi.program_company, "program_company" },
+ { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
+ { TYPE_STRING, &ldi.basepath, "basepath" },
+ { TYPE_STRING, &ldi.fullpath, "fullpath" },
+ { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
+ { TYPE_STRING, &ldi.class_desc, "class_desc" },
+
+ { -1, NULL, NULL },
+};
+
+static char *optional_tokens[] =
+{
+ "program_title",
+ "program_copyright",
+ "program_company",
+
+ NULL
+};
+
+static void setTreeInfoToDefaults(TreeInfo *ti, int type)
+{
+ ti->type = type;
+
+ ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
+ ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
+ ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
+ ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
+ NULL);
+
+ ti->node_parent = NULL;
+ ti->node_group = NULL;
+ ti->next = NULL;
+
+ ti->cl_first = -1;
+ ti->cl_cursor = -1;
+
+ ti->subdir = NULL;
+ ti->fullpath = NULL;
+ ti->basepath = NULL;
+ ti->identifier = NULL;
+ ti->name = getStringCopy(ANONYMOUS_NAME);
+ ti->name_sorting = NULL;
+ ti->author = getStringCopy(ANONYMOUS_NAME);
+ ti->year = NULL;
+
+ ti->program_title = NULL;
+ ti->program_copyright = NULL;
+ ti->program_company = NULL;
+
+ ti->sort_priority = LEVELCLASS_UNDEFINED; // default: least priority
+ ti->latest_engine = FALSE; // default: get from level
+ ti->parent_link = FALSE;
+ ti->is_copy = FALSE;
+ ti->in_user_dir = FALSE;
+ ti->user_defined = FALSE;
+ ti->color = 0;
+ ti->class_desc = NULL;
+
+ ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
+
+ if (ti->type == TREE_TYPE_LEVEL_DIR)
+ {
+ ti->imported_from = NULL;
+ ti->imported_by = NULL;
+ ti->tested_by = NULL;
+
+ ti->graphics_set_ecs = NULL;
+ ti->graphics_set_aga = NULL;
+ ti->graphics_set = NULL;
+ ti->sounds_set_default = NULL;
+ ti->sounds_set_lowpass = NULL;
+ ti->sounds_set = NULL;
+ ti->music_set = NULL;
+ ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
+ ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
+ ti->music_path = getStringCopy(UNDEFINED_FILENAME);
+
+ ti->level_filename = NULL;
+ ti->level_filetype = NULL;
+
+ ti->special_flags = NULL;
+
+ ti->empty_level_name = NULL;
+ ti->force_level_name = FALSE;
+
+ ti->levels = 0;
+ ti->first_level = 0;
+ ti->last_level = 0;
+ ti->level_group = FALSE;
+ ti->handicap_level = 0;
+ ti->readonly = TRUE;
+ ti->handicap = TRUE;
+ ti->time_limit = TRUE;
+ ti->skip_levels = FALSE;
+
+ ti->use_emc_tiles = FALSE;
+ ti->info_screens_from_main = FALSE;
+ }
+}
+
+static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
+{
+ if (parent == NULL)
+ {
+ Warn("setTreeInfoToDefaultsFromParent(): parent == NULL");
+
+ setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
+
+ return;
+ }
+
+ // copy all values from the parent structure
+
+ ti->type = parent->type;
+
+ ti->node_top = parent->node_top;
+ ti->node_parent = parent;
+ ti->node_group = NULL;
+ ti->next = NULL;
+
+ ti->cl_first = -1;
+ ti->cl_cursor = -1;
+
+ ti->subdir = NULL;
+ ti->fullpath = NULL;
+ ti->basepath = NULL;
+ ti->identifier = NULL;
+ ti->name = getStringCopy(ANONYMOUS_NAME);
+ ti->name_sorting = NULL;
+ ti->author = getStringCopy(parent->author);
+ ti->year = getStringCopy(parent->year);
+
+ ti->program_title = getStringCopy(parent->program_title);
+ ti->program_copyright = getStringCopy(parent->program_copyright);
+ ti->program_company = getStringCopy(parent->program_company);
+
+ ti->sort_priority = parent->sort_priority;
+ ti->latest_engine = parent->latest_engine;
+ ti->parent_link = FALSE;
+ ti->is_copy = FALSE;
+ ti->in_user_dir = parent->in_user_dir;
+ ti->user_defined = parent->user_defined;
+ ti->color = parent->color;
+ ti->class_desc = getStringCopy(parent->class_desc);
+
+ ti->infotext = getStringCopy(parent->infotext);
+
+ if (ti->type == TREE_TYPE_LEVEL_DIR)
+ {
+ ti->imported_from = getStringCopy(parent->imported_from);
+ ti->imported_by = getStringCopy(parent->imported_by);
+ ti->tested_by = getStringCopy(parent->tested_by);
+
+ ti->graphics_set_ecs = getStringCopy(parent->graphics_set_ecs);
+ ti->graphics_set_aga = getStringCopy(parent->graphics_set_aga);
+ ti->graphics_set = getStringCopy(parent->graphics_set);
+ ti->sounds_set_default = getStringCopy(parent->sounds_set_default);
+ ti->sounds_set_lowpass = getStringCopy(parent->sounds_set_lowpass);
+ ti->sounds_set = getStringCopy(parent->sounds_set);
+ ti->music_set = getStringCopy(parent->music_set);
+ ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
+ ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
+ ti->music_path = getStringCopy(UNDEFINED_FILENAME);
+
+ ti->level_filename = getStringCopy(parent->level_filename);
+ ti->level_filetype = getStringCopy(parent->level_filetype);
+
+ ti->special_flags = getStringCopy(parent->special_flags);
+
+ ti->empty_level_name = getStringCopy(parent->empty_level_name);
+ ti->force_level_name = parent->force_level_name;
+
+ ti->levels = parent->levels;
+ ti->first_level = parent->first_level;
+ ti->last_level = parent->last_level;
+ ti->level_group = FALSE;
+ ti->handicap_level = parent->handicap_level;
+ ti->readonly = parent->readonly;
+ ti->handicap = parent->handicap;
+ ti->time_limit = parent->time_limit;
+ ti->skip_levels = parent->skip_levels;
+
+ ti->use_emc_tiles = parent->use_emc_tiles;
+ ti->info_screens_from_main = parent->info_screens_from_main;
+ }
+}
+
+static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
+{
+ TreeInfo *ti_copy = newTreeInfo();
+
+ // copy all values from the original structure
+
+ ti_copy->type = ti->type;
+
+ ti_copy->node_top = ti->node_top;
+ ti_copy->node_parent = ti->node_parent;
+ ti_copy->node_group = ti->node_group;
+ ti_copy->next = ti->next;
+
+ ti_copy->cl_first = ti->cl_first;
+ ti_copy->cl_cursor = ti->cl_cursor;
+
+ ti_copy->subdir = getStringCopy(ti->subdir);
+ ti_copy->fullpath = getStringCopy(ti->fullpath);
+ ti_copy->basepath = getStringCopy(ti->basepath);
+ ti_copy->identifier = getStringCopy(ti->identifier);
+ ti_copy->name = getStringCopy(ti->name);
+ ti_copy->name_sorting = getStringCopy(ti->name_sorting);
+ ti_copy->author = getStringCopy(ti->author);
+ ti_copy->year = getStringCopy(ti->year);
+
+ ti_copy->program_title = getStringCopy(ti->program_title);
+ ti_copy->program_copyright = getStringCopy(ti->program_copyright);
+ ti_copy->program_company = getStringCopy(ti->program_company);
+
+ ti_copy->imported_from = getStringCopy(ti->imported_from);
+ ti_copy->imported_by = getStringCopy(ti->imported_by);
+ ti_copy->tested_by = getStringCopy(ti->tested_by);
+
+ ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
+ ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
+ ti_copy->graphics_set = getStringCopy(ti->graphics_set);
+ ti_copy->sounds_set_default = getStringCopy(ti->sounds_set_default);
+ ti_copy->sounds_set_lowpass = getStringCopy(ti->sounds_set_lowpass);
+ ti_copy->sounds_set = getStringCopy(ti->sounds_set);
+ ti_copy->music_set = getStringCopy(ti->music_set);
+ ti_copy->graphics_path = getStringCopy(ti->graphics_path);
+ ti_copy->sounds_path = getStringCopy(ti->sounds_path);
+ ti_copy->music_path = getStringCopy(ti->music_path);
+
+ ti_copy->level_filename = getStringCopy(ti->level_filename);
+ ti_copy->level_filetype = getStringCopy(ti->level_filetype);
+
+ ti_copy->special_flags = getStringCopy(ti->special_flags);
+
+ ti_copy->empty_level_name = getStringCopy(ti->empty_level_name);
+ ti_copy->force_level_name = ti->force_level_name;
+
+ ti_copy->levels = ti->levels;
+ ti_copy->first_level = ti->first_level;
+ ti_copy->last_level = ti->last_level;
+ ti_copy->sort_priority = ti->sort_priority;
+
+ ti_copy->latest_engine = ti->latest_engine;
+
+ ti_copy->level_group = ti->level_group;
+ ti_copy->parent_link = ti->parent_link;
+ ti_copy->is_copy = ti->is_copy;
+ ti_copy->in_user_dir = ti->in_user_dir;
+ ti_copy->user_defined = ti->user_defined;
+ ti_copy->readonly = ti->readonly;
+ ti_copy->handicap = ti->handicap;
+ ti_copy->time_limit = ti->time_limit;
+ ti_copy->skip_levels = ti->skip_levels;
+
+ ti_copy->use_emc_tiles = ti->use_emc_tiles;
+ ti_copy->info_screens_from_main = ti->info_screens_from_main;
+
+ ti_copy->color = ti->color;
+ ti_copy->class_desc = getStringCopy(ti->class_desc);
+ ti_copy->handicap_level = ti->handicap_level;
+
+ ti_copy->infotext = getStringCopy(ti->infotext);
+
+ return ti_copy;
+}
+
+void freeTreeInfo(TreeInfo *ti)
+{
+ if (ti == NULL)
+ return;
+
+ checked_free(ti->subdir);
+ checked_free(ti->fullpath);
+ checked_free(ti->basepath);
+ checked_free(ti->identifier);