changed using 'external' app data directory on Android version, if possible
[rocksndiamonds.git] / src / libgame / setup.c
index b9c9899c4a56d468a0d514b942dfc53398c37e32..ced73a837cff9f80921172c23e6eefa2419bda85 100644 (file)
@@ -1,15 +1,13 @@
-/***********************************************************
-* Artsoft Retro-Game Library                               *
-*----------------------------------------------------------*
-* (c) 1994-2006 Artsoft Entertainment                      *
-*               Holger Schemel                             *
-*               Detmolder Strasse 189                      *
-*               33604 Bielefeld                            *
-*               Germany                                    *
-*               e-mail: info@artsoft.org                   *
-*----------------------------------------------------------*
-* setup.c                                                  *
-***********************************************************/
+// ============================================================================
+// Artsoft Retro-Game Library
+// ----------------------------------------------------------------------------
+// (c) 1995-2014 by Artsoft Entertainment
+//                         Holger Schemel
+//                 info@artsoft.org
+//                 http://www.artsoft.org/
+// ----------------------------------------------------------------------------
+// setup.c
+// ============================================================================
 
 #include <sys/types.h>
 #include <sys/stat.h>
@@ -32,6 +30,8 @@
 #include "hash.h"
 
 
+#define ENABLE_UNUSED_CODE     FALSE   /* for currently unused functions */
+
 #define NUM_LEVELCLASS_DESC    8
 
 static char *levelclass_desc[NUM_LEVELCLASS_DESC] =
@@ -133,15 +133,25 @@ static char *getUserLevelDir(char *level_subdir)
 static char *getScoreDir(char *level_subdir)
 {
   static char *score_dir = NULL;
-  char *data_dir = getCommonDataDir();
+  static char *score_level_dir = NULL;
   char *score_subdir = SCORES_DIRECTORY;
 
-  checked_free(score_dir);
+  if (score_dir == NULL)
+  {
+    if (program.global_scores)
+      score_dir = getPath2(getCommonDataDir(),   score_subdir);
+    else
+      score_dir = getPath2(getUserGameDataDir(), score_subdir);
+  }
 
   if (level_subdir != NULL)
-    score_dir = getPath3(data_dir, score_subdir, level_subdir);
-  else
-    score_dir = getPath2(data_dir, score_subdir);
+  {
+    checked_free(score_level_dir);
+
+    score_level_dir = getPath2(score_dir, level_subdir);
+
+    return score_level_dir;
+  }
 
   return score_dir;
 }
@@ -314,6 +324,9 @@ static char *getSetupArtworkDir(TreeInfo *ti)
 {
   static char *artwork_dir = NULL;
 
+  if (ti == NULL)
+    return NULL;
+
   checked_free(artwork_dir);
 
   artwork_dir = getPath2(ti->basepath, ti->fullpath);
@@ -357,7 +370,7 @@ char *setLevelArtworkDir(TreeInfo *ti)
 
     checked_free(*artwork_set_ptr);
 
-    if (fileExists(dir))
+    if (directoryExists(dir))
     {
       *artwork_path_ptr = getStringCopy(getClassicArtworkDir(ti->type));
       *artwork_set_ptr = getStringCopy(getClassicArtworkSet(ti->type));
@@ -390,6 +403,29 @@ inline static char *getLevelArtworkDir(int type)
   return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
 }
 
+char *getProgramConfigFilename(char *command_filename_ptr)
+{
+  char *command_filename_1 = getStringCopy(command_filename_ptr);
+
+  // strip trailing executable suffix from command filename
+  if (strSuffix(command_filename_1, ".exe"))
+    command_filename_1[strlen(command_filename_1) - 4] = '\0';
+
+  char *command_basepath = getBasePath(command_filename_ptr);
+  char *command_basename = getBaseNameNoSuffix(command_filename_ptr);
+  char *command_filename_2 = getPath2(command_basepath, command_basename);
+
+  char *config_filename_1 = getStringCat2(command_filename_1, ".conf");
+  char *config_filename_2 = getStringCat2(command_filename_2, ".conf");
+
+  // 1st try: look for config file that exactly matches the binary filename
+  if (fileExists(config_filename_1))
+    return config_filename_1;
+
+  // 2nd try: return config filename that matches binary filename without suffix
+  return config_filename_2;
+}
+
 char *getTapeFilename(int nr)
 {
   static char *filename = NULL;
@@ -453,6 +489,11 @@ char *getSetupFilename()
   return filename;
 }
 
+char *getDefaultSetupFilename()
+{
+  return program.config_filename;
+}
+
 char *getEditorSetupFilename()
 {
   static char *filename = NULL;
@@ -598,34 +639,7 @@ char *getLevelSetTitleMessageFilename(int nr, boolean initial)
 
 static char *getCorrectedArtworkBasename(char *basename)
 {
-  char *basename_corrected = basename;
-
-#if defined(PLATFORM_MSDOS)
-  if (program.filename_prefix != NULL)
-  {
-    int prefix_len = strlen(program.filename_prefix);
-
-    if (strncmp(basename, program.filename_prefix, prefix_len) == 0)
-      basename_corrected = &basename[prefix_len];
-
-    /* if corrected filename is still longer than standard MS-DOS filename
-       size (8 characters + 1 dot + 3 characters file extension), shorten
-       filename by writing file extension after 8th basename character */
-    if (strlen(basename_corrected) > 8 + 1 + 3)
-    {
-      static char *msdos_filename = NULL;
-
-      checked_free(msdos_filename);
-
-      msdos_filename = getStringCopy(basename_corrected);
-      strncpy(&msdos_filename[8], &basename[strlen(basename) - (1+3)], 1+3 +1);
-
-      basename_corrected = msdos_filename;
-    }
-  }
-#endif
-
-  return basename_corrected;
+  return basename;
 }
 
 char *getCustomImageFilename(char *basename)
@@ -640,7 +654,7 @@ char *getCustomImageFilename(char *basename)
   if (!gfx.override_level_graphics)
   {
     /* 1st try: look for special artwork in current level series directory */
-    filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
+    filename = getImg3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
     if (fileExists(filename))
       return filename;
 
@@ -650,7 +664,7 @@ char *getCustomImageFilename(char *basename)
     if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
     {
       /* 2nd try: look for special artwork configured in level series config */
-      filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
+      filename = getImg2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
       if (fileExists(filename))
        return filename;
 
@@ -664,7 +678,7 @@ char *getCustomImageFilename(char *basename)
   if (!skip_setup_artwork)
   {
     /* 3rd try: look for special artwork in configured artwork directory */
-    filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
+    filename = getImg2(getSetupArtworkDir(artwork.gfx_current), basename);
     if (fileExists(filename))
       return filename;
 
@@ -672,29 +686,31 @@ char *getCustomImageFilename(char *basename)
   }
 
   /* 4th try: look for default artwork in new default artwork directory */
-  filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
+  filename = getImg2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
   if (fileExists(filename))
     return filename;
 
   free(filename);
 
   /* 5th try: look for default artwork in old default artwork directory */
-  filename = getPath2(options.graphics_directory, basename);
+  filename = getImg2(options.graphics_directory, basename);
   if (fileExists(filename))
     return filename;
 
-#if defined(CREATE_SPECIAL_EDITION)
-  free(filename);
+  if (!strEqual(GFX_FALLBACK_FILENAME, UNDEFINED_FILENAME))
+  {
+    free(filename);
 
-  if (options.debug)
-    Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)", basename);
+    if (options.debug)
+      Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)",
+           basename);
 
-  /* 6th try: look for fallback artwork in old default artwork directory */
-  /* (needed to prevent errors when trying to access unused artwork files) */
-  filename = getPath2(options.graphics_directory, GFX_FALLBACK_FILENAME);
-  if (fileExists(filename))
-    return filename;
-#endif
+    /* 6th try: look for fallback artwork in old default artwork directory */
+    /* (needed to prevent errors when trying to access unused artwork files) */
+    filename = getImg2(options.graphics_directory, GFX_FALLBACK_FILENAME);
+    if (fileExists(filename))
+      return filename;
+  }
 
   return NULL;         /* cannot find specified artwork file anywhere */
 }
