+}
+
+static int compareLevelDirInfoEntries(const void *object1, const void *object2)
+{
+ const struct LevelDirInfo *entry1 = *((struct LevelDirInfo **)object1);
+ const struct LevelDirInfo *entry2 = *((struct LevelDirInfo **)object2);
+ int compare_result;
+
+ if (entry1->parent_link || entry2->parent_link)
+ compare_result = (entry1->parent_link ? -1 : +1);
+ else if (entry1->sort_priority == entry2->sort_priority)
+ {
+ char *name1 = getStringToLower(entry1->name_sorting);
+ char *name2 = getStringToLower(entry2->name_sorting);
+
+ compare_result = strcmp(name1, name2);
+
+ free(name1);
+ free(name2);
+ }
+ else if (LEVELSORTING(entry1) == LEVELSORTING(entry2))
+ compare_result = entry1->sort_priority - entry2->sort_priority;
+ else
+ compare_result = LEVELSORTING(entry1) - LEVELSORTING(entry2);
+
+ return compare_result;
+}
+
+static void createParentLevelDirNode(struct LevelDirInfo *node_parent)
+{
+ struct LevelDirInfo *leveldir_new = newLevelDirInfo();
+
+ setLevelDirInfoToDefaults(leveldir_new);
+
+ leveldir_new->node_parent = node_parent;
+ leveldir_new->parent_link = TRUE;
+
+ leveldir_new->name = ".. (parent directory)";
+ leveldir_new->name_short = getStringCopy(leveldir_new->name);
+ leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
+
+ leveldir_new->filename = "..";
+ leveldir_new->fullpath = getStringCopy(node_parent->fullpath);
+
+ leveldir_new->sort_priority = node_parent->sort_priority;
+ leveldir_new->class_desc = getLevelClassDescription(leveldir_new);
+
+ pushLevelDirInfo(&node_parent->node_group, leveldir_new);
+}
+
+static void LoadLevelInfoFromLevelDir(struct LevelDirInfo **node_first,
+ struct LevelDirInfo *node_parent,
+ char *level_directory)
+{
+ DIR *dir;
+ struct dirent *dir_entry;
+ boolean valid_entry_found = FALSE;
+
+ if ((dir = opendir(level_directory)) == NULL)
+ {
+ Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
+ return;
+ }
+
+ while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
+ {
+ struct SetupFileList *setup_file_list = NULL;
+ struct stat file_status;
+ char *directory_name = dir_entry->d_name;
+ char *directory_path = getPath2(level_directory, directory_name);
+ char *filename = NULL;
+
+ /* skip entries for current and parent directory */
+ if (strcmp(directory_name, ".") == 0 ||
+ strcmp(directory_name, "..") == 0)
+ {
+ free(directory_path);
+ continue;
+ }
+
+ /* find out if directory entry is itself a directory */
+ if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
+ (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
+ {
+ free(directory_path);
+ continue;
+ }
+
+ filename = getPath2(directory_path, LEVELINFO_FILENAME);
+ setup_file_list = loadSetupFileList(filename);
+
+ if (setup_file_list)
+ {
+ struct LevelDirInfo *leveldir_new = newLevelDirInfo();
+ int i;
+
+ checkSetupFileListIdentifier(setup_file_list, LEVELINFO_COOKIE);
+ setLevelDirInfoToDefaultsFromParent(leveldir_new, node_parent);
+
+ /* set all structure fields according to the token/value pairs */
+ ldi = *leveldir_new;
+ for (i=FIRST_LEVELINFO_TOKEN; i<=LAST_LEVELINFO_TOKEN; i++)
+ setSetupInfo(i, getTokenValue(setup_file_list, token_info[i].text));
+ *leveldir_new = ldi;
+
+ DrawInitText(leveldir_new->name, 150, FC_YELLOW);
+
+ if (leveldir_new->name_short == NULL)
+ leveldir_new->name_short = getStringCopy(leveldir_new->name);
+
+ if (leveldir_new->name_sorting == NULL)
+ leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
+
+ leveldir_new->filename = getStringCopy(directory_name);
+
+ if (node_parent == NULL) /* top level group */
+ {
+ leveldir_new->basepath = level_directory;
+ leveldir_new->fullpath = leveldir_new->filename;
+ }
+ else /* sub level group */
+ {
+ leveldir_new->basepath = node_parent->basepath;
+ leveldir_new->fullpath = getPath2(node_parent->fullpath,
+ directory_name);
+ }
+
+ if (leveldir_new->levels < 1)
+ leveldir_new->levels = 1;
+
+ leveldir_new->last_level =
+ leveldir_new->first_level + leveldir_new->levels - 1;
+
+ leveldir_new->user_defined =
+ (leveldir_new->basepath == options.level_directory ? FALSE : TRUE);
+
+ leveldir_new->color = LEVELCOLOR(leveldir_new);
+ leveldir_new->class_desc = getLevelClassDescription(leveldir_new);
+
+ leveldir_new->handicap_level = /* set handicap to default value */
+ (leveldir_new->user_defined ?
+ leveldir_new->last_level :
+ leveldir_new->first_level);
+
+ pushLevelDirInfo(node_first, leveldir_new);
+
+ freeSetupFileList(setup_file_list);
+ valid_entry_found = TRUE;
+
+ if (leveldir_new->level_group)
+ {
+ /* create node to link back to current level directory */
+ createParentLevelDirNode(leveldir_new);
+
+ /* step into sub-directory and look for more level series */
+ LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
+ leveldir_new, directory_path);
+ }
+ }
+ else
+ Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
+
+ free(directory_path);
+ free(filename);
+ }
+
+ closedir(dir);
+
+ if (!valid_entry_found)
+ Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
+ level_directory);
+}
+
+void LoadLevelInfo()
+{
+ InitUserLevelDirectory(getLoginName());
+
+ DrawInitText("Loading level series:", 120, FC_GREEN);
+
+ LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
+ LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(""));
+
+ leveldir_current = getFirstValidLevelSeries(leveldir_first);
+
+ if (leveldir_first == NULL)
+ Error(ERR_EXIT, "cannot find any valid level series in any directory");
+
+ sortLevelDirInfo(&leveldir_first, compareLevelDirInfoEntries);
+
+#if 0
+ dumpLevelDirInfo(leveldir_first, 0);
+#endif
+}
+
+static void SaveUserLevelInfo()
+{
+ char *filename;
+ FILE *file;
+ int i;
+
+ filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
+
+ if (!(file = fopen(filename, MODE_WRITE)))
+ {
+ Error(ERR_WARN, "cannot write level info file '%s'", filename);
+ free(filename);
+ return;
+ }
+
+ /* always start with reliable default values */
+ setLevelDirInfoToDefaults(&ldi);
+
+ ldi.name = getLoginName();
+ ldi.author = getRealName();
+ ldi.levels = 100;
+ ldi.first_level = 1;
+ ldi.sort_priority = LEVELCLASS_USER_START;
+ ldi.readonly = FALSE;
+
+ fprintf(file, "%s\n\n",
+ getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER, LEVELINFO_COOKIE));
+
+ for (i=FIRST_LEVELINFO_TOKEN; i<=LAST_LEVELINFO_TOKEN; i++)
+ if (i != LEVELINFO_TOKEN_NAME_SHORT &&
+ i != LEVELINFO_TOKEN_NAME_SORTING &&
+ i != LEVELINFO_TOKEN_IMPORTED_FROM)
+ fprintf(file, "%s\n", getSetupLine("", i));
+
+ fclose(file);
+ free(filename);
+
+ SetFilePermissions(filename, PERMS_PRIVATE);
+}
+
+void LoadSetup()
+{
+ char *filename;
+ struct SetupFileList *setup_file_list = NULL;
+
+ /* always start with reliable default values */
+ setSetupInfoToDefaults(&setup);
+
+ filename = getPath2(getSetupDir(), SETUP_FILENAME);
+
+ setup_file_list = loadSetupFileList(filename);
+
+ if (setup_file_list)
+ {
+ checkSetupFileListIdentifier(setup_file_list, SETUP_COOKIE);
+ decodeSetupFileList(setup_file_list);
+
+ setup.direct_draw = !setup.double_buffering;
+
+ freeSetupFileList(setup_file_list);
+
+ /* needed to work around problems with fixed length strings */
+ if (strlen(setup.player_name) > MAX_PLAYER_NAME_LEN)
+ setup.player_name[MAX_PLAYER_NAME_LEN] = '\0';
+ else if (strlen(setup.player_name) < MAX_PLAYER_NAME_LEN)
+ {
+ char *new_name = checked_malloc(MAX_PLAYER_NAME_LEN + 1);
+
+ strcpy(new_name, setup.player_name);
+ free(setup.player_name);
+ setup.player_name = new_name;
+ }
+ }
+ else
+ Error(ERR_WARN, "using default setup values");
+
+ free(filename);
+}
+
+static char *getSetupLine(char *prefix, int token_nr)
+{
+ int i;
+ static char entry[MAX_LINE_LEN];
+ int token_type = token_info[token_nr].type;
+ void *setup_value = token_info[token_nr].value;
+ char *token_text = token_info[token_nr].text;
+
+ /* start with the prefix, token and some spaces to format output line */
+ sprintf(entry, "%s%s:", prefix, token_text);
+ for (i=strlen(entry); i<TOKEN_VALUE_POSITION; i++)
+ strcat(entry, " ");
+
+ /* continue with the token's value (which can have different types) */
+ switch (token_type)
+ {
+ case TYPE_BOOLEAN:
+ strcat(entry, (*(boolean *)setup_value ? "true" : "false"));
+ break;
+
+ case TYPE_SWITCH:
+ strcat(entry, (*(boolean *)setup_value ? "on" : "off"));
+ break;
+
+ case TYPE_KEY:
+ {
+ Key key = *(Key *)setup_value;
+ char *keyname = getKeyNameFromKey(key);
+
+ strcat(entry, getX11KeyNameFromKey(key));
+ for (i=strlen(entry); i<50; i++)
+ strcat(entry, " ");
+
+ /* add comment, if useful */
+ if (strcmp(keyname, "(undefined)") != 0 &&
+ strcmp(keyname, "(unknown)") != 0)
+ {
+ strcat(entry, "# ");
+ strcat(entry, keyname);
+ }
+ }
+ break;
+
+ case TYPE_INTEGER:
+ {
+ char buffer[MAX_LINE_LEN];
+
+ sprintf(buffer, "%d", *(int *)setup_value);
+ strcat(entry, buffer);
+ }
+ break;
+
+ case TYPE_STRING:
+ strcat(entry, *(char **)setup_value);
+ break;
+
+ default:
+ break;
+ }
+
+ return entry;
+}
+
+void SaveSetup()
+{
+ int i, pnr;
+ char *filename;
+ FILE *file;
+
+ InitUserDataDirectory();
+
+ filename = getPath2(getSetupDir(), SETUP_FILENAME);
+
+ if (!(file = fopen(filename, MODE_WRITE)))
+ {
+ Error(ERR_WARN, "cannot write setup file '%s'", filename);
+ free(filename);
+ return;
+ }
+
+ fprintf(file, "%s\n",
+ getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER, SETUP_COOKIE));
+ fprintf(file, "\n");
+
+ /* handle global setup values */
+ si = setup;
+ for (i=FIRST_GLOBAL_SETUP_TOKEN; i<=LAST_GLOBAL_SETUP_TOKEN; i++)
+ {
+ fprintf(file, "%s\n", getSetupLine("", i));
+
+ /* just to make things nicer :) */
+ if (i == SETUP_TOKEN_PLAYER_NAME)
+ fprintf(file, "\n");
+ }
+
+ /* handle player specific setup values */
+ for (pnr=0; pnr<MAX_PLAYERS; pnr++)
+ {
+ char prefix[30];
+
+ sprintf(prefix, "%s%d", TOKEN_STR_PLAYER_PREFIX, pnr + 1);
+ fprintf(file, "\n");
+
+ sii = setup.input[pnr];
+ for (i=FIRST_PLAYER_SETUP_TOKEN; i<=LAST_PLAYER_SETUP_TOKEN; i++)
+ fprintf(file, "%s\n", getSetupLine(prefix, i));
+ }
+
+ fclose(file);
+ free(filename);
+
+ SetFilePermissions(filename, PERMS_PRIVATE);
+}
+
+void LoadLevelSetup_LastSeries()
+{
+ char *filename;
+ struct SetupFileList *level_setup_list = NULL;
+
+ /* always start with reliable default values */
+ leveldir_current = getFirstValidLevelSeries(leveldir_first);
+
+ /* ----------------------------------------------------------------------- */
+ /* ~/.rocksndiamonds/levelsetup.conf */
+ /* ----------------------------------------------------------------------- */
+
+ filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
+
+ if ((level_setup_list = loadSetupFileList(filename)))
+ {
+ char *last_level_series =
+ getTokenValue(level_setup_list, TOKEN_STR_LAST_LEVEL_SERIES);
+
+ leveldir_current = getLevelDirInfoFromFilename(last_level_series);
+ if (leveldir_current == NULL)
+ leveldir_current = leveldir_first;
+
+ checkSetupFileListIdentifier(level_setup_list, LEVELSETUP_COOKIE);
+
+ freeSetupFileList(level_setup_list);
+ }
+ else
+ Error(ERR_WARN, "using default setup values");
+
+ free(filename);
+}
+
+void SaveLevelSetup_LastSeries()
+{
+ char *filename;
+ char *level_subdir = leveldir_current->filename;
+ FILE *file;
+
+ /* ----------------------------------------------------------------------- */
+ /* ~/.rocksndiamonds/levelsetup.conf */
+ /* ----------------------------------------------------------------------- */
+
+ InitUserDataDirectory();
+
+ filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
+
+ if (!(file = fopen(filename, MODE_WRITE)))
+ {
+ Error(ERR_WARN, "cannot write setup file '%s'", filename);
+ free(filename);
+ return;
+ }
+
+ fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
+ LEVELSETUP_COOKIE));
+ fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
+ level_subdir));
+
+ fclose(file);
+ free(filename);
+
+ SetFilePermissions(filename, PERMS_PRIVATE);
+}
+
+static void checkSeriesInfo()
+{
+ static char *level_directory = NULL;
+ DIR *dir;
+ struct dirent *dir_entry;
+
+ /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
+
+ level_directory = getPath2((leveldir_current->user_defined ?
+ getUserLevelDir("") :
+ options.level_directory),
+ leveldir_current->fullpath);
+
+ if ((dir = opendir(level_directory)) == NULL)
+ {
+ Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
+ return;
+ }
+
+ while ((dir_entry = readdir(dir)) != NULL) /* last directory entry */
+ {
+ if (strlen(dir_entry->d_name) > 4 &&
+ dir_entry->d_name[3] == '.' &&
+ strcmp(&dir_entry->d_name[4], LEVELFILE_EXTENSION) == 0)
+ {
+ char levelnum_str[4];
+ int levelnum_value;
+
+ strncpy(levelnum_str, dir_entry->d_name, 3);
+ levelnum_str[3] = '\0';
+
+ levelnum_value = atoi(levelnum_str);
+
+ if (levelnum_value < leveldir_current->first_level)
+ {
+ Error(ERR_WARN, "additional level %d found", levelnum_value);
+ leveldir_current->first_level = levelnum_value;
+ }
+ else if (levelnum_value > leveldir_current->last_level)
+ {
+ Error(ERR_WARN, "additional level %d found", levelnum_value);
+ leveldir_current->last_level = levelnum_value;
+ }
+ }
+ }
+
+ closedir(dir);
+}
+
+void LoadLevelSetup_SeriesInfo()
+{
+ char *filename;
+ struct SetupFileList *level_setup_list = NULL;
+ char *level_subdir = leveldir_current->filename;
+
+ /* always start with reliable default values */
+ level_nr = leveldir_current->first_level;
+
+ checkSeriesInfo(leveldir_current);
+
+ /* ----------------------------------------------------------------------- */
+ /* ~/.rocksndiamonds/levelsetup/<level series>/levelsetup.conf */
+ /* ----------------------------------------------------------------------- */
+
+ level_subdir = leveldir_current->filename;
+
+ filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
+
+ if ((level_setup_list = loadSetupFileList(filename)))
+ {
+ char *token_value;
+
+ token_value = getTokenValue(level_setup_list, TOKEN_STR_LAST_PLAYED_LEVEL);
+
+ if (token_value)
+ {
+ level_nr = atoi(token_value);
+
+ if (level_nr < leveldir_current->first_level)
+ level_nr = leveldir_current->first_level;
+ if (level_nr > leveldir_current->last_level)
+ level_nr = leveldir_current->last_level;
+ }
+
+ token_value = getTokenValue(level_setup_list, TOKEN_STR_HANDICAP_LEVEL);
+
+ if (token_value)
+ {
+ int level_nr = atoi(token_value);
+
+ if (level_nr < leveldir_current->first_level)
+ level_nr = leveldir_current->first_level;
+ if (level_nr > leveldir_current->last_level + 1)
+ level_nr = leveldir_current->last_level;
+
+ if (leveldir_current->user_defined)
+ level_nr = leveldir_current->last_level;
+
+ leveldir_current->handicap_level = level_nr;
+ }
+
+ checkSetupFileListIdentifier(level_setup_list, LEVELSETUP_COOKIE);
+
+ freeSetupFileList(level_setup_list);
+ }
+ else
+ Error(ERR_WARN, "using default setup values");
+
+ free(filename);
+}
+
+void SaveLevelSetup_SeriesInfo()
+{
+ char *filename;
+ char *level_subdir = leveldir_current->filename;
+ char *level_nr_str = int2str(level_nr, 0);
+ char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
+ FILE *file;
+
+ /* ----------------------------------------------------------------------- */
+ /* ~/.rocksndiamonds/levelsetup/<level series>/levelsetup.conf */
+ /* ----------------------------------------------------------------------- */
+
+ InitLevelSetupDirectory(level_subdir);
+
+ filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
+
+ if (!(file = fopen(filename, MODE_WRITE)))
+ {
+ Error(ERR_WARN, "cannot write setup file '%s'", filename);
+ free(filename);
+ return;
+ }
+
+ fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
+ LEVELSETUP_COOKIE));
+ fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
+ level_nr_str));
+ fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
+ handicap_level_str));
+