@@ -754,18 +770,20 @@ char *getCustomSoundFilename(char *basename)
   if (fileExists(filename))
     return filename;
 
-#if defined(CREATE_SPECIAL_EDITION)
-  free(filename);
+  if (!strEqual(SND_FALLBACK_FILENAME, UNDEFINED_FILENAME))
+  {
+    free(filename);
 
-  if (options.debug)
-    Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)", basename);
+    if (options.debug)
+      Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)",
+           basename);
 
-  /* 6th try: look for fallback artwork in old default artwork directory */
-  /* (needed to prevent errors when trying to access unused artwork files) */
-  filename = getPath2(options.sounds_directory, SND_FALLBACK_FILENAME);
-  if (fileExists(filename))
-    return filename;
-#endif
+    /* 6th try: look for fallback artwork in old default artwork directory */
+    /* (needed to prevent errors when trying to access unused artwork files) */
+    filename = getPath2(options.sounds_directory, SND_FALLBACK_FILENAME);
+    if (fileExists(filename))
+      return filename;
+  }
 
   return NULL;         /* cannot find specified artwork file anywhere */
 }
@@ -825,18 +843,20 @@ char *getCustomMusicFilename(char *basename)
   if (fileExists(filename))
     return filename;
 
-#if defined(CREATE_SPECIAL_EDITION)
-  free(filename);
+  if (!strEqual(MUS_FALLBACK_FILENAME, UNDEFINED_FILENAME))
+  {
+    free(filename);
 
-  if (options.debug)
-    Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)", basename);
+    if (options.debug)
+      Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)",
+           basename);
 
-  /* 6th try: look for fallback artwork in old default artwork directory */
-  /* (needed to prevent errors when trying to access unused artwork files) */
-  filename = getPath2(options.music_directory, MUS_FALLBACK_FILENAME);
-  if (fileExists(filename))
-    return filename;
-#endif
+    /* 6th try: look for fallback artwork in old default artwork directory */
+    /* (needed to prevent errors when trying to access unused artwork files) */
+    filename = getPath2(options.music_directory, MUS_FALLBACK_FILENAME);
+    if (fileExists(filename))
+      return filename;
+  }
 
   return NULL;         /* cannot find specified artwork file anywhere */
 }
@@ -880,7 +900,7 @@ char *getCustomMusicDirectory(void)
   {
     /* 1st try: look for special artwork in current level series directory */
     directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
-    if (fileExists(directory))
+    if (directoryExists(directory))
       return directory;
 
     free(directory);
@@ -890,7 +910,7 @@ char *getCustomMusicDirectory(void)
     {
       /* 2nd try: look for special artwork configured in level series config */
       directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
-      if (fileExists(directory))
+      if (directoryExists(directory))
        return directory;
 
       free(directory);
@@ -904,7 +924,7 @@ char *getCustomMusicDirectory(void)
   {
     /* 3rd try: look for special artwork in configured artwork directory */
     directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
-    if (fileExists(directory))
+    if (directoryExists(directory))
       return directory;
 
     free(directory);
@@ -912,14 +932,14 @@ char *getCustomMusicDirectory(void)
 
   /* 4th try: look for default artwork in new default artwork directory */
   directory = getStringCopy(getDefaultMusicDir(MUS_DEFAULT_SUBDIR));
-  if (fileExists(directory))
+  if (directoryExists(directory))
     return directory;
 
   free(directory);
 
   /* 5th try: look for default artwork in old default artwork directory */
   directory = getStringCopy(options.music_directory);
-  if (fileExists(directory))
+  if (directoryExists(directory))
     return directory;
 
   return NULL;         /* cannot find specified artwork file anywhere */
@@ -934,16 +954,22 @@ void InitTapeDirectory(char *level_subdir)
 
 void InitScoreDirectory(char *level_subdir)
 {
-  createDirectory(getCommonDataDir(), "common data", PERMS_PUBLIC);
-  createDirectory(getScoreDir(NULL), "main score", PERMS_PUBLIC);
-  createDirectory(getScoreDir(level_subdir), "level score", PERMS_PUBLIC);
+  int permissions = (program.global_scores ? PERMS_PUBLIC : PERMS_PRIVATE);
+
+  if (program.global_scores)
+    createDirectory(getCommonDataDir(), "common data", permissions);
+  else
+    createDirectory(getUserGameDataDir(), "user data", permissions);
+
+  createDirectory(getScoreDir(NULL), "main score", permissions);
+  createDirectory(getScoreDir(level_subdir), "level score", permissions);
 }
 
 static void SaveUserLevelInfo();
 
 void InitUserLevelDirectory(char *level_subdir)
 {
-  if (!fileExists(getUserLevelDir(level_subdir)))
+  if (!directoryExists(getUserLevelDir(level_subdir)))
   {
     createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
     createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
@@ -1118,13 +1144,7 @@ TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
     return cloneTreeNode(node_top, node_parent, node->next,
                         skip_sets_without_levels);
 
-#if 1
   node_new = getTreeInfoCopy(node);            /* copy complete node */
-#else
-  node_new = newTreeInfo();
-
-  *node_new = *node;                           /* copy complete node */
-#endif
 
   node_new->node_top = node_top;               /* correct top node link */
   node_new->node_parent = node_parent;         /* correct parent node link */
@@ -1292,14 +1312,17 @@ void sortTreeInfo(TreeInfo **node_first)
 #define MODE_X_ALL             (S_IXUSR | S_IXGRP | S_IXOTH)
 
 #define MODE_W_PRIVATE         (S_IWUSR)
-#define MODE_W_PUBLIC          (S_IWUSR | S_IWGRP)
+#define MODE_W_PUBLIC_FILE     (S_IWUSR | S_IWGRP)
 #define MODE_W_PUBLIC_DIR      (S_IWUSR | S_IWGRP | S_ISGID)
 
 #define DIR_PERMS_PRIVATE      (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
 #define DIR_PERMS_PUBLIC       (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
+#define DIR_PERMS_PUBLIC_ALL   (MODE_R_ALL | MODE_X_ALL | MODE_W_ALL)
 
 #define FILE_PERMS_PRIVATE     (MODE_R_ALL | MODE_W_PRIVATE)
-#define FILE_PERMS_PUBLIC      (MODE_R_ALL | MODE_W_PUBLIC)
+#define FILE_PERMS_PUBLIC      (MODE_R_ALL | MODE_W_PUBLIC_FILE)
+#define FILE_PERMS_PUBLIC_ALL  (MODE_R_ALL | MODE_W_ALL)
+
 
 char *getHomeDir()
 {
@@ -1375,37 +1398,21 @@ char *getUserGameDataDir(void)
 {
   static char *user_game_data_dir = NULL;
 
+#if defined(PLATFORM_ANDROID)
+  if (user_game_data_dir == NULL)
+    user_game_data_dir = (char *)(SDL_AndroidGetExternalStorageState() &
+                                 SDL_ANDROID_EXTERNAL_STORAGE_WRITE ?
+                                 SDL_AndroidGetExternalStoragePath() :
+                                 SDL_AndroidGetInternalStoragePath());
+#else
   if (user_game_data_dir == NULL)
     user_game_data_dir = getPath2(getPersonalDataDir(),
                                  program.userdata_subdir);
+#endif
 
   return user_game_data_dir;
 }
 
-void updateUserGameDataDir()
-{
-#if defined(PLATFORM_MACOSX)
-  char *userdata_dir_old = getPath2(getHomeDir(), program.userdata_subdir_unix);
-  char *userdata_dir_new = getUserGameDataDir();       /* do not free() this */
-
-  /* convert old Unix style game data directory to Mac OS X style, if needed */
-  if (fileExists(userdata_dir_old) && !fileExists(userdata_dir_new))
-  {
-    if (rename(userdata_dir_old, userdata_dir_new) != 0)
-    {
-      Error(ERR_WARN, "cannot move game data directory '%s' to '%s'",
-           userdata_dir_old, userdata_dir_new);
-
-      /* continue using Unix style data directory -- this should not happen */
-      program.userdata_path = getPath2(getPersonalDataDir(),
-                                      program.userdata_subdir_unix);
-    }
-  }
-
-  free(userdata_dir_old);
-#endif
-}
-
 char *getSetupDir()
 {
   return getUserGameDataDir();
@@ -1440,6 +1447,9 @@ static boolean posix_process_running_setgid()
 
 void createDirectory(char *dir, char *text, int permission_class)
 {
+  if (directoryExists(dir))
+    return;
+
   /* leave "other" permissions in umask untouched, but ensure group parts
      of USERDATA_DIR_MODE are not masked */
   mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
@@ -1448,18 +1458,20 @@ void createDirectory(char *dir, char *text, int permission_class)
   mode_t group_umask = ~(dir_mode & S_IRWXG);
   int running_setgid = posix_process_running_setgid();
 
-  /* if we're setgid, protect files against "other" */
-  /* else keep umask(0) to make the dir world-writable */
+  if (permission_class == PERMS_PUBLIC)
+  {
+    /* if we're setgid, protect files against "other" */
+    /* else keep umask(0) to make the dir world-writable */
 
-  if (running_setgid)
-    posix_umask(last_umask & group_umask);
-  else
-    dir_mode |= MODE_W_ALL;
+    if (running_setgid)
+      posix_umask(last_umask & group_umask);
+    else
+      dir_mode = DIR_PERMS_PUBLIC_ALL;
+  }
 
-  if (!fileExists(dir))
-    if (posix_mkdir(dir, dir_mode) != 0)
-      Error(ERR_WARN, "cannot create %s directory '%s': %s",
-           text, dir, strerror(errno));
+  if (posix_mkdir(dir, dir_mode) != 0)
+    Error(ERR_WARN, "cannot create %s directory '%s': %s",
+         text, dir, strerror(errno));
 
   if (permission_class == PERMS_PUBLIC && !running_setgid)
     chmod(dir, dir_mode);
@@ -1479,7 +1491,7 @@ void SetFilePermissions(char *filename, int permission_class)
               FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
 
   if (permission_class == PERMS_PUBLIC && !running_setgid)
-    perms |= MODE_W_ALL;
+    perms = FILE_PERMS_PUBLIC_ALL;
 
   chmod(filename, perms);
 }
@@ -1499,6 +1511,17 @@ char *getCookie(char *file_type)
   return cookie;
 }
 
+void fprintFileHeader(FILE *file, char *basename)
+{
+  char *prefix = "# ";
+  char *sep1 = "=";
+
+  fprintf_line_with_prefix(file, prefix, sep1, 77);
+  fprintf(file, "%s%s\n", prefix, basename);
+  fprintf_line_with_prefix(file, prefix, sep1, 77);
+  fprintf(file, "\n");
+}
+
 int getFileVersionFromCookieString(const char *cookie)
 {
   const char *ptr_cookie1, *ptr_cookie2;
@@ -1546,6 +1569,7 @@ boolean checkCookieString(const char *cookie, const char *template)
   return TRUE;
 }
 
+
 /* ------------------------------------------------------------------------- */
 /* setup file list and hash handling functions                               */
 /* ------------------------------------------------------------------------- */
@@ -1637,7 +1661,7 @@ SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
     return addListEntry(list->next, token, value);
 }
 
-#if 0
+#if ENABLE_UNUSED_CODE
 #ifdef DEBUG
 static void printSetupFileList(SetupFileList *list)
 {
@@ -1749,7 +1773,8 @@ char *removeHashEntry(SetupFileHash *hash, char *token)
   return remove_hash_entry(hash, token);
 }
 
-#if 0
+#if ENABLE_UNUSED_CODE
+#if DEBUG
 static void printSetupFileHash(SetupFileHash *hash)
 {
   BEGIN_HASH_ITERATION(hash, itr)
@@ -1760,6 +1785,7 @@ static void printSetupFileHash(SetupFileHash *hash)
   END_HASH_ITERATION(hash, itr)
 }
 #endif
+#endif
 
 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE           1
 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING           0
@@ -1825,12 +1851,8 @@ static boolean getTokenValueFromSetupLineExt(char *line,
   /* find end of token to determine start of value */
   for (line_ptr = token; *line_ptr; line_ptr++)
   {
-#if 1
     /* first look for an explicit token/value separator, like ':' or '=' */
     if (*line_ptr == ':' || *line_ptr == '=')
-#else
-    if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
-#endif
     {
       *line_ptr = '\0';                        /* terminate token string */
       value = line_ptr + 1;            /* set beginning of value */
@@ -1897,11 +1919,6 @@ static boolean getTokenValueFromSetupLineExt(char *line,
     if (*value != ' ' && *value != '\t')
       break;
 
-#if 0
-  if (*value == '\0')
-    value = "true";    /* treat tokens without value as "true" */
-#endif
-
   *token_ptr = token;
   *value_ptr = value;
 
@@ -1917,9 +1934,6 @@ boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
   return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
 }
 
-#if 1
-
-#if 1
 static boolean loadSetupFileData(void *setup_file_data, char *filename,
                                 boolean top_recursion_level, boolean is_hash)
 {
@@ -1939,10 +1953,6 @@ static boolean loadSetupFileData(void *setup_file_data, char *filename,
   token_already_exists_warning = FALSE;
 #endif
 
-#if 0
-  Error(ERR_INFO, "===== opening file: '%s'", filename);
-#endif
-
   if (!(file = openFile(filename, MODE_READ)))
   {
     Error(ERR_WARN, "cannot open configuration file '%s'", filename);
@@ -1950,10 +1960,6 @@ static boolean loadSetupFileData(void *setup_file_data, char *filename,
     return FALSE;
   }
 
-#if 0
-  Error(ERR_INFO, "===== reading file: '%s'", filename);
-#endif
-
   /* use "insert pointer" to store list end for constant insertion complexity */
   if (!is_hash)
     insert_ptr = setup_file_data;
@@ -1971,10 +1977,6 @@ static boolean loadSetupFileData(void *setup_file_data, char *filename,
     if (!getStringFromFile(file, line, MAX_LINE_LEN))
       break;
 
-#if 0
-    Error(ERR_INFO, "got line: '%s'", line);
-#endif
-
     /* check if line was completely read and is terminated by line break */
     if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
       line_nr++;
@@ -1989,14 +1991,6 @@ static boolean loadSetupFileData(void *setup_file_data, char *filename,
 
     if (read_continued_line)
     {
-#if 0
-      /* !!! ??? WHY ??? !!! */
-      /* cut leading whitespaces from input line */
-      for (line_ptr = line; *line_ptr; line_ptr++)
-       if (*line_ptr != ' ' && *line_ptr != '\t')
-         break;
-#endif
-
       /* 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);
@@ -2031,10 +2025,6 @@ static boolean loadSetupFileData(void *setup_file_data, char *filename,
          char *basename = getBaseName(value);
          char *filename_include = getPath2(basepath, basename);
 
-#if 0
-         Error(ERR_INFO, "[including file '%s']", filename_include);
-#endif
-
          loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
 
          free(basepath);
@@ -2106,518 +2096,96 @@ static boolean loadSetupFileData(void *setup_file_data, char *filename,
   return TRUE;
 }
 
-#else
-
-static boolean loadSetupFileData(void *setup_file_data, char *filename,
-                                boolean top_recursion_level, boolean is_hash)
+void saveSetupFileHash(SetupFileHash *hash, char *filename)
 {
-  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 = fopen(filename, MODE_READ)))
+  if (!(file = fopen(filename, MODE_WRITE)))
   {
-    Error(ERR_WARN, "cannot open configuration file '%s'", filename);
+    Error(ERR_WARN, "cannot write configuration file '%s'", filename);
 
-    return FALSE;
+    return;
   }
 
-  /* use "insert pointer" to store list end for constant insertion complexity */
-  if (!is_hash)
-    insert_ptr = setup_file_data;
+  BEGIN_HASH_ITERATION(hash, itr)
+  {
+    fprintf(file, "%s\n", getFormattedSetupEntry(HASH_ITERATION_TOKEN(itr),
+                                                HASH_ITERATION_VALUE(itr)));
+  }
+  END_HASH_ITERATION(hash, itr)
 
-  /* on top invocation, create hash to mark included files (to prevent loops) */
-  if (top_recursion_level)
-    include_filename_hash = newSetupFileHash();
+  fclose(file);
+}
 
-  /* mark this file as already included (to prevent including it again) */
-  setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
+SetupFileList *loadSetupFileList(char *filename)
+{
+  SetupFileList *setup_file_list = newSetupFileList("", "");
+  SetupFileList *first_valid_list_entry;
 
-  while (!feof(file))
+  if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
   {
-    /* read next line of input file */
-    if (!fgets(line, MAX_LINE_LEN, file))
-      break;
+    freeSetupFileList(setup_file_list);
 
-    /* check if line was completely read and is terminated by line break */
-    if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
-      line_nr++;
+    return NULL;
+  }
 
-    /* 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';
+  first_valid_list_entry = setup_file_list->next;
 
-    /* copy raw input line for later use (mainly debugging output) */
-    strcpy(line_raw, line);
+  /* free empty list header */
+  setup_file_list->next = NULL;
+  freeSetupFileList(setup_file_list);
 
-    if (read_continued_line)
-    {
-#if 0
-      /* !!! ??? WHY ??? !!! */
-      /* cut leading whitespaces from input line */
-      for (line_ptr = line; *line_ptr; line_ptr++)
-       if (*line_ptr != ' ' && *line_ptr != '\t')
-         break;
-#endif
+  return first_valid_list_entry;
+}
 
-      /* 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);
+SetupFileHash *loadSetupFileHash(char *filename)
+{
+  SetupFileHash *setup_file_hash = newSetupFileHash();
 
-      strcpy(line, previous_line);     /* copy storage buffer to line */
+  if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
+  {
+    freeSetupFileHash(setup_file_hash);
 
-      read_continued_line = FALSE;
-    }
+    return NULL;
+  }
 
-    /* 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 */
+  return setup_file_hash;
+}
 
-      read_continued_line = TRUE;
 
-      continue;
-    }
+/* ========================================================================= */
+/* setup file stuff                                                          */
+/* ========================================================================= */
 
-    if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
-                                      line_raw, line_nr, FALSE))
-      continue;
+#define TOKEN_STR_LAST_LEVEL_SERIES            "last_level_series"
+#define TOKEN_STR_LAST_PLAYED_LEVEL            "last_played_level"
+#define TOKEN_STR_HANDICAP_LEVEL               "handicap_level"
 
-    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);
-
-#if 0
-         Error(ERR_INFO, "[including file '%s']", filename_include);
-#endif
-
-         loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
-
-         free(basepath);
-         free(basename);
-         free(filename_include);
-
-         include_count++;
-       }
-       else
-       {
-         Error(ERR_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)
-           {
-             Error(ERR_INFO_LINE, "-");
-             Error(ERR_WARN, "duplicate token(s) found in config file:");
-             Error(ERR_INFO, "- config file: '%s'", filename);
-
-             token_already_exists_warning = TRUE;
-           }
-
-           Error(ERR_INFO, "- token: '%s' (in line %d)", token, line_nr);
-           Error(ERR_INFO, "  old value: '%s'", old_value);
-           Error(ERR_INFO, "  new value: '%s'", value);
-         }
-#endif
-
-         setHashEntry((SetupFileHash *)setup_file_data, token, value);
-       }
-       else
-       {
-         insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
-       }
-
-       token_count++;
-      }
-    }
-  }
-
-  fclose(file);
-
-#if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
-  if (token_value_separator_warning)
-    Error(ERR_INFO_LINE, "-");
-#endif
-
-#if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
-  if (token_already_exists_warning)
-    Error(ERR_INFO_LINE, "-");
-#endif
-
-  if (token_count == 0 && include_count == 0)
-    Error(ERR_WARN, "configuration file '%s' is empty", filename);
-
-  if (top_recursion_level)
-    freeSetupFileHash(include_filename_hash);
-
-  return TRUE;
-}
-
-#endif
-
-#else
-
-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;
-  int token_count = 0;
-
-#if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
-  token_value_separator_warning = FALSE;
-#endif
-
-  if (!(file = fopen(filename, MODE_READ)))
-  {
-    Error(ERR_WARN, "cannot open configuration file '%s'", filename);
-
-    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 (!feof(file))
-  {
-    /* read next line of input file */
-    if (!fgets(line, MAX_LINE_LEN, file))
-      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)
-    {
-      /* cut leading whitespaces from input line */
-      for (line_ptr = line; *line_ptr; line_ptr++)
-       if (*line_ptr != ' ' && *line_ptr != '\t')
-         break;
-
-      /* 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;
-    }
-
-    /* cut trailing comment from input line */
-    for (line_ptr = line; *line_ptr; line_ptr++)
-    {
-      if (*line_ptr == '#')
-      {
-       *line_ptr = '\0';
-       break;
-      }
-    }
-
-    /* cut trailing whitespaces from input line */
-    for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
-      if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
-       *line_ptr = '\0';
-
-    /* ignore empty lines */
-    if (*line == '\0')
-      continue;
-
-    /* cut leading whitespaces from token */
-    for (token = line; *token; token++)
-      if (*token != ' ' && *token != '\t')
-       break;
-
-    /* start with empty value as reliable default */
-    value = "";
-
-    token_value_separator_found = FALSE;
-
-    /* find end of token to determine start of value */
-    for (line_ptr = token; *line_ptr; line_ptr++)
-    {
-#if 1
-      /* first look for an explicit token/value separator, like ':' or '=' */
-      if (*line_ptr == ':' || *line_ptr == '=')
-#else
-      if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
-#endif
-      {
-       *line_ptr = '\0';               /* terminate token string */
-       value = line_ptr + 1;           /* set beginning of value */
-
-       token_value_separator_found = TRUE;
-
-       break;
-      }
-    }
-
-#if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
-    /* fallback: if no token/value separator found, also allow whitespaces */
-    if (!token_value_separator_found)
-    {
-      for (line_ptr = token; *line_ptr; line_ptr++)
-      {
-       if (*line_ptr == ' ' || *line_ptr == '\t')
-       {
-         *line_ptr = '\0';             /* terminate token string */
-         value = line_ptr + 1;         /* set beginning of value */
-
-         token_value_separator_found = TRUE;
-
-         break;
-       }
-      }
-
-#if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
-      if (token_value_separator_found)
-      {
-       if (!token_value_separator_warning)
-       {
-         Error(ERR_INFO_LINE, "-");
-         Error(ERR_WARN, "missing token/value separator(s) in config file:");
-         Error(ERR_INFO, "- config file: '%s'", filename);
-
-         token_value_separator_warning = TRUE;
-       }
-
-       Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
-      }
-#endif
-    }
-#endif
-
-    /* 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;
-
-#if 0
-    if (*value == '\0')
-      value = "true";  /* treat tokens without value as "true" */
-#endif
-
-    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);
-
-#if 0
-         Error(ERR_INFO, "[including file '%s']", filename_include);
-#endif
-
-         loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
-
-         free(basepath);
-         free(basename);
-         free(filename_include);
-       }
-       else
-       {
-         Error(ERR_WARN, "ignoring already processed file '%s'", value);
-       }
-      }
-      else
-      {
-       if (is_hash)
-         setHashEntry((SetupFileHash *)setup_file_data, token, value);
-       else
-         insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
-
-       token_count++;
-      }
-    }
-  }
-
-  fclose(file);
-
-#if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
-  if (token_value_separator_warning)
-    Error(ERR_INFO_LINE, "-");
-#endif
-
-  if (token_count == 0)
-    Error(ERR_WARN, "configuration file '%s' is empty", filename);
-
-  if (top_recursion_level)
-    freeSetupFileHash(include_filename_hash);
-
-  return TRUE;
-}
-#endif
-
-void saveSetupFileHash(SetupFileHash *hash, char *filename)
-{
-  FILE *file;
-
-  if (!(file = fopen(filename, MODE_WRITE)))
-  {
-    Error(ERR_WARN, "cannot write configuration file '%s'", filename);
-
-    return;
-  }
-
-  BEGIN_HASH_ITERATION(hash, itr)
-  {
-    fprintf(file, "%s\n", getFormattedSetupEntry(HASH_ITERATION_TOKEN(itr),
-                                                HASH_ITERATION_VALUE(itr)));
-  }
-  END_HASH_ITERATION(hash, itr)
-
-  fclose(file);
-}
-
-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;
-}
-
-void checkSetupFileHashIdentifier(SetupFileHash *setup_file_hash,
-                                 char *filename, char *identifier)
-{
-  char *value = getHashEntry(setup_file_hash, TOKEN_STR_FILE_IDENTIFIER);
-
-  if (value == NULL)
-    Error(ERR_WARN, "config file '%s' has no file identifier", filename);
-  else if (!checkCookieString(value, identifier))
-    Error(ERR_WARN, "config file '%s' has wrong file identifier", filename);
-}
-
-
-/* ========================================================================= */
-/* setup file stuff                                                          */
-/* ========================================================================= */
-
-#define TOKEN_STR_LAST_LEVEL_SERIES            "last_level_series"
-#define TOKEN_STR_LAST_PLAYED_LEVEL            "last_played_level"
-#define TOKEN_STR_HANDICAP_LEVEL               "handicap_level"
-
-/* 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_IMPORTED_FROM          5
-#define LEVELINFO_TOKEN_IMPORTED_BY            6
-#define LEVELINFO_TOKEN_TESTED_BY              7
-#define LEVELINFO_TOKEN_LEVELS                 8
-#define LEVELINFO_TOKEN_FIRST_LEVEL            9
-#define LEVELINFO_TOKEN_SORT_PRIORITY          10
-#define LEVELINFO_TOKEN_LATEST_ENGINE          11
-#define LEVELINFO_TOKEN_LEVEL_GROUP            12
-#define LEVELINFO_TOKEN_READONLY               13
-#define LEVELINFO_TOKEN_GRAPHICS_SET_ECS       14
-#define LEVELINFO_TOKEN_GRAPHICS_SET_AGA       15
-#define LEVELINFO_TOKEN_GRAPHICS_SET           16
-#define LEVELINFO_TOKEN_SOUNDS_SET             17
-#define LEVELINFO_TOKEN_MUSIC_SET              18
-#define LEVELINFO_TOKEN_FILENAME               19
-#define LEVELINFO_TOKEN_FILETYPE               20
-#define LEVELINFO_TOKEN_SPECIAL_FLAGS          21
-#define LEVELINFO_TOKEN_HANDICAP               22
-#define LEVELINFO_TOKEN_SKIP_LEVELS            23
+/* 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_IMPORTED_FROM          5
+#define LEVELINFO_TOKEN_IMPORTED_BY            6
+#define LEVELINFO_TOKEN_TESTED_BY              7
+#define LEVELINFO_TOKEN_LEVELS                 8
+#define LEVELINFO_TOKEN_FIRST_LEVEL            9
+#define LEVELINFO_TOKEN_SORT_PRIORITY          10
+#define LEVELINFO_TOKEN_LATEST_ENGINE          11
+#define LEVELINFO_TOKEN_LEVEL_GROUP            12
+#define LEVELINFO_TOKEN_READONLY               13
+#define LEVELINFO_TOKEN_GRAPHICS_SET_ECS       14
+#define LEVELINFO_TOKEN_GRAPHICS_SET_AGA       15
+#define LEVELINFO_TOKEN_GRAPHICS_SET           16
+#define LEVELINFO_TOKEN_SOUNDS_SET             17
+#define LEVELINFO_TOKEN_MUSIC_SET              18
+#define LEVELINFO_TOKEN_FILENAME               19
+#define LEVELINFO_TOKEN_FILETYPE               20
+#define LEVELINFO_TOKEN_SPECIAL_FLAGS          21
+#define LEVELINFO_TOKEN_HANDICAP               22
+#define LEVELINFO_TOKEN_SKIP_LEVELS            23
 
 #define NUM_LEVELINFO_TOKENS                   24
 
@@ -2804,11 +2372,7 @@ static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
     ti->last_level = 0;
     ti->level_group = FALSE;
     ti->handicap_level = 0;
-#if 1
     ti->readonly = parent->readonly;
-#else
-    ti->readonly = TRUE;
-#endif
     ti->handicap = TRUE;
     ti->skip_levels = FALSE;
   }
@@ -2921,6 +2485,14 @@ void freeTreeInfo(TreeInfo *ti)
     checked_free(ti->special_flags);
   }
 
+  // recursively free child node
+  if (ti->node_group)
+    freeTreeInfo(ti->node_group);
+
+  // recursively free next node
+  if (ti->next)
+    freeTreeInfo(ti->next);
+
   checked_free(ti);
 }
 
@@ -3007,12 +2579,12 @@ static int compareTreeInfoEntries(const void *object1, const void *object2)
   return compare_result;
 }
 
-static void createParentTreeInfoNode(TreeInfo *node_parent)
+static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
 {
   TreeInfo *ti_new;
 
   if (node_parent == NULL)
-    return;
+    return NULL;
 
   ti_new = newTreeInfo();
   setTreeInfoToDefaults(ti_new, node_parent->type);
@@ -3024,7 +2596,7 @@ static void createParentTreeInfoNode(TreeInfo *node_parent)
   setString(&ti_new->name, ".. (parent directory)");
   setString(&ti_new->name_sorting, ti_new->name);
 
-  setString(&ti_new->subdir, "..");
+  setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
   setString(&ti_new->fullpath, node_parent->fullpath);
 
   ti_new->sort_priority = node_parent->sort_priority;
@@ -3033,6 +2605,44 @@ static void createParentTreeInfoNode(TreeInfo *node_parent)
   setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
 
   pushTreeInfo(&node_parent->node_group, ti_new);
+
+  return ti_new;
+}
+
+static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
+{
+  TreeInfo *ti_new, *ti_new2;
+
+  if (node_first == NULL)
+    return NULL;
+
+  ti_new = newTreeInfo();
+  setTreeInfoToDefaults(ti_new, TREE_TYPE_LEVEL_DIR);
+
+  ti_new->node_parent = NULL;
+  ti_new->parent_link = FALSE;
+
+  setString(&ti_new->identifier, node_first->identifier);
+  setString(&ti_new->name, "level sets");
+  setString(&ti_new->name_sorting, ti_new->name);
+
+  setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
+  setString(&ti_new->fullpath, node_first->fullpath);
+
+  ti_new->sort_priority = node_first->sort_priority;;
+  ti_new->latest_engine = node_first->latest_engine;
+
+  setString(&ti_new->class_desc, "level sets");
+
+  ti_new->node_group = node_first;
+  ti_new->level_group = TRUE;
+
+  ti_new2 = createParentTreeInfoNode(ti_new);
+
+  setString(&ti_new2->name, ".. (main menu)");
+  setString(&ti_new2->name_sorting, ti_new2->name);
+
+  return ti_new;
 }
 
 
@@ -3098,16 +2708,7 @@ static char *getCacheToken(char *prefix, char *suffix)
 
 static char *getFileTimestampString(char *filename)
 {
-#if 1
   return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
-#else
-  struct stat file_status;
-
-  if (stat(filename, &file_status) != 0)       /* cannot stat file */
-    return getStringCopy(i_to_a(0));
-
-  return getStringCopy(i_to_a(file_status.st_mtime));
-#endif
 }
 
 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
@@ -3155,9 +2756,7 @@ static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
       /* check if cache entry for this item is invalid or incomplete */
       if (value == NULL)
       {
-#if 1
        Error(ERR_WARN, "cache entry '%s' invalid", token);
-#endif
 
        cached = FALSE;
       }
@@ -3187,11 +2786,6 @@ static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
     if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
       cached = FALSE;
 
-#if 0
-    if (!cached)
-      printf("::: '%s': INVALIDATED FROM CACHE BY TIMESTAMP\n", identifier);
-#endif
-
     checked_free(filename_levelinfo);
     checked_free(filename_artworkinfo);
   }
@@ -3263,10 +2857,6 @@ static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
                                          char *level_directory,
                                          char *directory_name)
 {
-#if 0
-  static unsigned int progress_delay = 0;
-  unsigned int progress_delay_value = 100;     /* (in milliseconds) */
-#endif
   char *directory_path = getPath2(level_directory, directory_name);
   char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
   SetupFileHash *setup_file_hash;
@@ -3303,9 +2893,6 @@ static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
 
   leveldir_new->subdir = getStringCopy(directory_name);
 
-  checkSetupFileHashIdentifier(setup_file_hash, filename,
-                              getCookie("LEVELINFO"));
-
   /* set all structure fields according to the token/value pairs */
   ldi = *leveldir_new;
   for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
@@ -3333,23 +2920,12 @@ static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
     leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
   }
 
-#if 0
-  if (leveldir_new->levels < 1)
-    leveldir_new->levels = 1;
-#endif
-
   leveldir_new->last_level =
     leveldir_new->first_level + leveldir_new->levels - 1;
 
   leveldir_new->in_user_dir =
     (!strEqual(leveldir_new->basepath, options.level_directory));
 
-#if 0
-  printf("::: '%s' -> %d\n",
-        leveldir_new->identifier,
-        leveldir_new->in_user_dir);
-#endif
-
   /* adjust some settings if user's private level directory was detected */
   if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
       leveldir_new->in_user_dir &&
@@ -3372,34 +2948,7 @@ static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
     (leveldir_new->user_defined || !leveldir_new->handicap ?
      leveldir_new->last_level : leveldir_new->first_level);
 
-#if 1
-#if 1
-  DrawInitTextExt(leveldir_new->name, 150, FC_YELLOW,
-                 leveldir_new->level_group);
-#else
-  if (leveldir_new->level_group ||
-      DelayReached(&progress_delay, progress_delay_value))
-    DrawInitText(leveldir_new->name, 150, FC_YELLOW);
-#endif
-#else
   DrawInitText(leveldir_new->name, 150, FC_YELLOW);
-#endif
-
-#if 0
-  /* !!! don't skip sets without levels (else artwork base sets are missing) */
-#if 1
-  if (leveldir_new->levels < 1 && !leveldir_new->level_group)
-  {
-    /* skip level sets without levels (which are probably artwork base sets) */
-
-    freeSetupFileHash(setup_file_hash);
-    free(directory_path);
-    free(filename);
-
-    return FALSE;
-  }
-#endif
-#endif
 
   pushTreeInfo(node_first, leveldir_new);
 
@@ -3421,7 +2970,6 @@ static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
   return TRUE;
 }
 
-#if 1
 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
                                      TreeInfo *node_parent,
                                      char *level_directory)
@@ -3430,10 +2978,6 @@ static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
   DirectoryEntry *dir_entry;
   boolean valid_entry_found = FALSE;
 
-#if 0
-  Error(ERR_INFO, "looking for levels in '%s' ...", level_directory);
-#endif
-
   if ((dir = openDirectory(level_directory)) == NULL)
   {
     Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
@@ -3441,20 +2985,11 @@ static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
     return;
   }
 
-#if 0
-  Error(ERR_INFO, "opening '%s' succeeded ...", level_directory);
-#endif
-
   while ((dir_entry = readDirectory(dir)) != NULL)     /* loop all entries */
   {
-    struct stat file_status;
     char *directory_name = dir_entry->basename;
     char *directory_path = getPath2(level_directory, directory_name);
 
-#if 0
-    Error(ERR_INFO, "checking entry '%s' ...", directory_name);
-#endif
-
     /* skip entries for current and parent directory */
     if (strEqual(directory_name, ".") ||
        strEqual(directory_name, ".."))
@@ -3464,7 +2999,6 @@ static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
       continue;
     }
 
-#if 1
     /* find out if directory entry is itself a directory */
     if (!dir_entry->is_directory)                      /* not a directory */
     {
@@ -3472,16 +3006,6 @@ static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
 
       continue;
     }
-#else
-    /* 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;
-    }
-#endif
 
     free(directory_path);
 
@@ -3510,85 +3034,6 @@ static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
          level_directory);
 }
 
-#else
-
-static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
-                                     TreeInfo *node_parent,
-                                     char *level_directory)
-{
-  DIR *dir;
-  struct dirent *dir_entry;
-  boolean valid_entry_found = FALSE;
-
-#if 1
-  Error(ERR_INFO, "looking for levels in '%s' ...", level_directory);
-#endif
-
-  if ((dir = opendir(level_directory)) == NULL)
-  {
-    Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
-
-    return;
-  }
-
-#if 1
-  Error(ERR_INFO, "opening '%s' succeeded ...", level_directory);
-#endif
-
-  while ((dir_entry = readdir(dir)) != NULL)   /* loop until last dir entry */
-  {
-    struct stat file_status;
-    char *directory_name = dir_entry->d_name;
-    char *directory_path = getPath2(level_directory, directory_name);
-
-#if 1
-    Error(ERR_INFO, "checking entry '%s' ...", directory_name);
-#endif
-
-    /* skip entries for current and parent directory */
-    if (strEqual(directory_name, ".") ||
-       strEqual(directory_name, ".."))
-    {
-      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;
-    }
-
-    free(directory_path);
-
-    if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
-       strEqual(directory_name, SOUNDS_DIRECTORY) ||
-       strEqual(directory_name, MUSIC_DIRECTORY))
-      continue;
-
-    valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
-                                                   level_directory,
-                                                   directory_name);
-  }
-
-  closedir(dir);
-
-  /* special case: top level directory may directly contain "levelinfo.conf" */
-  if (node_parent == NULL && !valid_entry_found)
-  {
-    /* check if this directory directly contains a file "levelinfo.conf" */
-    valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
-                                                   level_directory, ".");
-  }
-
-  if (!valid_entry_found)
-    Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
-         level_directory);
-}
-#endif
-
 boolean AdjustGraphicsForEMC()
 {
   boolean settings_changed = FALSE;
@@ -3608,6 +3053,8 @@ void LoadLevelInfo()
   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
 
+  leveldir_first = createTopTreeInfoNode(leveldir_first);
+
   /* after loading all level set information, clone the level directory tree
      and remove all level sets without levels (these may still contain artwork
      to be offered in the setup menu as "custom artwork", and are therefore
@@ -3625,7 +3072,7 @@ void LoadLevelInfo()
 
   sortTreeInfo(&leveldir_first);
 
-#if 0
+#if ENABLE_UNUSED_CODE
   dumpTreeInfo(leveldir_first, 0);
 #endif
 }
@@ -3646,24 +3093,23 @@ static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
 
   if (setup_file_hash == NULL) /* no config file -- look for artwork files */
   {
-    DIR *dir;
-    struct dirent *dir_entry;
+    Directory *dir;
+    DirectoryEntry *dir_entry;
     boolean valid_file_found = FALSE;
 
-    if ((dir = opendir(directory_path)) != NULL)
+    if ((dir = openDirectory(directory_path)) != NULL)
     {
-      while ((dir_entry = readdir(dir)) != NULL)
+      while ((dir_entry = readDirectory(dir)) != NULL)
       {
-       char *entry_name = dir_entry->d_name;
-
-       if (FileIsArtworkType(entry_name, type))
+       if (FileIsArtworkType(dir_entry->filename, type))
        {
          valid_file_found = TRUE;
+
          break;
        }
       }
 
-      closedir(dir);
+      closeDirectory(dir);
     }
 
     if (!valid_file_found)
@@ -3689,10 +3135,6 @@ static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
 
   if (setup_file_hash) /* (before defining ".color" and ".class_desc") */
   {
-#if 0
-    checkSetupFileHashIdentifier(setup_file_hash, filename, getCookie("..."));
-#endif
-
     /* set all structure fields according to the token/value pairs */
     ldi = *artwork_new;
     for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
@@ -3759,10 +3201,6 @@ static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
     setString(&artwork_new->name_sorting, artwork_new->name);
   }
 
-#if 0
-  DrawInitText(artwork_new->name, 150, FC_YELLOW);
-#endif
-
   pushTreeInfo(node_first, artwork_new);
 
   freeSetupFileHash(setup_file_hash);
@@ -3777,11 +3215,11 @@ static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
                                          TreeInfo *node_parent,
                                          char *base_directory, int type)
 {
-  DIR *dir;
-  struct dirent *dir_entry;
+  Directory *dir;
+  DirectoryEntry *dir_entry;
   boolean valid_entry_found = FALSE;
 
-  if ((dir = opendir(base_directory)) == NULL)
+  if ((dir = openDirectory(base_directory)) == NULL)
   {
     /* display error if directory is main "options.graphics_directory" etc. */
     if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
@@ -3790,10 +3228,9 @@ static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
     return;
   }
 
-  while ((dir_entry = readdir(dir)) != NULL)   /* loop until last dir entry */
+  while ((dir_entry = readDirectory(dir)) != NULL)     /* loop all entries */
   {
-    struct stat file_status;
-    char *directory_name = dir_entry->d_name;
+    char *directory_name = dir_entry->basename;
     char *directory_path = getPath2(base_directory, directory_name);
 
     /* skip directory entries for current and parent directory */
@@ -3801,14 +3238,15 @@ static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
        strEqual(directory_name, ".."))
     {
       free(directory_path);
+
       continue;
     }
 
-    /* skip directory entries which are not a directory or are not accessible */
-    if (stat(directory_path, &file_status) != 0 ||     /* cannot stat file */
-       (file_status.st_mode & S_IFMT) != S_IFDIR)      /* not a directory */
+    /* skip directory entries which are not a directory */
+    if (!dir_entry->is_directory)                      /* not a directory */
     {
       free(directory_path);
+
       continue;
     }
 
@@ -3820,7 +3258,7 @@ static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
                                                        directory_name, type);
   }
 
-  closedir(dir);
+  closeDirectory(dir);
 
   /* check if this directory directly contains artwork itself */
   valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
@@ -3912,7 +3350,7 @@ void LoadArtworkInfo()
   artwork.snd_current_identifier = artwork.snd_current->identifier;
   artwork.mus_current_identifier = artwork.mus_current->identifier;
 
-#if 0
+#if ENABLE_UNUSED_CODE
   printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
   printf("sounds set == %s\n\n", artwork.snd_current_identifier);
   printf("music set == %s\n\n", artwork.mus_current_identifier);
@@ -3922,7 +3360,7 @@ void LoadArtworkInfo()
   sortTreeInfo(&artwork.snd_first);
   sortTreeInfo(&artwork.mus_first);
 
-#if 0
+#if ENABLE_UNUSED_CODE
   dumpTreeInfo(artwork.gfx_first, 0);
   dumpTreeInfo(artwork.snd_first, 0);
   dumpTreeInfo(artwork.mus_first, 0);
@@ -3932,10 +3370,6 @@ void LoadArtworkInfo()
 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
                                  LevelDirTree *level_node)
 {
-#if 0
-  static unsigned int progress_delay = 0;
-  unsigned int progress_delay_value = 100;     /* (in milliseconds) */
-#endif
   int type = (*artwork_node)->type;
 
   /* recursively check all level directories for artwork sub-directories */
@@ -3980,14 +3414,7 @@ void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
        setArtworkInfoCacheEntry(artwork_new, level_node, type);
     }
 
-#if 1
-    DrawInitTextExt(level_node->name, 150, FC_YELLOW,
-                   level_node->level_group);
-#else
-    if (level_node->level_group ||
-       DelayReached(&progress_delay, progress_delay_value))
-      DrawInitText(level_node->name, 150, FC_YELLOW);
-#endif
+    DrawInitText(level_node->name, 150, FC_YELLOW);
 
     if (level_node->node_group != NULL)
       LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
@@ -3998,14 +3425,23 @@ void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
 
 void LoadLevelArtworkInfo()
 {
+  print_timestamp_init("LoadLevelArtworkInfo");
+
   DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
 
+  print_timestamp_time("DrawTimeText");
+
   LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
+  print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
   LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
+  print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
   LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
+  print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
 
   SaveArtworkInfoCache();
 
+  print_timestamp_time("SaveArtworkInfoCache");
+
   /* needed for reloading level artwork not known at ealier stage */
 
   if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
@@ -4041,15 +3477,21 @@ void LoadLevelArtworkInfo()
       artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
   }
 
+  print_timestamp_time("getTreeInfoFromIdentifier");
+
   sortTreeInfo(&artwork.gfx_first);
   sortTreeInfo(&artwork.snd_first);
   sortTreeInfo(&artwork.mus_first);
 
-#if 0
+  print_timestamp_time("sortTreeInfo");
+
+#if ENABLE_UNUSED_CODE
   dumpTreeInfo(artwork.gfx_first, 0);
   dumpTreeInfo(artwork.snd_first, 0);
   dumpTreeInfo(artwork.mus_first, 0);
 #endif
+
+  print_timestamp_done("LoadLevelArtworkInfo");
 }
 
 static void SaveUserLevelInfo()
@@ -4080,8 +3522,7 @@ static void SaveUserLevelInfo()
 
   token_value_position = TOKEN_VALUE_POSITION_SHORT;
 
-  fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
-                                                getCookie("LEVELINFO")));
+  fprintFileHeader(file, LEVELINFO_FILENAME);
 
   ldi = *level_info;
   for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
@@ -4222,12 +3663,13 @@ void LoadLevelSetup_LastSeries()
   /* always start with reliable default values */
   leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
 
-#if defined(CREATE_SPECIAL_EDITION_RND_JUE)
-  leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
-                                              "jue_start");
-  if (leveldir_current == NULL)
-    leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
-#endif
+  if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
+  {
+    leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
+                                                DEFAULT_LEVELSET);
+    if (leveldir_current == NULL)
+      leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
+  }
 
   if ((level_setup_hash = loadSetupFileHash(filename)))
   {
@@ -4239,9 +3681,6 @@ void LoadLevelSetup_LastSeries()
     if (leveldir_current == NULL)
       leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
 
-    checkSetupFileHashIdentifier(level_setup_hash, filename,
-                                getCookie("LEVELSETUP"));
-
     freeSetupFileHash(level_setup_hash);
   }
   else
@@ -4275,8 +3714,7 @@ static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
     return;
   }
 
-  fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
-                                                getCookie("LEVELSETUP")));
+  fprintFileHeader(file, LEVELSETUP_FILENAME);
 
   if (deactivate_last_level_series)
     fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
@@ -4301,15 +3739,10 @@ void SaveLevelSetup_LastSeries_Deactivate()
   SaveLevelSetup_LastSeries_Ext(TRUE);
 }
 
-#if 1
-
 static void checkSeriesInfo()
 {
   static char *level_directory = NULL;
   Directory *dir;
-#if 0
-  DirectoryEntry *dir_entry;
-#endif
 
   /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
 
@@ -4325,96 +3758,9 @@ static void checkSeriesInfo()
     return;
   }
 
-#if 0
-  while ((dir_entry = readDirectory(dir)) != NULL)   /* last directory entry */
-  {
-    if (strlen(dir_entry->basename) > 4 &&
-       dir_entry->basename[3] == '.' &&
-       strEqual(&dir_entry->basename[4], LEVELFILE_EXTENSION))
-    {
-      char levelnum_str[4];
-      int levelnum_value;
-
-      strncpy(levelnum_str, dir_entry->basename, 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;
-      }
-    }
-  }
-#endif
-
   closeDirectory(dir);
 }
 
-#else
-
-static void checkSeriesInfo()
-{
-  static char *level_directory = NULL;
-  DIR *dir;
-#if 0
-  struct dirent *dir_entry;
-#endif
-
-  /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
-
-  level_directory = getPath2((leveldir_current->in_user_dir ?
-                             getUserLevelDir(NULL) :
-                             options.level_directory),
-                            leveldir_current->fullpath);
-
-  if ((dir = opendir(level_directory)) == NULL)
-  {
-    Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
-
-    return;
-  }
-
-#if 0
-  while ((dir_entry = readdir(dir)) != NULL)   /* last directory entry */
-  {
-    if (strlen(dir_entry->d_name) > 4 &&
-       dir_entry->d_name[3] == '.' &&
-       strEqual(&dir_entry->d_name[4], LEVELFILE_EXTENSION))
-    {
-      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;
-      }
-    }
-  }
-#endif
-
-  closedir(dir);
-}
-
-#endif
-
 void LoadLevelSetup_SeriesInfo()
 {
   char *filename;
@@ -4431,7 +3777,7 @@ void LoadLevelSetup_SeriesInfo()
     LevelStats_setSolved(i, 0);
   }
 
-  checkSeriesInfo(leveldir_current);
+  checkSeriesInfo();
 
   /* ----------------------------------------------------------------------- */
   /* ~/.<program>/levelsetup/<level series>/levelsetup.conf                  */
@@ -4503,9 +3849,6 @@ void LoadLevelSetup_SeriesInfo()
     }
     END_HASH_ITERATION(hash, itr)
 
-    checkSetupFileHashIdentifier(level_setup_hash, filename,
-                                getCookie("LEVELSETUP"));
-
     freeSetupFileHash(level_setup_hash);
   }
   else
@@ -4538,8 +3881,8 @@ void SaveLevelSetup_SeriesInfo()
     return;
   }
 
-  fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
-                                                getCookie("LEVELSETUP")));
+  fprintFileHeader(file, LEVELSETUP_FILENAME);
+
   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
                                               level_nr_str));
   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,