removed unused option to set first level of user level sets not to be '1'
[rocksndiamonds.git] / src / libgame / setup.c
index 1e9a56e2e1917f2f2779fc8c2256a065d42be6bc..8662b204bb5f336ca426408be1b2fe841efa58a7 100644 (file)
@@ -1,21 +1,27 @@
-/***********************************************************
-* Artsoft Retro-Game Library                               *
-*----------------------------------------------------------*
-* (c) 1994-2002 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>
 #include <dirent.h>
 #include <string.h>
 #include <unistd.h>
 
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <dirent.h>
 #include <string.h>
 #include <unistd.h>
+#include <errno.h>
+
+#include "platform.h"
+
+#if !defined(PLATFORM_WIN32)
+#include <pwd.h>
+#include <sys/param.h>
+#endif
 
 #include "setup.h"
 #include "joystick.h"
 
 #include "setup.h"
 #include "joystick.h"
 #include "misc.h"
 #include "hash.h"
 
 #include "misc.h"
 #include "hash.h"
 
-/* file names and filename extensions */
-#if !defined(PLATFORM_MSDOS)
-#define LEVELSETUP_DIRECTORY   "levelsetup"
-#define SETUP_FILENAME         "setup.conf"
-#define LEVELSETUP_FILENAME    "levelsetup.conf"
-#define LEVELINFO_FILENAME     "levelinfo.conf"
-#define GRAPHICSINFO_FILENAME  "graphicsinfo.conf"
-#define SOUNDSINFO_FILENAME    "soundsinfo.conf"
-#define MUSICINFO_FILENAME     "musicinfo.conf"
-#define LEVELFILE_EXTENSION    "level"
-#define TAPEFILE_EXTENSION     "tape"
-#define SCOREFILE_EXTENSION    "score"
-#else
-#define LEVELSETUP_DIRECTORY   "lvlsetup"
-#define SETUP_FILENAME         "setup.cnf"
-#define LEVELSETUP_FILENAME    "lvlsetup.cnf"
-#define LEVELINFO_FILENAME     "lvlinfo.cnf"
-#define GRAPHICSINFO_FILENAME  "gfxinfo.cnf"
-#define SOUNDSINFO_FILENAME    "sndinfo.cnf"
-#define MUSICINFO_FILENAME     "musinfo.cnf"
-#define LEVELFILE_EXTENSION    "lvl"
-#define TAPEFILE_EXTENSION     "tap"
-#define SCOREFILE_EXTENSION    "sco"
-#endif
+
+#define ENABLE_UNUSED_CODE     FALSE   /* for currently unused functions */
 
 #define NUM_LEVELCLASS_DESC    8
 
 #define NUM_LEVELCLASS_DESC    8
+
 static char *levelclass_desc[NUM_LEVELCLASS_DESC] =
 {
   "Tutorial Levels",
 static char *levelclass_desc[NUM_LEVELCLASS_DESC] =
 {
   "Tutorial Levels",
@@ -61,72 +46,67 @@ static char *levelclass_desc[NUM_LEVELCLASS_DESC] =
   "DX Boulderdash"
 };
 
   "DX Boulderdash"
 };
 
-#define LEVELCOLOR(n)  (IS_LEVELCLASS_TUTORIAL(n) ?            FC_BLUE : \
-                        IS_LEVELCLASS_CLASSICS(n) ?            FC_RED : \
-                        IS_LEVELCLASS_BD(n) ?                  FC_GREEN : \
-                        IS_LEVELCLASS_EM(n) ?                  FC_YELLOW : \
-                        IS_LEVELCLASS_SP(n) ?                  FC_GREEN : \
-                        IS_LEVELCLASS_DX(n) ?                  FC_YELLOW : \
-                        IS_LEVELCLASS_CONTRIBUTION(n) ?        FC_GREEN : \
-                        IS_LEVELCLASS_USER(n) ?                FC_RED : \
+
+#define LEVELCOLOR(n)  (IS_LEVELCLASS_TUTORIAL(n) ?            FC_BLUE :    \
+                        IS_LEVELCLASS_CLASSICS(n) ?            FC_RED :     \
+                        IS_LEVELCLASS_BD(n) ?                  FC_YELLOW :  \
+                        IS_LEVELCLASS_EM(n) ?                  FC_YELLOW :  \
+                        IS_LEVELCLASS_SP(n) ?                  FC_YELLOW :  \
+                        IS_LEVELCLASS_DX(n) ?                  FC_YELLOW :  \
+                        IS_LEVELCLASS_SB(n) ?                  FC_YELLOW :  \
+                        IS_LEVELCLASS_CONTRIB(n) ?             FC_GREEN :   \
+                        IS_LEVELCLASS_PRIVATE(n) ?             FC_RED :     \
                         FC_BLUE)
 
                         FC_BLUE)
 
-#define LEVELSORTING(n)        (IS_LEVELCLASS_TUTORIAL(n) ?            0 : \
-                        IS_LEVELCLASS_CLASSICS(n) ?            1 : \
-                        IS_LEVELCLASS_BD(n) ?                  2 : \
-                        IS_LEVELCLASS_EM(n) ?                  3 : \
-                        IS_LEVELCLASS_SP(n) ?                  4 : \
-                        IS_LEVELCLASS_DX(n) ?                  5 : \
-                        IS_LEVELCLASS_CONTRIBUTION(n) ?        6 : \
-                        IS_LEVELCLASS_USER(n) ?                7 : \
+#define LEVELSORTING(n)        (IS_LEVELCLASS_TUTORIAL(n) ?            0 :     \
+                        IS_LEVELCLASS_CLASSICS(n) ?            1 :     \
+                        IS_LEVELCLASS_BD(n) ?                  2 :     \
+                        IS_LEVELCLASS_EM(n) ?                  3 :     \
+                        IS_LEVELCLASS_SP(n) ?                  4 :     \
+                        IS_LEVELCLASS_DX(n) ?                  5 :     \
+                        IS_LEVELCLASS_SB(n) ?                  6 :     \
+                        IS_LEVELCLASS_CONTRIB(n) ?             7 :     \
+                        IS_LEVELCLASS_PRIVATE(n) ?             8 :     \
                         9)
 
                         9)
 
-#define ARTWORKCOLOR(n)        (IS_ARTWORKCLASS_CLASSICS(n) ?          FC_RED : \
-                        IS_ARTWORKCLASS_CONTRIBUTION(n) ?      FC_YELLOW : \
-                        IS_ARTWORKCLASS_LEVEL(n) ?             FC_GREEN : \
-                        IS_ARTWORKCLASS_USER(n) ?              FC_RED : \
+#define ARTWORKCOLOR(n)        (IS_ARTWORKCLASS_CLASSICS(n) ?          FC_RED :     \
+                        IS_ARTWORKCLASS_CONTRIB(n) ?           FC_GREEN :   \
+                        IS_ARTWORKCLASS_PRIVATE(n) ?           FC_RED :     \
+                        IS_ARTWORKCLASS_LEVEL(n) ?             FC_YELLOW :  \
                         FC_BLUE)
 
                         FC_BLUE)
 
-#define ARTWORKSORTING(n) (IS_ARTWORKCLASS_CLASSICS(n) ?       0 : \
-                        IS_ARTWORKCLASS_CONTRIBUTION(n) ?      1 : \
-                        IS_ARTWORKCLASS_LEVEL(n) ?             2 : \
-                        IS_ARTWORKCLASS_USER(n) ?              3 : \
-                        9)
+#define ARTWORKSORTING(n) (IS_ARTWORKCLASS_CLASSICS(n) ?       0 :     \
+                          IS_ARTWORKCLASS_LEVEL(n) ?           1 :     \
+                          IS_ARTWORKCLASS_CONTRIB(n) ?         2 :     \
+                          IS_ARTWORKCLASS_PRIVATE(n) ?         3 :     \
+                          9)
+
+#define TOKEN_VALUE_POSITION_SHORT             32
+#define TOKEN_VALUE_POSITION_DEFAULT           40
+#define TOKEN_COMMENT_POSITION_DEFAULT         60
 
 
-#define TOKEN_VALUE_POSITION           40
-#define TOKEN_COMMENT_POSITION         60
+#define MAX_COOKIE_LEN                         256
 
 
-#define MAX_COOKIE_LEN                 256
 
 
-#define ARTWORKINFO_FILENAME(type)     ((type) == TREE_TYPE_GRAPHICS_DIR ? \
-                                        GRAPHICSINFO_FILENAME :            \
-                                        (type) == TREE_TYPE_SOUNDS_DIR ?   \
-                                        SOUNDSINFO_FILENAME :              \
-                                        (type) == TREE_TYPE_MUSIC_DIR ?    \
-                                        MUSICINFO_FILENAME : "")
+static void setTreeInfoToDefaults(TreeInfo *, int);
+static TreeInfo *getTreeInfoCopy(TreeInfo *ti);
+static int compareTreeInfoEntries(const void *, const void *);
 
 
-#define ARTWORK_DIRECTORY(type)                ((type) == TREE_TYPE_GRAPHICS_DIR ? \
-                                        GRAPHICS_DIRECTORY :               \
-                                        (type) == TREE_TYPE_SOUNDS_DIR ?   \
-                                        SOUNDS_DIRECTORY :                 \
-                                        (type) == TREE_TYPE_MUSIC_DIR ?    \
-                                        MUSIC_DIRECTORY : "")
+static int token_value_position   = TOKEN_VALUE_POSITION_DEFAULT;
+static int token_comment_position = TOKEN_COMMENT_POSITION_DEFAULT;
 
 
-#define OPTIONS_ARTWORK_DIRECTORY(type)        ((type) == TREE_TYPE_GRAPHICS_DIR ? \
-                                        options.graphics_directory :       \
-                                        (type) == TREE_TYPE_SOUNDS_DIR ?   \
-                                        options.sounds_directory :         \
-                                        (type) == TREE_TYPE_MUSIC_DIR ?    \
-                                        options.music_directory : "")
+static SetupFileHash *artworkinfo_cache_old = NULL;
+static SetupFileHash *artworkinfo_cache_new = NULL;
+static boolean use_artworkinfo_cache = TRUE;
 
 
 /* ------------------------------------------------------------------------- */
 /* file functions                                                            */
 /* ------------------------------------------------------------------------- */
 
 
 
 /* ------------------------------------------------------------------------- */
 /* file functions                                                            */
 /* ------------------------------------------------------------------------- */
 
-static char *getLevelClassDescription(TreeInfo *ldi)
+static char *getLevelClassDescription(TreeInfo *ti)
 {
 {
-  int position = ldi->sort_priority / 100;
+  int position = ti->sort_priority / 100;
 
   if (position >= 0 && position < NUM_LEVELCLASS_DESC)
     return levelclass_desc[position];
 
   if (position >= 0 && position < NUM_LEVELCLASS_DESC)
     return levelclass_desc[position];
@@ -137,11 +117,10 @@ static char *getLevelClassDescription(TreeInfo *ldi)
 static char *getUserLevelDir(char *level_subdir)
 {
   static char *userlevel_dir = NULL;
 static char *getUserLevelDir(char *level_subdir)
 {
   static char *userlevel_dir = NULL;
-  char *data_dir = getUserDataDir();
+  char *data_dir = getUserGameDataDir();
   char *userlevel_subdir = LEVELS_DIRECTORY;
 
   char *userlevel_subdir = LEVELS_DIRECTORY;
 
-  if (userlevel_dir)
-    free(userlevel_dir);
+  checked_free(userlevel_dir);
 
   if (level_subdir != NULL)
     userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir);
 
   if (level_subdir != NULL)
     userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir);
@@ -151,36 +130,28 @@ static char *getUserLevelDir(char *level_subdir)
   return userlevel_dir;
 }
 
   return userlevel_dir;
 }
 
-static char *getTapeDir(char *level_subdir)
-{
-  static char *tape_dir = NULL;
-  char *data_dir = getUserDataDir();
-  char *tape_subdir = TAPES_DIRECTORY;
-
-  if (tape_dir)
-    free(tape_dir);
-
-  if (level_subdir != NULL)
-    tape_dir = getPath3(data_dir, tape_subdir, level_subdir);
-  else
-    tape_dir = getPath2(data_dir, tape_subdir);
-
-  return tape_dir;
-}
-
 static char *getScoreDir(char *level_subdir)
 {
   static char *score_dir = NULL;
 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;
 
   char *score_subdir = SCORES_DIRECTORY;
 
-  if (score_dir)
-    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)
 
   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;
 }
 
   return score_dir;
 }
@@ -188,11 +159,10 @@ static char *getScoreDir(char *level_subdir)
 static char *getLevelSetupDir(char *level_subdir)
 {
   static char *levelsetup_dir = NULL;
 static char *getLevelSetupDir(char *level_subdir)
 {
   static char *levelsetup_dir = NULL;
-  char *data_dir = getUserDataDir();
+  char *data_dir = getUserGameDataDir();
   char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
 
   char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
 
-  if (levelsetup_dir)
-    free(levelsetup_dir);
+  checked_free(levelsetup_dir);
 
   if (level_subdir != NULL)
     levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir);
 
   if (level_subdir != NULL)
     levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir);
@@ -202,6 +172,16 @@ static char *getLevelSetupDir(char *level_subdir)
   return levelsetup_dir;
 }
 
   return levelsetup_dir;
 }
 
+static char *getCacheDir()
+{
+  static char *cache_dir = NULL;
+
+  if (cache_dir == NULL)
+    cache_dir = getPath2(getUserGameDataDir(), CACHE_DIRECTORY);
+
+  return cache_dir;
+}
+
 static char *getLevelDirFromTreeInfo(TreeInfo *node)
 {
   static char *level_dir = NULL;
 static char *getLevelDirFromTreeInfo(TreeInfo *node)
 {
   static char *level_dir = NULL;
@@ -209,20 +189,70 @@ static char *getLevelDirFromTreeInfo(TreeInfo *node)
   if (node == NULL)
     return options.level_directory;
 
   if (node == NULL)
     return options.level_directory;
 
-  if (level_dir)
-    free(level_dir);
+  checked_free(level_dir);
 
 
-  level_dir = getPath2((node->user_defined ? getUserLevelDir(NULL) :
+  level_dir = getPath2((node->in_user_dir ? getUserLevelDir(NULL) :
                        options.level_directory), node->fullpath);
 
   return level_dir;
 }
 
                        options.level_directory), node->fullpath);
 
   return level_dir;
 }
 
-static char *getCurrentLevelDir()
+char *getCurrentLevelDir()
 {
   return getLevelDirFromTreeInfo(leveldir_current);
 }
 
 {
   return getLevelDirFromTreeInfo(leveldir_current);
 }
 
+char *getNewUserLevelSubdir()
+{
+  static char *new_level_subdir = NULL;
+  char *subdir_prefix = getLoginName();
+  char subdir_suffix[10];
+  int max_suffix_number = 1000;
+  int i = 0;
+
+  while (++i < max_suffix_number)
+  {
+    sprintf(subdir_suffix, "_%d", i);
+
+    checked_free(new_level_subdir);
+    new_level_subdir = getStringCat2(subdir_prefix, subdir_suffix);
+
+    if (!directoryExists(getUserLevelDir(new_level_subdir)))
+      break;
+  }
+
+  return new_level_subdir;
+}
+
+static char *getTapeDir(char *level_subdir)
+{
+  static char *tape_dir = NULL;
+  char *data_dir = getUserGameDataDir();
+  char *tape_subdir = TAPES_DIRECTORY;
+
+  checked_free(tape_dir);
+
+  if (level_subdir != NULL)
+    tape_dir = getPath3(data_dir, tape_subdir, level_subdir);
+  else
+    tape_dir = getPath2(data_dir, tape_subdir);
+
+  return tape_dir;
+}
+
+static char *getSolutionTapeDir()
+{
+  static char *tape_dir = NULL;
+  char *data_dir = getCurrentLevelDir();
+  char *tape_subdir = TAPES_DIRECTORY;
+
+  checked_free(tape_dir);
+
+  tape_dir = getPath2(data_dir, tape_subdir);
+
+  return tape_dir;
+}
+
 static char *getDefaultGraphicsDir(char *graphics_subdir)
 {
   static char *graphics_dir = NULL;
 static char *getDefaultGraphicsDir(char *graphics_subdir)
 {
   static char *graphics_dir = NULL;
@@ -230,8 +260,7 @@ static char *getDefaultGraphicsDir(char *graphics_subdir)
   if (graphics_subdir == NULL)
     return options.graphics_directory;
 
   if (graphics_subdir == NULL)
     return options.graphics_directory;
 
-  if (graphics_dir)
-    free(graphics_dir);
+  checked_free(graphics_dir);
 
   graphics_dir = getPath2(options.graphics_directory, graphics_subdir);
 
 
   graphics_dir = getPath2(options.graphics_directory, graphics_subdir);
 
@@ -245,8 +274,7 @@ static char *getDefaultSoundsDir(char *sounds_subdir)
   if (sounds_subdir == NULL)
     return options.sounds_directory;
 
   if (sounds_subdir == NULL)
     return options.sounds_directory;
 
-  if (sounds_dir)
-    free(sounds_dir);
+  checked_free(sounds_dir);
 
   sounds_dir = getPath2(options.sounds_directory, sounds_subdir);
 
 
   sounds_dir = getPath2(options.sounds_directory, sounds_subdir);
 
@@ -260,20 +288,36 @@ static char *getDefaultMusicDir(char *music_subdir)
   if (music_subdir == NULL)
     return options.music_directory;
 
   if (music_subdir == NULL)
     return options.music_directory;
 
-  if (music_dir)
-    free(music_dir);
+  checked_free(music_dir);
 
   music_dir = getPath2(options.music_directory, music_subdir);
 
   return music_dir;
 }
 
 
   music_dir = getPath2(options.music_directory, music_subdir);
 
   return music_dir;
 }
 
+static char *getClassicArtworkSet(int type)
+{
+  return (type == TREE_TYPE_GRAPHICS_DIR ? GFX_CLASSIC_SUBDIR :
+         type == TREE_TYPE_SOUNDS_DIR   ? SND_CLASSIC_SUBDIR :
+         type == TREE_TYPE_MUSIC_DIR    ? MUS_CLASSIC_SUBDIR : "");
+}
+
+static char *getClassicArtworkDir(int type)
+{
+  return (type == TREE_TYPE_GRAPHICS_DIR ?
+         getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR) :
+         type == TREE_TYPE_SOUNDS_DIR ?
+         getDefaultSoundsDir(SND_CLASSIC_SUBDIR) :
+         type == TREE_TYPE_MUSIC_DIR ?
+         getDefaultMusicDir(MUS_CLASSIC_SUBDIR) : "");
+}
+
 static char *getUserGraphicsDir()
 {
   static char *usergraphics_dir = NULL;
 
   if (usergraphics_dir == NULL)
 static char *getUserGraphicsDir()
 {
   static char *usergraphics_dir = NULL;
 
   if (usergraphics_dir == NULL)
-    usergraphics_dir = getPath2(getUserDataDir(), GRAPHICS_DIRECTORY);
+    usergraphics_dir = getPath2(getUserGameDataDir(), GRAPHICS_DIRECTORY);
 
   return usergraphics_dir;
 }
 
   return usergraphics_dir;
 }
@@ -283,7 +327,7 @@ static char *getUserSoundsDir()
   static char *usersounds_dir = NULL;
 
   if (usersounds_dir == NULL)
   static char *usersounds_dir = NULL;
 
   if (usersounds_dir == NULL)
-    usersounds_dir = getPath2(getUserDataDir(), SOUNDS_DIRECTORY);
+    usersounds_dir = getPath2(getUserGameDataDir(), SOUNDS_DIRECTORY);
 
   return usersounds_dir;
 }
 
   return usersounds_dir;
 }
@@ -293,7 +337,7 @@ static char *getUserMusicDir()
   static char *usermusic_dir = NULL;
 
   if (usermusic_dir == NULL)
   static char *usermusic_dir = NULL;
 
   if (usermusic_dir == NULL)
-    usermusic_dir = getPath2(getUserDataDir(), MUSIC_DIRECTORY);
+    usermusic_dir = getPath2(getUserGameDataDir(), MUSIC_DIRECTORY);
 
   return usermusic_dir;
 }
 
   return usermusic_dir;
 }
@@ -302,81 +346,143 @@ static char *getSetupArtworkDir(TreeInfo *ti)
 {
   static char *artwork_dir = NULL;
 
 {
   static char *artwork_dir = NULL;
 
-  if (artwork_dir != NULL)
-    free(artwork_dir);
+  if (ti == NULL)
+    return NULL;
+
+  checked_free(artwork_dir);
 
   artwork_dir = getPath2(ti->basepath, ti->fullpath);
 
   return artwork_dir;
 }
 
 
   artwork_dir = getPath2(ti->basepath, ti->fullpath);
 
   return artwork_dir;
 }
 
-void setLevelArtworkDir(TreeInfo *ti)
+char *setLevelArtworkDir(TreeInfo *ti)
 {
 {
-  char **artwork_path_ptr, *artwork_set;
+  char **artwork_path_ptr, **artwork_set_ptr;
   TreeInfo *level_artwork;
 
   if (ti == NULL || leveldir_current == NULL)
   TreeInfo *level_artwork;
 
   if (ti == NULL || leveldir_current == NULL)
-    return;
+    return NULL;
 
 
-  artwork_path_ptr =
-    (ti->type == TREE_TYPE_GRAPHICS_DIR ? &leveldir_current->graphics_path :
-     ti->type == TREE_TYPE_SOUNDS_DIR   ? &leveldir_current->sounds_path :
-     &leveldir_current->music_path);
+  artwork_path_ptr = LEVELDIR_ARTWORK_PATH_PTR(leveldir_current, ti->type);
+  artwork_set_ptr  = LEVELDIR_ARTWORK_SET_PTR( leveldir_current, ti->type);
 
 
-  artwork_set =
-    (ti->type == TREE_TYPE_GRAPHICS_DIR ? leveldir_current->graphics_set :
-     ti->type == TREE_TYPE_SOUNDS_DIR   ? leveldir_current->sounds_set :
-     leveldir_current->music_set);
+  checked_free(*artwork_path_ptr);
 
 
-  if ((level_artwork = getTreeInfoFromIdentifier(ti, artwork_set)) == NULL)
-    return;
+  if ((level_artwork = getTreeInfoFromIdentifier(ti, *artwork_set_ptr)))
+  {
+    *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
+  }
+  else
+  {
+    /*
+      No (or non-existing) artwork configured in "levelinfo.conf". This would
+      normally result in using the artwork configured in the setup menu. But
+      if an artwork subdirectory exists (which might contain custom artwork
+      or an artwork configuration file), this level artwork must be treated
+      as relative to the default "classic" artwork, not to the artwork that
+      is currently configured in the setup menu.
+
+      Update: For "special" versions of R'n'D (like "R'n'D jue"), do not use
+      the "default" artwork (which would be "jue0" for "R'n'D jue"), but use
+      the real "classic" artwork from the original R'n'D (like "gfx_classic").
+    */
+
+    char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
 
 
-  if (*artwork_path_ptr != NULL)
-    free(*artwork_path_ptr);
+    checked_free(*artwork_set_ptr);
+
+    if (directoryExists(dir))
+    {
+      *artwork_path_ptr = getStringCopy(getClassicArtworkDir(ti->type));
+      *artwork_set_ptr = getStringCopy(getClassicArtworkSet(ti->type));
+    }
+    else
+    {
+      *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
+      *artwork_set_ptr = NULL;
+    }
+
+    free(dir);
+  }
 
 
-  *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
+  return *artwork_set_ptr;
 }
 
 }
 
-static char *getLevelArtworkDir(int type)
+inline static char *getLevelArtworkSet(int type)
 {
 {
-  char *artwork_path;
+  if (leveldir_current == NULL)
+    return NULL;
+
+  return LEVELDIR_ARTWORK_SET(leveldir_current, type);
+}
 
 
+inline static char *getLevelArtworkDir(int type)
+{
   if (leveldir_current == NULL)
     return UNDEFINED_FILENAME;
 
   if (leveldir_current == NULL)
     return UNDEFINED_FILENAME;
 
-  artwork_path =
-    (type == TREE_TYPE_GRAPHICS_DIR ? leveldir_current->graphics_path :
-     type == TREE_TYPE_SOUNDS_DIR   ? leveldir_current->sounds_path :
-     type == TREE_TYPE_MUSIC_DIR    ? leveldir_current->music_path :
-     UNDEFINED_FILENAME);
+  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;
 
 
-  return artwork_path;
+  // 2nd try: return config filename that matches binary filename without suffix
+  return config_filename_2;
 }
 
 }
 
-char *getLevelFilename(int nr)
+char *getTapeFilename(int nr)
 {
   static char *filename = NULL;
   char basename[MAX_FILENAME_LEN];
 
 {
   static char *filename = NULL;
   char basename[MAX_FILENAME_LEN];
 
-  if (filename != NULL)
-    free(filename);
+  checked_free(filename);
 
 
-  sprintf(basename, "%03d.%s", nr, LEVELFILE_EXTENSION);
-  filename = getPath2(getCurrentLevelDir(), basename);
+  sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
+  filename = getPath2(getTapeDir(leveldir_current->subdir), basename);
 
   return filename;
 }
 
 
   return filename;
 }
 
-char *getTapeFilename(int nr)
+char *getSolutionTapeFilename(int nr)
 {
   static char *filename = NULL;
   char basename[MAX_FILENAME_LEN];
 
 {
   static char *filename = NULL;
   char basename[MAX_FILENAME_LEN];
 
-  if (filename != NULL)
-    free(filename);
+  checked_free(filename);
 
   sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
 
   sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
-  filename = getPath2(getTapeDir(leveldir_current->filename), basename);
+  filename = getPath2(getSolutionTapeDir(), basename);
+
+  if (!fileExists(filename))
+  {
+    static char *filename_sln = NULL;
+
+    checked_free(filename_sln);
+
+    sprintf(basename, "%03d.sln", nr);
+    filename_sln = getPath2(getSolutionTapeDir(), basename);
+
+    if (fileExists(filename_sln))
+      return filename_sln;
+  }
 
   return filename;
 }
 
   return filename;
 }
@@ -386,11 +492,10 @@ char *getScoreFilename(int nr)
   static char *filename = NULL;
   char basename[MAX_FILENAME_LEN];
 
   static char *filename = NULL;
   char basename[MAX_FILENAME_LEN];
 
-  if (filename != NULL)
-    free(filename);
+  checked_free(filename);
 
   sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
 
   sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
-  filename = getPath2(getScoreDir(leveldir_current->filename), basename);
+  filename = getPath2(getScoreDir(leveldir_current->subdir), basename);
 
   return filename;
 }
 
   return filename;
 }
@@ -399,86 +504,154 @@ char *getSetupFilename()
 {
   static char *filename = NULL;
 
 {
   static char *filename = NULL;
 
-  if (filename != NULL)
-    free(filename);
+  checked_free(filename);
 
   filename = getPath2(getSetupDir(), SETUP_FILENAME);
 
   return filename;
 }
 
 
   filename = getPath2(getSetupDir(), SETUP_FILENAME);
 
   return filename;
 }
 
-static char *getCorrectedImageBasename(char *basename)
+char *getDefaultSetupFilename()
 {
 {
-  char *basename_corrected = basename;
+  return program.config_filename;
+}
 
 
-#if defined(PLATFORM_MSDOS)
-  if (program.filename_prefix != NULL)
-  {
-    int prefix_len = strlen(program.filename_prefix);
+char *getEditorSetupFilename()
+{
+  static char *filename = NULL;
 
 
-    if (strncmp(basename, program.filename_prefix, prefix_len) == 0)
-      basename_corrected = &basename[prefix_len];
+  checked_free(filename);
+  filename = getPath2(getCurrentLevelDir(), EDITORSETUP_FILENAME);
 
 
-    /* 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;
+  if (fileExists(filename))
+    return filename;
 
 
-      if (msdos_filename != NULL)
-       free(msdos_filename);
+  checked_free(filename);
+  filename = getPath2(getSetupDir(), EDITORSETUP_FILENAME);
 
 
-      msdos_filename = getStringCopy(basename_corrected);
-      strncpy(&msdos_filename[8], &basename[strlen(basename) - 1+3], 1+3 + 1);
-    }
+  return filename;
+}
+
+char *getHelpAnimFilename()
+{
+  static char *filename = NULL;
+
+  checked_free(filename);
+
+  filename = getPath2(getCurrentLevelDir(), HELPANIM_FILENAME);
+
+  return filename;
+}
+
+char *getHelpTextFilename()
+{
+  static char *filename = NULL;
+
+  checked_free(filename);
+
+  filename = getPath2(getCurrentLevelDir(), HELPTEXT_FILENAME);
+
+  return filename;
+}
+
+char *getLevelSetInfoFilename()
+{
+  static char *filename = NULL;
+  char *basenames[] =
+  {
+    "README",
+    "README.TXT",
+    "README.txt",
+    "Readme",
+    "Readme.txt",
+    "readme",
+    "readme.txt",
+
+    NULL
+  };
+  int i;
+
+  for (i = 0; basenames[i] != NULL; i++)
+  {
+    checked_free(filename);
+    filename = getPath2(getCurrentLevelDir(), basenames[i]);
+
+    if (fileExists(filename))
+      return filename;
   }
   }
-#endif
 
 
-  return basename_corrected;
+  return NULL;
 }
 
 }
 
-char *getCustomImageFilename(char *basename)
+char *getLevelSetTitleMessageBasename(int nr, boolean initial)
+{
+  static char basename[32];
+
+  sprintf(basename, "%s_%d.txt",
+         (initial ? "titlemessage_initial" : "titlemessage"), nr + 1);
+
+  return basename;
+}
+
+char *getLevelSetTitleMessageFilename(int nr, boolean initial)
 {
   static char *filename = NULL;
 {
   static char *filename = NULL;
+  char *basename;
+  boolean skip_setup_artwork = FALSE;
 
 
-  if (filename != NULL)
-    free(filename);
+  checked_free(filename);
 
 
-  basename = getCorrectedImageBasename(basename);
+  basename = getLevelSetTitleMessageBasename(nr, initial);
 
 
-  if (!setup.override_level_graphics)
+  if (!gfx.override_level_graphics)
   {
   {
-    /* 1st try: look for special artwork configured in level series config */
-    filename = getPath2(getLevelArtworkDir(TREE_TYPE_GRAPHICS_DIR), basename);
+    /* 1st try: look for special artwork in current level series directory */
+    filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
     if (fileExists(filename))
       return filename;
 
     free(filename);
 
     if (fileExists(filename))
       return filename;
 
     free(filename);
 
-    /* 2nd try: look for special artwork in current level series directory */
-    filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
+    /* 2nd try: look for message file in current level set directory */
+    filename = getPath2(getCurrentLevelDir(), basename);
     if (fileExists(filename))
       return filename;
 
     free(filename);
     if (fileExists(filename))
       return filename;
 
     free(filename);
+
+    /* check if there is special artwork configured in level series config */
+    if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
+    {
+      /* 3rd try: look for special artwork configured in level series config */
+      filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
+      if (fileExists(filename))
+       return filename;
+
+      free(filename);
+
+      /* take missing artwork configured in level set config from default */
+      skip_setup_artwork = TRUE;
+    }
   }
 
   }
 
-  /* 3rd try: look for special artwork in configured artwork directory */
-  filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
-  if (fileExists(filename))
-    return filename;
+  if (!skip_setup_artwork)
+  {
+    /* 4th try: look for special artwork in configured artwork directory */
+    filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
+    if (fileExists(filename))
+      return filename;
 
 
-  free(filename);
+    free(filename);
+  }
 
 
-  /* 4th try: look for default artwork in new default artwork directory */
-  filename = getPath2(getDefaultGraphicsDir(GRAPHICS_SUBDIR), basename);
+  /* 5th try: look for default artwork in new default artwork directory */
+  filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
   if (fileExists(filename))
     return filename;
 
   free(filename);
 
   if (fileExists(filename))
     return filename;
 
   free(filename);
 
-  /* 5th try: look for default artwork in old default artwork directory */
+  /* 6th try: look for default artwork in old default artwork directory */
   filename = getPath2(options.graphics_directory, basename);
   if (fileExists(filename))
     return filename;
   filename = getPath2(options.graphics_directory, basename);
   if (fileExists(filename))
     return filename;
@@ -486,108 +659,309 @@ char *getCustomImageFilename(char *basename)
   return NULL;         /* cannot find specified artwork file anywhere */
 }
 
   return NULL;         /* cannot find specified artwork file anywhere */
 }
 
-char *getCustomSoundFilename(char *basename)
+static char *getCorrectedArtworkBasename(char *basename)
+{
+  return basename;
+}
+
+char *getCustomImageFilename(char *basename)
 {
   static char *filename = NULL;
 {
   static char *filename = NULL;
+  boolean skip_setup_artwork = FALSE;
 
 
-  if (filename != NULL)
-    free(filename);
+  checked_free(filename);
 
 
-  if (!setup.override_level_sounds)
+  basename = getCorrectedArtworkBasename(basename);
+
+  if (!gfx.override_level_graphics)
   {
   {
-    /* 1st try: look for special artwork configured in level series config */
-    filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
+    /* 1st try: look for special artwork in current level series directory */
+    filename = getImg3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
     if (fileExists(filename))
       return filename;
 
     free(filename);
 
     if (fileExists(filename))
       return filename;
 
     free(filename);
 
-    /* 2nd try: look for special artwork in current level series directory */
-    filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
+    /* check if there is special artwork configured in level series config */
+    if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
+    {
+      /* 2nd try: look for special artwork configured in level series config */
+      filename = getImg2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
+      if (fileExists(filename))
+       return filename;
+
+      free(filename);
+
+      /* take missing artwork configured in level set config from default */
+      skip_setup_artwork = TRUE;
+    }
+  }
+
+  if (!skip_setup_artwork)
+  {
+    /* 3rd try: look for special artwork in configured artwork directory */
+    filename = getImg2(getSetupArtworkDir(artwork.gfx_current), basename);
     if (fileExists(filename))
       return filename;
 
     free(filename);
   }
 
     if (fileExists(filename))
       return filename;
 
     free(filename);
   }
 
-  /* 3rd try: look for special artwork in configured artwork directory */
-  filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
-  if (fileExists(filename))
-    return filename;
-
-  free(filename);
-
   /* 4th try: look for default artwork in new default artwork directory */
   /* 4th try: look for default artwork in new default artwork directory */
-  filename = getPath2(getDefaultSoundsDir(SOUNDS_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 */
   if (fileExists(filename))
     return filename;
 
   free(filename);
 
   /* 5th try: look for default artwork in old default artwork directory */
-  filename = getPath2(options.sounds_directory, basename);
+  filename = getImg2(options.graphics_directory, basename);
   if (fileExists(filename))
     return filename;
 
   if (fileExists(filename))
     return filename;
 
-  return NULL;         /* cannot find specified artwork file anywhere */
-}
+  if (!strEqual(GFX_FALLBACK_FILENAME, UNDEFINED_FILENAME))
+  {
+    free(filename);
 
 
-char *getCustomArtworkFilename(char *basename, int type)
-{
-  if (type == ARTWORK_TYPE_GRAPHICS)
-    return getCustomImageFilename(basename);
-  else if (type == ARTWORK_TYPE_SOUNDS)
-    return getCustomSoundFilename(basename);
-  else
-    return UNDEFINED_FILENAME;
-}
+    if (options.debug)
+      Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)",
+           basename);
 
 
-char *getCustomArtworkConfigFilename(int type)
-{
-  return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
+    /* 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 */
 }
 
 }
 
-char *getCustomMusicDirectory(void)
+char *getCustomSoundFilename(char *basename)
 {
 {
-  static char *directory = NULL;
+  static char *filename = NULL;
+  boolean skip_setup_artwork = FALSE;
 
 
-  if (directory != NULL)
-    free(directory);
+  checked_free(filename);
 
 
-  if (!setup.override_level_music)
+  basename = getCorrectedArtworkBasename(basename);
+
+  if (!gfx.override_level_sounds)
   {
   {
-    /* 1st try: look for special artwork configured in level series config */
-    directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
-    if (fileExists(directory))
-      return directory;
+    /* 1st try: look for special artwork in current level series directory */
+    filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
+    if (fileExists(filename))
+      return filename;
 
 
-    free(directory);
+    free(filename);
 
 
-    /* 2nd try: look for special artwork in current level series directory */
-    directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
-    if (fileExists(directory))
-      return directory;
+    /* check if there is special artwork configured in level series config */
+    if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
+    {
+      /* 2nd try: look for special artwork configured in level series config */
+      filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
+      if (fileExists(filename))
+       return filename;
 
 
-    free(directory);
+      free(filename);
+
+      /* take missing artwork configured in level set config from default */
+      skip_setup_artwork = TRUE;
+    }
   }
 
   }
 
-  /* 3rd try: look for special artwork in configured artwork directory */
-  directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
-  if (fileExists(directory))
-    return directory;
+  if (!skip_setup_artwork)
+  {
+    /* 3rd try: look for special artwork in configured artwork directory */
+    filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
+    if (fileExists(filename))
+      return filename;
 
 
-  free(directory);
+    free(filename);
+  }
 
   /* 4th try: look for default artwork in new default artwork directory */
 
   /* 4th try: look for default artwork in new default artwork directory */
-  directory = getStringCopy(getDefaultMusicDir(MUSIC_SUBDIR));
-  if (fileExists(directory))
-    return directory;
+  filename = getPath2(getDefaultSoundsDir(SND_DEFAULT_SUBDIR), basename);
+  if (fileExists(filename))
+    return filename;
+
+  free(filename);
+
+  /* 5th try: look for default artwork in old default artwork directory */
+  filename = getPath2(options.sounds_directory, basename);
+  if (fileExists(filename))
+    return filename;
+
+  if (!strEqual(SND_FALLBACK_FILENAME, UNDEFINED_FILENAME))
+  {
+    free(filename);
+
+    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;
+  }
+
+  return NULL;         /* cannot find specified artwork file anywhere */
+}
+
+char *getCustomMusicFilename(char *basename)
+{
+  static char *filename = NULL;
+  boolean skip_setup_artwork = FALSE;
+
+  checked_free(filename);
+
+  basename = getCorrectedArtworkBasename(basename);
+
+  if (!gfx.override_level_music)
+  {
+    /* 1st try: look for special artwork in current level series directory */
+    filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
+    if (fileExists(filename))
+      return filename;
+
+    free(filename);
+
+    /* check if there is special artwork configured in level series config */
+    if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
+    {
+      /* 2nd try: look for special artwork configured in level series config */
+      filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
+      if (fileExists(filename))
+       return filename;
+
+      free(filename);
+
+      /* take missing artwork configured in level set config from default */
+      skip_setup_artwork = TRUE;
+    }
+  }
+
+  if (!skip_setup_artwork)
+  {
+    /* 3rd try: look for special artwork in configured artwork directory */
+    filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
+    if (fileExists(filename))
+      return filename;
+
+    free(filename);
+  }
+
+  /* 4th try: look for default artwork in new default artwork directory */
+  filename = getPath2(getDefaultMusicDir(MUS_DEFAULT_SUBDIR), basename);
+  if (fileExists(filename))
+    return filename;
+
+  free(filename);
+
+  /* 5th try: look for default artwork in old default artwork directory */
+  filename = getPath2(options.music_directory, basename);
+  if (fileExists(filename))
+    return filename;
+
+  if (!strEqual(MUS_FALLBACK_FILENAME, UNDEFINED_FILENAME))
+  {
+    free(filename);
+
+    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;
+  }
+
+  return NULL;         /* cannot find specified artwork file anywhere */
+}
+
+char *getCustomArtworkFilename(char *basename, int type)
+{
+  if (type == ARTWORK_TYPE_GRAPHICS)
+    return getCustomImageFilename(basename);
+  else if (type == ARTWORK_TYPE_SOUNDS)
+    return getCustomSoundFilename(basename);
+  else if (type == ARTWORK_TYPE_MUSIC)
+    return getCustomMusicFilename(basename);
+  else
+    return UNDEFINED_FILENAME;
+}
+
+char *getCustomArtworkConfigFilename(int type)
+{
+  return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
+}
+
+char *getCustomArtworkLevelConfigFilename(int type)
+{
+  static char *filename = NULL;
+
+  checked_free(filename);
+
+  filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
+
+  return filename;
+}
+
+char *getCustomMusicDirectory(void)
+{
+  static char *directory = NULL;
+  boolean skip_setup_artwork = FALSE;
+
+  checked_free(directory);
+
+  if (!gfx.override_level_music)
+  {
+    /* 1st try: look for special artwork in current level series directory */
+    directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
+    if (directoryExists(directory))
+      return directory;
+
+    free(directory);
+
+    /* check if there is special artwork configured in level series config */
+    if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
+    {
+      /* 2nd try: look for special artwork configured in level series config */
+      directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
+      if (directoryExists(directory))
+       return directory;
+
+      free(directory);
+
+      /* take missing artwork configured in level set config from default */
+      skip_setup_artwork = TRUE;
+    }
+  }
+
+  if (!skip_setup_artwork)
+  {
+    /* 3rd try: look for special artwork in configured artwork directory */
+    directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
+    if (directoryExists(directory))
+      return directory;
+
+    free(directory);
+  }
+
+  /* 4th try: look for default artwork in new default artwork directory */
+  directory = getStringCopy(getDefaultMusicDir(MUS_DEFAULT_SUBDIR));
+  if (directoryExists(directory))
+    return directory;
 
   free(directory);
 
   /* 5th try: look for default artwork in old default artwork directory */
   directory = getStringCopy(options.music_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 */
     return directory;
 
   return NULL;         /* cannot find specified artwork file anywhere */
@@ -595,27 +969,33 @@ char *getCustomMusicDirectory(void)
 
 void InitTapeDirectory(char *level_subdir)
 {
 
 void InitTapeDirectory(char *level_subdir)
 {
-  createDirectory(getUserDataDir(), "user data", PERMS_PRIVATE);
+  createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
   createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
   createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
 }
 
 void InitScoreDirectory(char *level_subdir)
 {
   createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
   createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
 }
 
 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)
 {
 }
 
 static void SaveUserLevelInfo();
 
 void InitUserLevelDirectory(char *level_subdir)
 {
-  if (access(getUserLevelDir(level_subdir), F_OK) != 0)
+  if (!directoryExists(getUserLevelDir(level_subdir)))
   {
   {
-    createDirectory(getUserDataDir(), "user data", PERMS_PRIVATE);
+    createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
     createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
     createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
-    createDirectory(getUserLevelDir(level_subdir), "user level",PERMS_PRIVATE);
+    createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
 
     SaveUserLevelInfo();
   }
 
     SaveUserLevelInfo();
   }
@@ -623,14 +1003,20 @@ void InitUserLevelDirectory(char *level_subdir)
 
 void InitLevelSetupDirectory(char *level_subdir)
 {
 
 void InitLevelSetupDirectory(char *level_subdir)
 {
-  createDirectory(getUserDataDir(), "user data", PERMS_PRIVATE);
+  createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
   createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
   createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
-  createDirectory(getLevelSetupDir(level_subdir), "level setup",PERMS_PRIVATE);
+  createDirectory(getLevelSetupDir(level_subdir), "level setup", PERMS_PRIVATE);
+}
+
+void InitCacheDirectory()
+{
+  createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
+  createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
 }
 
 
 /* ------------------------------------------------------------------------- */
 }
 
 
 /* ------------------------------------------------------------------------- */
-/* some functions to handle lists of level directories                       */
+/* some functions to handle lists of level and artwork directories           */
 /* ------------------------------------------------------------------------- */
 
 TreeInfo *newTreeInfo()
 /* ------------------------------------------------------------------------- */
 
 TreeInfo *newTreeInfo()
@@ -638,6 +1024,15 @@ TreeInfo *newTreeInfo()
   return checked_calloc(sizeof(TreeInfo));
 }
 
   return checked_calloc(sizeof(TreeInfo));
 }
 
+TreeInfo *newTreeInfo_setDefaults(int type)
+{
+  TreeInfo *ti = newTreeInfo();
+
+  setTreeInfoToDefaults(ti, type);
+
+  return ti;
+}
+
 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
 {
   node_new->next = *node_first;
 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
 {
   node_new->next = *node_first;
@@ -748,7 +1143,7 @@ TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
     }
     else if (!node->parent_link)
     {
     }
     else if (!node->parent_link)
     {
-      if (strcmp(identifier, node->identifier) == 0)
+      if (strEqual(identifier, node->identifier))
        return node;
     }
 
        return node;
     }
 
@@ -758,6 +1153,69 @@ TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
   return NULL;
 }
 
   return NULL;
 }
 
+TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
+                       TreeInfo *node, boolean skip_sets_without_levels)
+{
+  TreeInfo *node_new;
+
+  if (node == NULL)
+    return NULL;
+
+  if (!node->parent_link && !node->level_group &&
+      skip_sets_without_levels && node->levels == 0)
+    return cloneTreeNode(node_top, node_parent, node->next,
+                        skip_sets_without_levels);
+
+  node_new = getTreeInfoCopy(node);            /* copy complete node */
+
+  node_new->node_top = node_top;               /* correct top node link */
+  node_new->node_parent = node_parent;         /* correct parent node link */
+
+  if (node->level_group)
+    node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
+                                        skip_sets_without_levels);
+
+  node_new->next = cloneTreeNode(node_top, node_parent, node->next,
+                                skip_sets_without_levels);
+  
+  return node_new;
+}
+
+void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
+{
+  TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
+
+  *ti_new = ti_cloned;
+}
+
+static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
+{
+  boolean settings_changed = FALSE;
+
+  while (node)
+  {
+    if (node->graphics_set_ecs && !setup.prefer_aga_graphics &&
+       !strEqual(node->graphics_set, node->graphics_set_ecs))
+    {
+      setString(&node->graphics_set, node->graphics_set_ecs);
+      settings_changed = TRUE;
+    }
+    else if (node->graphics_set_aga && setup.prefer_aga_graphics &&
+            !strEqual(node->graphics_set, node->graphics_set_aga))
+    {
+      setString(&node->graphics_set, node->graphics_set_aga);
+      settings_changed = TRUE;
+    }
+
+    if (node->node_group != NULL)
+      settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
+
+    node = node->next;
+  }
+
+  return settings_changed;
+}
+
 void dumpTreeInfo(TreeInfo *node, int depth)
 {
   int i;
 void dumpTreeInfo(TreeInfo *node, int depth)
 {
   int i;
@@ -766,11 +1224,16 @@ void dumpTreeInfo(TreeInfo *node, int depth)
 
   while (node)
   {
 
   while (node)
   {
-    for (i=0; i<(depth + 1) * 3; i++)
+    for (i = 0; i < (depth + 1) * 3; i++)
       printf(" ");
 
       printf(" ");
 
-    printf("filename == '%s' (%s) [%s] (%d)\n",
-          node->filename, node->name, node->identifier, node->sort_priority);
+    printf("'%s' / '%s'\n", node->identifier, node->name);
+
+    /*
+    // use for dumping artwork info tree
+    printf("subdir == '%s' ['%s', '%s'] [%d])\n",
+          node->subdir, node->fullpath, node->basepath, node->in_user_dir);
+    */
 
     if (node->node_group != NULL)
       dumpTreeInfo(node->node_group, depth + 1);
 
     if (node->node_group != NULL)
       dumpTreeInfo(node->node_group, depth + 1);
@@ -779,8 +1242,9 @@ void dumpTreeInfo(TreeInfo *node, int depth)
   }
 }
 
   }
 }
 
-void sortTreeInfo(TreeInfo **node_first,
-                 int (*compare_function)(const void *, const void *))
+void sortTreeInfoBySortFunction(TreeInfo **node_first,
+                               int (*compare_function)(const void *,
+                                                       const void *))
 {
   int num_nodes = numTreeInfo(*node_first);
   TreeInfo **sort_array;
 {
   int num_nodes = numTreeInfo(*node_first);
   TreeInfo **sort_array;
@@ -807,7 +1271,7 @@ void sortTreeInfo(TreeInfo **node_first,
        compare_function);
 
   /* update the linkage of list elements with the sorted node array */
        compare_function);
 
   /* update the linkage of list elements with the sorted node array */
-  for (i=0; i<num_nodes - 1; i++)
+  for (i = 0; i < num_nodes - 1; i++)
     sort_array[i]->next = sort_array[i + 1];
   sort_array[num_nodes - 1]->next = NULL;
 
     sort_array[i]->next = sort_array[i + 1];
   sort_array[num_nodes - 1]->next = NULL;
 
@@ -821,12 +1285,17 @@ void sortTreeInfo(TreeInfo **node_first,
   while (node)
   {
     if (node->node_group != NULL)
   while (node)
   {
     if (node->node_group != NULL)
-      sortTreeInfo(&node->node_group, compare_function);
+      sortTreeInfoBySortFunction(&node->node_group, compare_function);
 
     node = node->next;
   }
 }
 
 
     node = node->next;
   }
 }
 
+void sortTreeInfo(TreeInfo **node_first)
+{
+  sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
+}
+
 
 /* ========================================================================= */
 /* some stuff from "files.c"                                                 */
 
 /* ========================================================================= */
 /* some stuff from "files.c"                                                 */
@@ -865,23 +1334,48 @@ void sortTreeInfo(TreeInfo **node_first,
 #define MODE_X_ALL             (S_IXUSR | S_IXGRP | S_IXOTH)
 
 #define MODE_W_PRIVATE         (S_IWUSR)
 #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 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_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 *getUserDataDir(void)
+char *getHomeDir()
 {
 {
-  static char *userdata_dir = NULL;
+  static char *dir = NULL;
 
 
-  if (userdata_dir == NULL)
-    userdata_dir = getPath2(getHomeDir(), program.userdata_directory);
+#if defined(PLATFORM_WIN32)
+  if (dir == NULL)
+  {
+    dir = checked_malloc(MAX_PATH + 1);
+
+    if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
+      strcpy(dir, ".");
+  }
+#elif defined(PLATFORM_UNIX)
+  if (dir == NULL)
+  {
+    if ((dir = getenv("HOME")) == NULL)
+    {
+      struct passwd *pwd;
+
+      if ((pwd = getpwuid(getuid())) != NULL)
+       dir = getStringCopy(pwd->pw_dir);
+      else
+       dir = ".";
+    }
+  }
+#else
+  dir = ".";
+#endif
 
 
-  return userdata_dir;
+  return dir;
 }
 
 char *getCommonDataDir(void)
 }
 
 char *getCommonDataDir(void)
@@ -894,8 +1388,8 @@ char *getCommonDataDir(void)
     char *dir = checked_malloc(MAX_PATH + 1);
 
     if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
     char *dir = checked_malloc(MAX_PATH + 1);
 
     if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
-       && strcmp(dir, "") != 0)        /* empty for Windows 95/98 */
-      common_data_dir = getPath2(dir, program.userdata_directory);
+       && !strEqual(dir, ""))          /* empty for Windows 95/98 */
+      common_data_dir = getPath2(dir, program.userdata_subdir);
     else
       common_data_dir = options.rw_base_directory;
   }
     else
       common_data_dir = options.rw_base_directory;
   }
@@ -907,9 +1401,43 @@ char *getCommonDataDir(void)
   return common_data_dir;
 }
 
   return common_data_dir;
 }
 
+char *getPersonalDataDir(void)
+{
+  static char *personal_data_dir = NULL;
+
+#if defined(PLATFORM_MACOSX)
+  if (personal_data_dir == NULL)
+    personal_data_dir = getPath2(getHomeDir(), "Documents");
+#else
+  if (personal_data_dir == NULL)
+    personal_data_dir = getHomeDir();
+#endif
+
+  return personal_data_dir;
+}
+
+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;
+}
+
 char *getSetupDir()
 {
 char *getSetupDir()
 {
-  return getUserDataDir();
+  return getUserGameDataDir();
 }
 
 static mode_t posix_umask(mode_t mask)
 }
 
 static mode_t posix_umask(mode_t mask)
@@ -930,32 +1458,64 @@ static int posix_mkdir(const char *pathname, mode_t mode)
 #endif
 }
 
 #endif
 }
 
+static boolean posix_process_running_setgid()
+{
+#if defined(PLATFORM_UNIX)
+  return (getgid() != getegid());
+#else
+  return FALSE;
+#endif
+}
+
 void createDirectory(char *dir, char *text, int permission_class)
 {
 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 ?
                     DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
   /* 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 ?
                     DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
-  mode_t normal_umask = posix_umask(0);
+  mode_t last_umask = posix_umask(0);
   mode_t group_umask = ~(dir_mode & S_IRWXG);
   mode_t group_umask = ~(dir_mode & S_IRWXG);
-  posix_umask(normal_umask & group_umask);
+  int running_setgid = posix_process_running_setgid();
+
+  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 = DIR_PERMS_PUBLIC_ALL;
+  }
 
 
-  if (access(dir, F_OK) != 0)
-    if (posix_mkdir(dir, dir_mode) != 0)
-      Error(ERR_WARN, "cannot create %s directory '%s'", text, dir);
+  if (posix_mkdir(dir, dir_mode) != 0)
+    Error(ERR_WARN, "cannot create %s directory '%s': %s",
+         text, dir, strerror(errno));
 
 
-  posix_umask(normal_umask);           /* reset normal umask */
+  if (permission_class == PERMS_PUBLIC && !running_setgid)
+    chmod(dir, dir_mode);
+
+  posix_umask(last_umask);             /* restore previous umask */
 }
 
 void InitUserDataDirectory()
 {
 }
 
 void InitUserDataDirectory()
 {
-  createDirectory(getUserDataDir(), "user data", PERMS_PRIVATE);
+  createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
 }
 
 void SetFilePermissions(char *filename, int permission_class)
 {
 }
 
 void SetFilePermissions(char *filename, int permission_class)
 {
-  chmod(filename, (permission_class == PERMS_PRIVATE ?
-                  FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC));
+  int running_setgid = posix_process_running_setgid();
+  int perms = (permission_class == PERMS_PRIVATE ?
+              FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
+
+  if (permission_class == PERMS_PUBLIC && !running_setgid)
+    perms = FILE_PERMS_PUBLIC_ALL;
+
+  chmod(filename, perms);
 }
 
 char *getCookie(char *file_type)
 }
 
 char *getCookie(char *file_type)
@@ -973,6 +1533,17 @@ char *getCookie(char *file_type)
   return cookie;
 }
 
   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;
 int getFileVersionFromCookieString(const char *cookie)
 {
   const char *ptr_cookie1, *ptr_cookie2;
@@ -1001,7 +1572,7 @@ int getFileVersionFromCookieString(const char *cookie)
   version_major = ptr_cookie2[0] - '0';
   version_minor = ptr_cookie2[2] - '0';
 
   version_major = ptr_cookie2[0] - '0';
   version_minor = ptr_cookie2[2] - '0';
 
-  return VERSION_IDENT(version_major, version_minor, 0);
+  return VERSION_IDENT(version_major, version_minor, 0, 0);
 }
 
 boolean checkCookieString(const char *cookie, const char *template)
 }
 
 boolean checkCookieString(const char *cookie, const char *template)
@@ -1020,6 +1591,7 @@ boolean checkCookieString(const char *cookie, const char *template)
   return TRUE;
 }
 
   return TRUE;
 }
 
+
 /* ------------------------------------------------------------------------- */
 /* setup file list and hash handling functions                               */
 /* ------------------------------------------------------------------------- */
 /* ------------------------------------------------------------------------- */
 /* setup file list and hash handling functions                               */
 /* ------------------------------------------------------------------------- */
@@ -1029,9 +1601,13 @@ char *getFormattedSetupEntry(char *token, char *value)
   int i;
   static char entry[MAX_LINE_LEN];
 
   int i;
   static char entry[MAX_LINE_LEN];
 
+  /* if value is an empty string, just return token without value */
+  if (*value == '\0')
+    return token;
+
   /* start with the token and some spaces to format output line */
   sprintf(entry, "%s:", token);
   /* start with the token and some spaces to format output line */
   sprintf(entry, "%s:", token);
-  for (i=strlen(entry); i<TOKEN_VALUE_POSITION; i++)
+  for (i = strlen(entry); i < token_value_position; i++)
     strcat(entry, " ");
 
   /* continue with the token's value */
     strcat(entry, " ");
 
   /* continue with the token's value */
@@ -1040,20 +1616,6 @@ char *getFormattedSetupEntry(char *token, char *value)
   return entry;
 }
 
   return entry;
 }
 
-void freeSetupFileList(SetupFileList *list)
-{
-  if (list == NULL)
-    return;
-
-  if (list->token)
-    free(list->token);
-  if (list->value)
-    free(list->value);
-  if (list->next)
-    freeSetupFileList(list->next);
-  free(list);
-}
-
 SetupFileList *newSetupFileList(char *token, char *value)
 {
   SetupFileList *new = checked_malloc(sizeof(SetupFileList));
 SetupFileList *newSetupFileList(char *token, char *value)
 {
   SetupFileList *new = checked_malloc(sizeof(SetupFileList));
@@ -1066,35 +1628,62 @@ SetupFileList *newSetupFileList(char *token, char *value)
   return new;
 }
 
   return new;
 }
 
+void freeSetupFileList(SetupFileList *list)
+{
+  if (list == NULL)
+    return;
+
+  checked_free(list->token);
+  checked_free(list->value);
+
+  if (list->next)
+    freeSetupFileList(list->next);
+
+  free(list);
+}
+
 char *getListEntry(SetupFileList *list, char *token)
 {
   if (list == NULL)
     return NULL;
 
 char *getListEntry(SetupFileList *list, char *token)
 {
   if (list == NULL)
     return NULL;
 
-  if (strcmp(list->token, token) == 0)
+  if (strEqual(list->token, token))
     return list->value;
   else
     return getListEntry(list->next, token);
 }
 
     return list->value;
   else
     return getListEntry(list->next, token);
 }
 
-void setListEntry(SetupFileList *list, char *token, char *value)
+SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
 {
   if (list == NULL)
 {
   if (list == NULL)
-    return;
+    return NULL;
 
 
-  if (strcmp(list->token, token) == 0)
+  if (strEqual(list->token, token))
   {
   {
-    if (list->value)
-      free(list->value);
+    checked_free(list->value);
 
     list->value = getStringCopy(value);
 
     list->value = getStringCopy(value);
+
+    return list;
   }
   else if (list->next == NULL)
   }
   else if (list->next == NULL)
-    list->next = newSetupFileList(token, value);
+    return (list->next = newSetupFileList(token, value));
   else
   else
-    setListEntry(list->next, token, value);
+    return setListEntry(list->next, token, value);
 }
 
 }
 
+SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
+{
+  if (list == NULL)
+    return NULL;
+
+  if (list->next == NULL)
+    return (list->next = newSetupFileList(token, value));
+  else
+    return addListEntry(list->next, token, value);
+}
+
+#if ENABLE_UNUSED_CODE
 #ifdef DEBUG
 static void printSetupFileList(SetupFileList *list)
 {
 #ifdef DEBUG
 static void printSetupFileList(SetupFileList *list)
 {
@@ -1107,6 +1696,7 @@ static void printSetupFileList(SetupFileList *list)
   printSetupFileList(list->next);
 }
 #endif
   printSetupFileList(list->next);
 }
 #endif
+#endif
 
 #ifdef DEBUG
 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
 
 #ifdef DEBUG
 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
@@ -1120,16 +1710,24 @@ DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
 #define remove_hash_entry hashtable_remove
 #endif
 
 #define remove_hash_entry hashtable_remove
 #endif
 
-static unsigned int get_hash_from_key(void *key)
+unsigned int get_hash_from_key(void *key)
 {
   /*
     djb2
 
 {
   /*
     djb2
 
-    this algorithm (k=33) was first reported by dan bernstein many years ago in
-    comp.lang.c. another version of this algorithm (now favored by bernstein)
-    uses xor: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
+    This algorithm (k=33) was first reported by Dan Bernstein many years ago in
+    'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
+    uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
     it works better than many other constants, prime or not) has never been
     adequately explained.
     it works better than many other constants, prime or not) has never been
     adequately explained.
+
+    If you just want to have a good hash function, and cannot wait, djb2
+    is one of the best string hash functions i know. It has excellent
+    distribution and speed on many different sets of keys and table sizes.
+    You are not likely to do better with one of the "well known" functions
+    such as PJW, K&R, etc.
+
+    Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
   */
 
   char *str = (char *)key;
   */
 
   char *str = (char *)key;
@@ -1144,16 +1742,7 @@ static unsigned int get_hash_from_key(void *key)
 
 static int keys_are_equal(void *key1, void *key2)
 {
 
 static int keys_are_equal(void *key1, void *key2)
 {
-  return (strcmp((char *)key1, (char *)key2) == 0);
-}
-
-void freeSetupFileHash(SetupFileHash *hash)
-{
-  if (hash == NULL)
-    return;
-
-  hashtable_destroy(hash, 1);  /* 1 == also free values */
-  free(hash);
+  return (strEqual((char *)key1, (char *)key2));
 }
 
 SetupFileHash *newSetupFileHash()
 }
 
 SetupFileHash *newSetupFileHash()
@@ -1161,18 +1750,29 @@ SetupFileHash *newSetupFileHash()
   SetupFileHash *new_hash =
     create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
 
   SetupFileHash *new_hash =
     create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
 
+  if (new_hash == NULL)
+    Error(ERR_EXIT, "create_hashtable() failed -- out of memory");
+
   return new_hash;
 }
 
   return new_hash;
 }
 
-char *getHashEntry(SetupFileHash *hash, char *token)
+void freeSetupFileHash(SetupFileHash *hash)
 {
   if (hash == NULL)
 {
   if (hash == NULL)
-    return NULL;
+    return;
 
 
-  return search_hash_entry(hash, token);
+  hashtable_destroy(hash, 1);  /* 1 == also free values stored in hash */
 }
 
 }
 
-void setHashEntry(SetupFileHash *hash, char *token, char *value)
+char *getHashEntry(SetupFileHash *hash, char *token)
+{
+  if (hash == NULL)
+    return NULL;
+
+  return search_hash_entry(hash, token);
+}
+
+void setHashEntry(SetupFileHash *hash, char *token, char *value)
 {
   char *value_copy;
 
 {
   char *value_copy;
 
@@ -1187,30 +1787,18 @@ void setHashEntry(SetupFileHash *hash, char *token, char *value)
       Error(ERR_EXIT, "cannot insert into hash -- aborting");
 }
 
       Error(ERR_EXIT, "cannot insert into hash -- aborting");
 }
 
-#if 0
-#ifdef DEBUG
-static void printSetupFileHash(SetupFileHash *hash)
+char *removeHashEntry(SetupFileHash *hash, char *token)
 {
 {
-#if 0
   if (hash == NULL)
   if (hash == NULL)
-    return;
-
-  /* iterator constructor only returns valid iterator for non-empty hash */
-  if (hash != NULL && hashtable_count(hash) > 0)
-  {
-    struct hashtable_itr *itr = hashtable_iterator(hash);
-
-    do
-    {
-      printf("token: '%s'\n", (char *)hashtable_iterator_key(itr));
-      printf("value: '%s'\n", (char *)hashtable_iterator_value(itr));
-    }
-    while (hashtable_iterator_advance(itr));
+    return NULL;
 
 
-    free(itr);
-  }
-#endif
+  return remove_hash_entry(hash, token);
+}
 
 
+#if ENABLE_UNUSED_CODE
+#if DEBUG
+static void printSetupFileHash(SetupFileHash *hash)
+{
   BEGIN_HASH_ITERATION(hash, itr)
   {
     printf("token: '%s'\n", HASH_ITERATION_TOKEN(itr));
   BEGIN_HASH_ITERATION(hash, itr)
   {
     printf("token: '%s'\n", HASH_ITERATION_TOKEN(itr));
@@ -1221,129 +1809,369 @@ static void printSetupFileHash(SetupFileHash *hash)
 #endif
 #endif
 
 #endif
 #endif
 
-static void *loadSetupFileData(char *filename, boolean use_hash)
+#define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE           1
+#define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING           0
+#define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH            0
+
+static boolean token_value_separator_found = FALSE;
+#if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
+static boolean token_value_separator_warning = FALSE;
+#endif
+#if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
+static boolean token_already_exists_warning = FALSE;
+#endif
+
+static boolean getTokenValueFromSetupLineExt(char *line,
+                                            char **token_ptr, char **value_ptr,
+                                            char *filename, char *line_raw,
+                                            int line_nr,
+                                            boolean separator_required)
 {
 {
-  int line_len;
-  char line[MAX_LINE_LEN];
+  static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
   char *token, *value, *line_ptr;
   char *token, *value, *line_ptr;
-  void *setup_file_data;
-  FILE *file;
 
 
-  if (use_hash)
-    setup_file_data = newSetupFileHash();
-  else
-    setup_file_data = newSetupFileList("", "");
+  /* when externally invoked via ReadTokenValueFromLine(), copy line buffers */
+  if (line_raw == NULL)
+  {
+    strncpy(line_copy, line, MAX_LINE_LEN);
+    line_copy[MAX_LINE_LEN] = '\0';
+    line = line_copy;
 
 
-  if (!(file = fopen(filename, MODE_READ)))
+    strcpy(line_raw_copy, line_copy);
+    line_raw = line_raw_copy;
+  }
+
+  /* cut trailing comment from input line */
+  for (line_ptr = line; *line_ptr; line_ptr++)
   {
   {
-    Error(ERR_WARN, "cannot open configuration file '%s'", filename);
-    return NULL;
+    if (*line_ptr == '#')
+    {
+      *line_ptr = '\0';
+      break;
+    }
   }
 
   }
 
-  while(!feof(file))
+  /* 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')
+    return FALSE;
+
+  /* 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++)
   {
   {
-    /* read next line of input file */
-    if (!fgets(line, MAX_LINE_LEN, file))
+    /* first look for an explicit token/value separator, like ':' or '=' */
+    if (*line_ptr == ':' || *line_ptr == '=')
+    {
+      *line_ptr = '\0';                        /* terminate token string */
+      value = line_ptr + 1;            /* set beginning of value */
+
+      token_value_separator_found = TRUE;
+
       break;
       break;
+    }
+  }
 
 
-    /* cut trailing comment or whitespace from input line */
-    for (line_ptr = line; *line_ptr; line_ptr++)
+#if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
+  /* fallback: if no token/value separator found, also allow whitespaces */
+  if (!token_value_separator_found && !separator_required)
+  {
+    for (line_ptr = token; *line_ptr; line_ptr++)
     {
     {
-      if (*line_ptr == '#' || *line_ptr == '\n' || *line_ptr == '\r')
+      if (*line_ptr == ' ' || *line_ptr == '\t')
       {
       {
-       *line_ptr = '\0';
+       *line_ptr = '\0';               /* terminate token string */
+       value = line_ptr + 1;           /* set beginning of value */
+
+       token_value_separator_found = TRUE;
+
        break;
       }
     }
 
        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';
+#if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
+    if (token_value_separator_found)
+    {
+      if (!token_value_separator_warning)
+      {
+       Error(ERR_INFO_LINE, "-");
 
 
-    /* ignore empty lines */
-    if (*line == '\0')
-      continue;
+       if (filename != NULL)
+       {
+         Error(ERR_WARN, "missing token/value separator(s) in config file:");
+         Error(ERR_INFO, "- config file: '%s'", filename);
+       }
+       else
+       {
+         Error(ERR_WARN, "missing token/value separator(s):");
+       }
 
 
-    line_len = strlen(line);
+       token_value_separator_warning = TRUE;
+      }
 
 
-    /* cut leading whitespaces from token */
-    for (token = line; *token; token++)
-      if (*token != ' ' && *token != '\t')
-       break;
+      if (filename != NULL)
+       Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
+      else
+       Error(ERR_INFO, "- line: '%s'", line_raw);
+    }
+#endif
+  }
+#endif
 
 
-    /* find end of token */
-    for (line_ptr = token; *line_ptr; line_ptr++)
-    {
-      if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
-      {
+  /* 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)))
+  {
+    Error(ERR_DEBUG, "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 (!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';
        *line_ptr = '\0';
-       break;
-      }
+
+    /* 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 (line_ptr < line + line_len)
-      value = line_ptr + 1;
-    else
-      value = "\0";
+    /* 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 */
 
 
-    /* cut leading whitespaces from value */
-    for (; *value; value++)
-      if (*value != ' ' && *value != '\t')
-       break;
+      read_continued_line = TRUE;
+
+      continue;
+    }
+
+    if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
+                                      line_raw, line_nr, FALSE))
+      continue;
 
 
-    if (*token && *value)
+    if (*token)
     {
     {
-      if (use_hash)
-       setHashEntry((SetupFileHash *)setup_file_data, token, value);
+      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
+       {
+         Error(ERR_WARN, "ignoring already processed file '%s'", value);
+       }
+      }
       else
       else
-       setListEntry((SetupFileList *)setup_file_data, token, value);
+      {
+       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);
+  closeFile(file);
 
 
-  if (use_hash)
-  {
-    if (hashtable_count((SetupFileHash *)setup_file_data) == 0)
-      Error(ERR_WARN, "configuration file '%s' is empty", filename);
-  }
-  else
+#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;
+}
+
+void saveSetupFileHash(SetupFileHash *hash, char *filename)
+{
+  FILE *file;
+
+  if (!(file = fopen(filename, MODE_WRITE)))
   {
   {
-    SetupFileList *setup_file_list = (SetupFileList *)setup_file_data;
-    SetupFileList *first_valid_list_entry = setup_file_list->next;
+    Error(ERR_WARN, "cannot write configuration file '%s'", filename);
 
 
-    /* free empty list header */
-    setup_file_list->next = NULL;
-    freeSetupFileList(setup_file_list);
-    setup_file_data = first_valid_list_entry;
+    return;
+  }
 
 
-    if (first_valid_list_entry == NULL)
-      Error(ERR_WARN, "configuration file '%s' is empty", filename);
+  BEGIN_HASH_ITERATION(hash, itr)
+  {
+    fprintf(file, "%s\n", getFormattedSetupEntry(HASH_ITERATION_TOKEN(itr),
+                                                HASH_ITERATION_VALUE(itr)));
   }
   }
+  END_HASH_ITERATION(hash, itr)
 
 
-  return setup_file_data;
+  fclose(file);
 }
 
 SetupFileList *loadSetupFileList(char *filename)
 {
 }
 
 SetupFileList *loadSetupFileList(char *filename)
 {
-  return (SetupFileList *)loadSetupFileData(filename, FALSE);
+  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 *loadSetupFileHash(char *filename)
 {
-  return (SetupFileHash *)loadSetupFileData(filename, TRUE);
-}
+  SetupFileHash *setup_file_hash = newSetupFileHash();
 
 
-void checkSetupFileHashIdentifier(SetupFileHash *setup_file_hash,
-                                 char *identifier)
-{
-  char *value = getHashEntry(setup_file_hash, TOKEN_STR_FILE_IDENTIFIER);
+  if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
+  {
+    freeSetupFileHash(setup_file_hash);
 
 
-  if (value == NULL)
-    Error(ERR_WARN, "configuration file has no file identifier");
-  else if (!checkCookieString(value, identifier))
-    Error(ERR_WARN, "configuration file has wrong file identifier");
+    return NULL;
+  }
+
+  return setup_file_hash;
 }
 
 
 }
 
 
@@ -1351,132 +2179,343 @@ void checkSetupFileHashIdentifier(SetupFileHash *setup_file_hash,
 /* setup file stuff                                                          */
 /* ========================================================================= */
 
 /* 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"
+#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 */
 
 /* 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_IMPORTED_FROM  4
-#define LEVELINFO_TOKEN_LEVELS         5
-#define LEVELINFO_TOKEN_FIRST_LEVEL    6
-#define LEVELINFO_TOKEN_SORT_PRIORITY  7
-#define LEVELINFO_TOKEN_LEVEL_GROUP    8
-#define LEVELINFO_TOKEN_READONLY       9
-#define LEVELINFO_TOKEN_GRAPHICS_SET   10
-#define LEVELINFO_TOKEN_SOUNDS_SET     11
-#define LEVELINFO_TOKEN_MUSIC_SET      12
-
-#define NUM_LEVELINFO_TOKENS           13
+#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
 
 static LevelDirTree ldi;
 
 static struct TokenInfo levelinfo_tokens[] =
 {
   /* level directory info */
 
 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.imported_from,  "imported_from" },
-  { TYPE_INTEGER, &ldi.levels,         "levels"        },
-  { TYPE_INTEGER, &ldi.first_level,    "first_level"   },
-  { TYPE_INTEGER, &ldi.sort_priority,  "sort_priority" },
-  { TYPE_BOOLEAN, &ldi.level_group,    "level_group"   },
-  { TYPE_BOOLEAN, &ldi.readonly,       "readonly"      },
-  { TYPE_STRING,  &ldi.graphics_set,   "graphics_set"  },
-  { TYPE_STRING,  &ldi.sounds_set,     "sounds_set"    },
-  { TYPE_STRING,  &ldi.music_set,      "music_set"     }
+  { 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.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,        "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_BOOLEAN,      &ldi.handicap,          "handicap"              },
+  { TYPE_BOOLEAN,      &ldi.skip_levels,       "skip_levels"           }
 };
 
 };
 
-static void setTreeInfoToDefaults(TreeInfo *ldi, int type)
+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_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_INTEGER,      &ldi.color,             "color"                 },
+  { TYPE_STRING,       &ldi.class_desc,        "class_desc"            },
+
+  { -1,                        NULL,                   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->sort_priority = LEVELCLASS_UNDEFINED;    /* default: least priority */
+  ti->latest_engine = FALSE;                   /* default: get from level */
+  ti->parent_link = 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 = 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->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->skip_levels = FALSE;
+  }
+}
+
+static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
 {
 {
-  ldi->type = type;
+  if (parent == NULL)
+  {
+    Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
+
+    setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
+
+    return;
+  }
+
+  /* copy all values from the parent structure */
+
+  ti->type = parent->type;
 
 
-  ldi->node_top = (ldi->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
-                  ldi->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
-                  ldi->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
-                  ldi->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
-                  NULL);
+  ti->node_top = parent->node_top;
+  ti->node_parent = parent;
+  ti->node_group = NULL;
+  ti->next = NULL;
 
 
-  ldi->node_parent = NULL;
-  ldi->node_group = NULL;
-  ldi->next = NULL;
+  ti->cl_first = -1;
+  ti->cl_cursor = -1;
 
 
-  ldi->cl_first = -1;
-  ldi->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);
 
 
-  ldi->filename = NULL;
-  ldi->fullpath = NULL;
-  ldi->basepath = NULL;
-  ldi->identifier = NULL;
-  ldi->name = getStringCopy(ANONYMOUS_NAME);
-  ldi->name_sorting = NULL;
-  ldi->author = getStringCopy(ANONYMOUS_NAME);
+  ti->sort_priority = parent->sort_priority;
+  ti->latest_engine = parent->latest_engine;
+  ti->parent_link = 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);
 
 
-  ldi->sort_priority = LEVELCLASS_UNDEFINED;   /* default: least priority */
-  ldi->parent_link = FALSE;
-  ldi->user_defined = FALSE;
-  ldi->color = 0;
-  ldi->class_desc = NULL;
+  ti->infotext = getStringCopy(parent->infotext);
 
 
-  if (ldi->type == TREE_TYPE_LEVEL_DIR)
+  if (ti->type == TREE_TYPE_LEVEL_DIR)
   {
   {
-    ldi->imported_from = NULL;
-    ldi->graphics_set = NULL;
-    ldi->sounds_set = NULL;
-    ldi->music_set = NULL;
-    ldi->graphics_path = getStringCopy(UNDEFINED_FILENAME);
-    ldi->sounds_path = getStringCopy(UNDEFINED_FILENAME);
-    ldi->music_path = getStringCopy(UNDEFINED_FILENAME);
-    ldi->levels = 0;
-    ldi->first_level = 0;
-    ldi->last_level = 0;
-    ldi->level_group = FALSE;
-    ldi->handicap_level = 0;
-    ldi->readonly = TRUE;
+    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 = 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->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->skip_levels = parent->skip_levels;
   }
 }
 
   }
 }
 
-static void setTreeInfoToDefaultsFromParent(TreeInfo *ldi, TreeInfo *parent)
+static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
 {
 {
-  if (parent == NULL)
-  {
-    Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
+  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->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          = 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->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->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->skip_levels         = ti->skip_levels;
+
+  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;
+}
 
 
-    setTreeInfoToDefaults(ldi, TREE_TYPE_GENERIC);
+void freeTreeInfo(TreeInfo *ti)
+{
+  if (ti == NULL)
     return;
     return;
-  }
 
 
-  /* first copy all values from the parent structure ... */
-  *ldi = *parent;
+  checked_free(ti->subdir);
+  checked_free(ti->fullpath);
+  checked_free(ti->basepath);
+  checked_free(ti->identifier);
 
 
-  /* ... then set all fields to default that cannot be inherited from parent.
-     This is especially important for all those fields that can be set from
-     the 'levelinfo.conf' config file, because the function 'setSetupInfo()'
-     calls 'free()' for all already set token values which requires that no
-     other structure's pointer may point to them!
-  */
+  checked_free(ti->name);
+  checked_free(ti->name_sorting);
+  checked_free(ti->author);
+  checked_free(ti->year);
 
 
-  ldi->filename = NULL;
-  ldi->fullpath = NULL;
-  ldi->basepath = NULL;
-  ldi->identifier = NULL;
-  ldi->name = getStringCopy(ANONYMOUS_NAME);
-  ldi->name_sorting = NULL;
-  ldi->author = getStringCopy(parent->author);
-  ldi->imported_from = getStringCopy(parent->imported_from);
+  checked_free(ti->class_desc);
 
 
-  ldi->level_group = FALSE;
-  ldi->parent_link = FALSE;
+  checked_free(ti->infotext);
 
 
-  ldi->node_top = parent->node_top;
-  ldi->node_parent = parent;
-  ldi->node_group = NULL;
-  ldi->next = NULL;
+  if (ti->type == TREE_TYPE_LEVEL_DIR)
+  {
+    checked_free(ti->imported_from);
+    checked_free(ti->imported_by);
+    checked_free(ti->tested_by);
+
+    checked_free(ti->graphics_set_ecs);
+    checked_free(ti->graphics_set_aga);
+    checked_free(ti->graphics_set);
+    checked_free(ti->sounds_set);
+    checked_free(ti->music_set);
+
+    checked_free(ti->graphics_path);
+    checked_free(ti->sounds_path);
+    checked_free(ti->music_path);
+
+    checked_free(ti->level_filename);
+    checked_free(ti->level_filetype);
+
+    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);
 }
 
 void setSetupInfo(struct TokenInfo *token_info,
 }
 
 void setSetupInfo(struct TokenInfo *token_info,
@@ -1496,6 +2535,10 @@ void setSetupInfo(struct TokenInfo *token_info,
       *(boolean *)setup_value = get_boolean_from_string(token_value);
       break;
 
       *(boolean *)setup_value = get_boolean_from_string(token_value);
       break;
 
+    case TYPE_SWITCH3:
+      *(int *)setup_value = get_switch3_from_string(token_value);
+      break;
+
     case TYPE_KEY:
       *(Key *)setup_value = getKeyFromKeyName(token_value);
       break;
     case TYPE_KEY:
       *(Key *)setup_value = getKeyFromKeyName(token_value);
       break;
@@ -1509,8 +2552,7 @@ void setSetupInfo(struct TokenInfo *token_info,
       break;
 
     case TYPE_STRING:
       break;
 
     case TYPE_STRING:
-      if (*(char **)setup_value != NULL)
-       free(*(char **)setup_value);
+      checked_free(*(char **)setup_value);
       *(char **)setup_value = getStringCopy(token_value);
       break;
 
       *(char **)setup_value = getStringCopy(token_value);
       break;
 
@@ -1523,7 +2565,7 @@ static int compareTreeInfoEntries(const void *object1, const void *object2)
 {
   const TreeInfo *entry1 = *((TreeInfo **)object1);
   const TreeInfo *entry2 = *((TreeInfo **)object2);
 {
   const TreeInfo *entry1 = *((TreeInfo **)object1);
   const TreeInfo *entry2 = *((TreeInfo **)object2);
-  int class_sorting1, class_sorting2;
+  int class_sorting1 = 0, class_sorting2 = 0;
   int compare_result;
 
   if (entry1->type == TREE_TYPE_LEVEL_DIR)
   int compare_result;
 
   if (entry1->type == TREE_TYPE_LEVEL_DIR)
@@ -1531,7 +2573,9 @@ static int compareTreeInfoEntries(const void *object1, const void *object2)
     class_sorting1 = LEVELSORTING(entry1);
     class_sorting2 = LEVELSORTING(entry2);
   }
     class_sorting1 = LEVELSORTING(entry1);
     class_sorting2 = LEVELSORTING(entry2);
   }
-  else
+  else if (entry1->type == TREE_TYPE_GRAPHICS_DIR ||
+          entry1->type == TREE_TYPE_SOUNDS_DIR ||
+          entry1->type == TREE_TYPE_MUSIC_DIR)
   {
     class_sorting1 = ARTWORKSORTING(entry1);
     class_sorting2 = ARTWORKSORTING(entry2);
   {
     class_sorting1 = ARTWORKSORTING(entry1);
     class_sorting2 = ARTWORKSORTING(entry2);
@@ -1557,12 +2601,12 @@ static int compareTreeInfoEntries(const void *object1, const void *object2)
   return compare_result;
 }
 
   return compare_result;
 }
 
-static void createParentTreeInfoNode(TreeInfo *node_parent)
+static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
 {
   TreeInfo *ti_new;
 
   if (node_parent == NULL)
 {
   TreeInfo *ti_new;
 
   if (node_parent == NULL)
-    return;
+    return NULL;
 
   ti_new = newTreeInfo();
   setTreeInfoToDefaults(ti_new, node_parent->type);
 
   ti_new = newTreeInfo();
   setTreeInfoToDefaults(ti_new, node_parent->type);
@@ -1570,19 +2614,263 @@ static void createParentTreeInfoNode(TreeInfo *node_parent)
   ti_new->node_parent = node_parent;
   ti_new->parent_link = TRUE;
 
   ti_new->node_parent = node_parent;
   ti_new->parent_link = TRUE;
 
-  ti_new->identifier = getStringCopy(node_parent->identifier);
-  ti_new->name = ".. (parent directory)";
-  ti_new->name_sorting = getStringCopy(ti_new->name);
+  setString(&ti_new->identifier, node_parent->identifier);
+  setString(&ti_new->name, ".. (parent directory)");
+  setString(&ti_new->name_sorting, ti_new->name);
 
 
-  ti_new->filename = "..";
-  ti_new->fullpath = getStringCopy(node_parent->fullpath);
+  setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
+  setString(&ti_new->fullpath, node_parent->fullpath);
 
   ti_new->sort_priority = node_parent->sort_priority;
 
   ti_new->sort_priority = node_parent->sort_priority;
-  ti_new->class_desc = getLevelClassDescription(ti_new);
+  ti_new->latest_engine = node_parent->latest_engine;
+
+  setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
 
   pushTreeInfo(&node_parent->node_group, 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, ".");
+
+  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;
+}
+
+
+/* -------------------------------------------------------------------------- */
+/* functions for handling level and custom artwork info cache                 */
+/* -------------------------------------------------------------------------- */
+
+static void LoadArtworkInfoCache()
+{
+  InitCacheDirectory();
+
+  if (artworkinfo_cache_old == NULL)
+  {
+    char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
+
+    /* try to load artwork info hash from already existing cache file */
+    artworkinfo_cache_old = loadSetupFileHash(filename);
+
+    /* if no artwork info cache file was found, start with empty hash */
+    if (artworkinfo_cache_old == NULL)
+      artworkinfo_cache_old = newSetupFileHash();
+
+    free(filename);
+  }
+
+  if (artworkinfo_cache_new == NULL)
+    artworkinfo_cache_new = newSetupFileHash();
+}
+
+static void SaveArtworkInfoCache()
+{
+  char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
+
+  InitCacheDirectory();
+
+  saveSetupFileHash(artworkinfo_cache_new, filename);
+
+  free(filename);
+}
+
+static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
+{
+  static char *prefix = NULL;
+
+  checked_free(prefix);
+
+  prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
+
+  return prefix;
+}
+
+/* (identical to above function, but separate string buffer needed -- nasty) */
+static char *getCacheToken(char *prefix, char *suffix)
+{
+  static char *token = NULL;
+
+  checked_free(token);
+
+  token = getStringCat2WithSeparator(prefix, suffix, ".");
+
+  return token;
+}
+
+static char *getFileTimestampString(char *filename)
+{
+  return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
+}
+
+static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
+{
+  struct stat file_status;
+
+  if (timestamp_string == NULL)
+    return TRUE;
+
+  if (stat(filename, &file_status) != 0)       /* cannot stat file */
+    return TRUE;
+
+  return (file_status.st_mtime != atoi(timestamp_string));
+}
+
+static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
+{
+  char *identifier = level_node->subdir;
+  char *type_string = ARTWORK_DIRECTORY(type);
+  char *token_prefix = getCacheTokenPrefix(type_string, identifier);
+  char *token_main = getCacheToken(token_prefix, "CACHED");
+  char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
+  boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
+  TreeInfo *artwork_info = NULL;
+
+  if (!use_artworkinfo_cache)
+    return NULL;
+
+  if (cached)
+  {
+    int i;
+
+    artwork_info = newTreeInfo();
+    setTreeInfoToDefaults(artwork_info, type);
+
+    /* set all structure fields according to the token/value pairs */
+    ldi = *artwork_info;
+    for (i = 0; artworkinfo_tokens[i].type != -1; i++)
+    {
+      char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
+      char *value = getHashEntry(artworkinfo_cache_old, token);
+
+      setSetupInfo(artworkinfo_tokens, i, value);
+
+      /* check if cache entry for this item is invalid or incomplete */
+      if (value == NULL)
+      {
+       Error(ERR_WARN, "cache entry '%s' invalid", token);
+
+       cached = FALSE;
+      }
+    }
+
+    *artwork_info = ldi;
+  }
+
+  if (cached)
+  {
+    char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
+                                       LEVELINFO_FILENAME);
+    char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
+                                         ARTWORKINFO_FILENAME(type));
+
+    /* check if corresponding "levelinfo.conf" file has changed */
+    token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
+    cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
+
+    if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
+      cached = FALSE;
+
+    /* check if corresponding "<artworkinfo>.conf" file has changed */
+    token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
+    cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
+
+    if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
+      cached = FALSE;
+
+    checked_free(filename_levelinfo);
+    checked_free(filename_artworkinfo);
+  }
+
+  if (!cached && artwork_info != NULL)
+  {
+    freeTreeInfo(artwork_info);
+
+    return NULL;
+  }
+
+  return artwork_info;
+}
+
+static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
+                                    LevelDirTree *level_node, int type)
+{
+  char *identifier = level_node->subdir;
+  char *type_string = ARTWORK_DIRECTORY(type);
+  char *token_prefix = getCacheTokenPrefix(type_string, identifier);
+  char *token_main = getCacheToken(token_prefix, "CACHED");
+  boolean set_cache_timestamps = TRUE;
+  int i;
+
+  setHashEntry(artworkinfo_cache_new, token_main, "true");
+
+  if (set_cache_timestamps)
+  {
+    char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
+                                       LEVELINFO_FILENAME);
+    char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
+                                         ARTWORKINFO_FILENAME(type));
+    char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
+    char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
+
+    token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
+    setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
+
+    token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
+    setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
+
+    checked_free(filename_levelinfo);
+    checked_free(filename_artworkinfo);
+    checked_free(timestamp_levelinfo);
+    checked_free(timestamp_artworkinfo);
+  }
+
+  ldi = *artwork_info;
+  for (i = 0; artworkinfo_tokens[i].type != -1; i++)
+  {
+    char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
+    char *value = getSetupValue(artworkinfo_tokens[i].type,
+                               artworkinfo_tokens[i].value);
+    if (value != NULL)
+      setHashEntry(artworkinfo_cache_new, token, value);
+  }
 }
 
 }
 
+
+/* -------------------------------------------------------------------------- */
+/* functions for loading level info and custom artwork info                   */
+/* -------------------------------------------------------------------------- */
+
 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
 
 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
 
@@ -1593,10 +2881,21 @@ static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
 {
   char *directory_path = getPath2(level_directory, directory_name);
   char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
 {
   char *directory_path = getPath2(level_directory, directory_name);
   char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
-  SetupFileHash *setup_file_hash = loadSetupFileHash(filename);
+  SetupFileHash *setup_file_hash;
   LevelDirTree *leveldir_new = NULL;
   int i;
 
   LevelDirTree *leveldir_new = NULL;
   int i;
 
+  /* unless debugging, silently ignore directories without "levelinfo.conf" */
+  if (!options.debug && !fileExists(filename))
+  {
+    free(directory_path);
+    free(filename);
+
+    return FALSE;
+  }
+
+  setup_file_hash = loadSetupFileHash(filename);
+
   if (setup_file_hash == NULL)
   {
     Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
   if (setup_file_hash == NULL)
   {
     Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
@@ -1614,58 +2913,64 @@ static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
   else
     setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
 
   else
     setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
 
-  leveldir_new->filename = getStringCopy(directory_name);
-
-  checkSetupFileHashIdentifier(setup_file_hash, getCookie("LEVELINFO"));
+  leveldir_new->subdir = getStringCopy(directory_name);
 
   /* set all structure fields according to the token/value pairs */
   ldi = *leveldir_new;
 
   /* set all structure fields according to the token/value pairs */
   ldi = *leveldir_new;
-  for (i=0; i<NUM_LEVELINFO_TOKENS; i++)
+  for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
     setSetupInfo(levelinfo_tokens, i,
                 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
   *leveldir_new = ldi;
 
     setSetupInfo(levelinfo_tokens, i,
                 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
   *leveldir_new = ldi;
 
-  if (strcmp(leveldir_new->name, ANONYMOUS_NAME) == 0)
-  {
-    free(leveldir_new->name);
-    leveldir_new->name = getStringCopy(leveldir_new->filename);
-  }
-
-  DrawInitText(leveldir_new->name, 150, FC_YELLOW);
+  if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
+    setString(&leveldir_new->name, leveldir_new->subdir);
 
   if (leveldir_new->identifier == NULL)
 
   if (leveldir_new->identifier == NULL)
-    leveldir_new->identifier = getStringCopy(leveldir_new->filename);
+    leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
 
   if (leveldir_new->name_sorting == NULL)
     leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
 
   if (node_parent == NULL)             /* top level group */
   {
 
   if (leveldir_new->name_sorting == NULL)
     leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
 
   if (node_parent == NULL)             /* top level group */
   {
-    leveldir_new->basepath = level_directory;
-    leveldir_new->fullpath = leveldir_new->filename;
+    leveldir_new->basepath = getStringCopy(level_directory);
+    leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
   }
   else                                 /* sub level group */
   {
   }
   else                                 /* sub level group */
   {
-    leveldir_new->basepath = node_parent->basepath;
+    leveldir_new->basepath = getStringCopy(node_parent->basepath);
     leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
   }
 
     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->last_level =
     leveldir_new->first_level + leveldir_new->levels - 1;
 
+  leveldir_new->in_user_dir =
+    (!strEqual(leveldir_new->basepath, options.level_directory));
+
+  /* adjust some settings if user's private level directory was detected */
+  if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
+      leveldir_new->in_user_dir &&
+      (strEqual(leveldir_new->subdir, getLoginName()) ||
+       strEqual(leveldir_new->name,   getLoginName()) ||
+       strEqual(leveldir_new->author, getRealName())))
+  {
+    leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
+    leveldir_new->readonly = FALSE;
+  }
+
   leveldir_new->user_defined =
   leveldir_new->user_defined =
-    (leveldir_new->basepath == options.level_directory ? FALSE : TRUE);
+    (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
 
   leveldir_new->color = LEVELCOLOR(leveldir_new);
 
   leveldir_new->color = LEVELCOLOR(leveldir_new);
-  leveldir_new->class_desc = getLevelClassDescription(leveldir_new);
+
+  setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
 
   leveldir_new->handicap_level =       /* set handicap to default value */
 
   leveldir_new->handicap_level =       /* set handicap to default value */
-    (leveldir_new->user_defined ?
-     leveldir_new->last_level :
-     leveldir_new->first_level);
+    (leveldir_new->user_defined || !leveldir_new->handicap ?
+     leveldir_new->last_level : leveldir_new->first_level);
+
+  DrawInitText(leveldir_new->name, 150, FC_YELLOW);
 
   pushTreeInfo(node_first, leveldir_new);
 
 
   pushTreeInfo(node_first, leveldir_new);
 
@@ -1676,7 +2981,7 @@ static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
     /* create node to link back to current level directory */
     createParentTreeInfoNode(leveldir_new);
 
     /* create node to link back to current level directory */
     createParentTreeInfoNode(leveldir_new);
 
-    /* step into sub-directory and look for more level series */
+    /* recursively step into sub-directory and look for more level series */
     LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
                              leveldir_new, directory_path);
   }
     LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
                              leveldir_new, directory_path);
   }
@@ -1691,43 +2996,44 @@ static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
                                      TreeInfo *node_parent,
                                      char *level_directory)
 {
                                      TreeInfo *node_parent,
                                      char *level_directory)
 {
-  DIR *dir;
-  struct dirent *dir_entry;
+  Directory *dir;
+  DirectoryEntry *dir_entry;
   boolean valid_entry_found = FALSE;
 
   boolean valid_entry_found = FALSE;
 
-  if ((dir = opendir(level_directory)) == NULL)
+  if ((dir = openDirectory(level_directory)) == NULL)
   {
     Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
   {
     Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
+
     return;
   }
 
     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(level_directory, directory_name);
 
     /* skip entries for current and parent directory */
     char *directory_path = getPath2(level_directory, directory_name);
 
     /* skip entries for current and parent directory */
-    if (strcmp(directory_name, ".")  == 0 ||
-       strcmp(directory_name, "..") == 0)
+    if (strEqual(directory_name, ".") ||
+       strEqual(directory_name, ".."))
     {
       free(directory_path);
     {
       free(directory_path);
+
       continue;
     }
 
     /* find out if directory entry is itself a directory */
       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 */
+    if (!dir_entry->is_directory)                      /* not a directory */
     {
       free(directory_path);
     {
       free(directory_path);
+
       continue;
     }
 
     free(directory_path);
 
       continue;
     }
 
     free(directory_path);
 
-    if (strcmp(directory_name, GRAPHICS_DIRECTORY) == 0 ||
-       strcmp(directory_name, SOUNDS_DIRECTORY) == 0 ||
-       strcmp(directory_name, MUSIC_DIRECTORY) == 0)
+    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,
       continue;
 
     valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
@@ -1735,9 +3041,10 @@ static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
                                                    directory_name);
   }
 
                                                    directory_name);
   }
 
-  closedir(dir);
+  closeDirectory(dir);
 
 
-  if (!valid_entry_found)
+  /* 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,
   {
     /* check if this directory directly contains a file "levelinfo.conf" */
     valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
@@ -1749,24 +3056,45 @@ static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
          level_directory);
 }
 
          level_directory);
 }
 
+boolean AdjustGraphicsForEMC()
+{
+  boolean settings_changed = FALSE;
+
+  settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
+  settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
+
+  return settings_changed;
+}
+
 void LoadLevelInfo()
 {
   InitUserLevelDirectory(getLoginName());
 
 void LoadLevelInfo()
 {
   InitUserLevelDirectory(getLoginName());
 
-  DrawInitText("Loading level series:", 120, FC_GREEN);
+  DrawInitText("Loading level series", 120, FC_GREEN);
 
   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
 
 
   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
+     checked for existing artwork in the function "LoadLevelArtworkInfo()") */
+  leveldir_first_all = leveldir_first;
+  cloneTree(&leveldir_first, leveldir_first_all, TRUE);
+
+  AdjustGraphicsForEMC();
+
   /* before sorting, the first entries will be from the user directory */
   leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
 
   if (leveldir_first == NULL)
     Error(ERR_EXIT, "cannot find any valid level series in any directory");
 
   /* before sorting, the first entries will be from the user directory */
   leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
 
   if (leveldir_first == NULL)
     Error(ERR_EXIT, "cannot find any valid level series in any directory");
 
-  sortTreeInfo(&leveldir_first, compareTreeInfoEntries);
+  sortTreeInfo(&leveldir_first);
 
 
-#if 0
+#if ENABLE_UNUSED_CODE
   dumpTreeInfo(leveldir_first, 0);
 #endif
 }
   dumpTreeInfo(leveldir_first, 0);
 #endif
 }
@@ -1782,34 +3110,33 @@ static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
   TreeInfo *artwork_new = NULL;
   int i;
 
   TreeInfo *artwork_new = NULL;
   int i;
 
-  if (access(filename, F_OK) == 0)             /* file exists */
+  if (fileExists(filename))
     setup_file_hash = loadSetupFileHash(filename);
 
   if (setup_file_hash == NULL) /* no config file -- look for artwork files */
   {
     setup_file_hash = loadSetupFileHash(filename);
 
   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;
 
     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;
        {
          valid_file_found = TRUE;
+
          break;
        }
       }
 
          break;
        }
       }
 
-      closedir(dir);
+      closeDirectory(dir);
     }
 
     if (!valid_file_found)
     {
     }
 
     if (!valid_file_found)
     {
-      if (strcmp(directory_name, ".") != 0)
+      if (!strEqual(directory_name, "."))
        Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
 
       free(directory_path);
        Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
 
       free(directory_path);
@@ -1826,33 +3153,22 @@ static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
   else
     setTreeInfoToDefaults(artwork_new, type);
 
   else
     setTreeInfoToDefaults(artwork_new, type);
 
-  artwork_new->filename = getStringCopy(directory_name);
+  artwork_new->subdir = getStringCopy(directory_name);
 
   if (setup_file_hash) /* (before defining ".color" and ".class_desc") */
   {
 
   if (setup_file_hash) /* (before defining ".color" and ".class_desc") */
   {
-#if 0
-    checkSetupFileHashIdentifier(setup_file_hash, getCookie("..."));
-#endif
-
     /* set all structure fields according to the token/value pairs */
     ldi = *artwork_new;
     /* set all structure fields according to the token/value pairs */
     ldi = *artwork_new;
-    for (i=0; i<NUM_LEVELINFO_TOKENS; i++)
+    for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
       setSetupInfo(levelinfo_tokens, i,
                   getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
     *artwork_new = ldi;
 
       setSetupInfo(levelinfo_tokens, i,
                   getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
     *artwork_new = ldi;
 
-    if (strcmp(artwork_new->name, ANONYMOUS_NAME) == 0)
-    {
-      free(artwork_new->name);
-      artwork_new->name = getStringCopy(artwork_new->filename);
-    }
-
-#if 0
-    DrawInitText(artwork_new->name, 150, FC_YELLOW);
-#endif
+    if (strEqual(artwork_new->name, ANONYMOUS_NAME))
+      setString(&artwork_new->name, artwork_new->subdir);
 
     if (artwork_new->identifier == NULL)
 
     if (artwork_new->identifier == NULL)
-      artwork_new->identifier = getStringCopy(artwork_new->filename);
+      artwork_new->identifier = getStringCopy(artwork_new->subdir);
 
     if (artwork_new->name_sorting == NULL)
       artwork_new->name_sorting = getStringCopy(artwork_new->name);
 
     if (artwork_new->name_sorting == NULL)
       artwork_new->name_sorting = getStringCopy(artwork_new->name);
@@ -1861,7 +3177,7 @@ static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
   if (node_parent == NULL)             /* top level group */
   {
     artwork_new->basepath = getStringCopy(base_directory);
   if (node_parent == NULL)             /* top level group */
   {
     artwork_new->basepath = getStringCopy(base_directory);
-    artwork_new->fullpath = getStringCopy(artwork_new->filename);
+    artwork_new->fullpath = getStringCopy(artwork_new->subdir);
   }
   else                                 /* sub level group */
   {
   }
   else                                 /* sub level group */
   {
@@ -1869,46 +3185,44 @@ static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
     artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
   }
 
     artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
   }
 
-  artwork_new->user_defined =
-    (artwork_new->basepath == OPTIONS_ARTWORK_DIRECTORY(type) ? FALSE : TRUE);
+  artwork_new->in_user_dir =
+    (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
 
   /* (may use ".sort_priority" from "setup_file_hash" above) */
   artwork_new->color = ARTWORKCOLOR(artwork_new);
 
   /* (may use ".sort_priority" from "setup_file_hash" above) */
   artwork_new->color = ARTWORKCOLOR(artwork_new);
-  artwork_new->class_desc = getLevelClassDescription(artwork_new);
+
+  setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
 
   if (setup_file_hash == NULL) /* (after determining ".user_defined") */
   {
 
   if (setup_file_hash == NULL) /* (after determining ".user_defined") */
   {
-    if (artwork_new->name != NULL)
-      free(artwork_new->name);
-
-    if (strcmp(artwork_new->filename, ".") == 0)
+    if (strEqual(artwork_new->subdir, "."))
     {
       if (artwork_new->user_defined)
       {
     {
       if (artwork_new->user_defined)
       {
-       artwork_new->identifier = getStringCopy("private");
-       artwork_new->sort_priority = ARTWORKCLASS_USER;
+       setString(&artwork_new->identifier, "private");
+       artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
       }
       else
       {
       }
       else
       {
-       artwork_new->identifier = getStringCopy("classic");
+       setString(&artwork_new->identifier, "classic");
        artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
       }
 
       /* set to new values after changing ".sort_priority" */
       artwork_new->color = ARTWORKCOLOR(artwork_new);
        artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
       }
 
       /* set to new values after changing ".sort_priority" */
       artwork_new->color = ARTWORKCOLOR(artwork_new);
-      artwork_new->class_desc = getLevelClassDescription(artwork_new);
+
+      setString(&artwork_new->class_desc,
+               getLevelClassDescription(artwork_new));
     }
     else
     {
     }
     else
     {
-      artwork_new->identifier = getStringCopy(artwork_new->filename);
+      setString(&artwork_new->identifier, artwork_new->subdir);
     }
 
     }
 
-    artwork_new->name = getStringCopy(artwork_new->identifier);
-    artwork_new->name_sorting = getStringCopy(artwork_new->name);
+    setString(&artwork_new->name, artwork_new->identifier);
+    setString(&artwork_new->name_sorting, artwork_new->name);
   }
 
   }
 
-  DrawInitText(artwork_new->name, 150, FC_YELLOW);
-
   pushTreeInfo(node_first, artwork_new);
 
   freeSetupFileHash(setup_file_hash);
   pushTreeInfo(node_first, artwork_new);
 
   freeSetupFileHash(setup_file_hash);
@@ -1923,51 +3237,53 @@ static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
                                          TreeInfo *node_parent,
                                          char *base_directory, int type)
 {
                                          TreeInfo *node_parent,
                                          char *base_directory, int type)
 {
-  DIR *dir;
-  struct dirent *dir_entry;
+  Directory *dir;
+  DirectoryEntry *dir_entry;
   boolean valid_entry_found = FALSE;
 
   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))
       Error(ERR_WARN, "cannot read directory '%s'", base_directory);
     if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
       Error(ERR_WARN, "cannot read directory '%s'", base_directory);
+
     return;
   }
 
     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);
 
     char *directory_path = getPath2(base_directory, directory_name);
 
-    /* skip entries for current and parent directory */
-    if (strcmp(directory_name, ".")  == 0 ||
-       strcmp(directory_name, "..") == 0)
+    /* skip directory entries for current and parent directory */
+    if (strEqual(directory_name, ".") ||
+       strEqual(directory_name, ".."))
     {
       free(directory_path);
     {
       free(directory_path);
+
       continue;
     }
 
       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 */
+    /* skip directory entries which are not a directory */
+    if (!dir_entry->is_directory)                      /* not a directory */
     {
       free(directory_path);
     {
       free(directory_path);
+
       continue;
     }
 
     free(directory_path);
 
     /* check if this directory contains artwork with or without config file */
       continue;
     }
 
     free(directory_path);
 
     /* check if this directory contains artwork with or without config file */
-    valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first,node_parent,
+    valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
                                                        base_directory,
                                                        directory_name, type);
   }
 
                                                        base_directory,
                                                        directory_name, type);
   }
 
-  closedir(dir);
+  closeDirectory(dir);
 
   /* check if this directory directly contains artwork itself */
 
   /* check if this directory directly contains artwork itself */
-  valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first,node_parent,
+  valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
                                                      base_directory, ".",
                                                      type);
   if (!valid_entry_found)
                                                      base_directory, ".",
                                                      type);
   if (!valid_entry_found)
@@ -1982,23 +3298,22 @@ static TreeInfo *getDummyArtworkInfo(int type)
 
   setTreeInfoToDefaults(artwork_new, type);
 
 
   setTreeInfoToDefaults(artwork_new, type);
 
-  artwork_new->filename = getStringCopy(UNDEFINED_FILENAME);
-  artwork_new->fullpath = getStringCopy(UNDEFINED_FILENAME);
-  artwork_new->basepath = getStringCopy(UNDEFINED_FILENAME);
-
-  if (artwork_new->name != NULL)
-    free(artwork_new->name);
+  setString(&artwork_new->subdir,   UNDEFINED_FILENAME);
+  setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
+  setString(&artwork_new->basepath, UNDEFINED_FILENAME);
 
 
-  artwork_new->identifier   = getStringCopy(UNDEFINED_FILENAME);
-  artwork_new->name         = getStringCopy(UNDEFINED_FILENAME);
-  artwork_new->name_sorting = getStringCopy(UNDEFINED_FILENAME);
+  setString(&artwork_new->identifier,   UNDEFINED_FILENAME);
+  setString(&artwork_new->name,         UNDEFINED_FILENAME);
+  setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
 
   return artwork_new;
 }
 
 void LoadArtworkInfo()
 {
 
   return artwork_new;
 }
 
 void LoadArtworkInfo()
 {
-  DrawInitText("Looking for custom artwork:", 120, FC_GREEN);
+  LoadArtworkInfoCache();
+
+  DrawInitText("Looking for custom artwork", 120, FC_GREEN);
 
   LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
                                options.graphics_directory,
 
   LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
                                options.graphics_directory,
@@ -2031,16 +3346,25 @@ void LoadArtworkInfo()
   /* before sorting, the first entries will be from the user directory */
   artwork.gfx_current =
     getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
   /* before sorting, the first entries will be from the user directory */
   artwork.gfx_current =
     getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
+  if (artwork.gfx_current == NULL)
+    artwork.gfx_current =
+      getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
   if (artwork.gfx_current == NULL)
     artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
 
   artwork.snd_current =
     getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
   if (artwork.gfx_current == NULL)
     artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
 
   artwork.snd_current =
     getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
+  if (artwork.snd_current == NULL)
+    artwork.snd_current =
+      getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
   if (artwork.snd_current == NULL)
     artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
 
   artwork.mus_current =
     getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
   if (artwork.snd_current == NULL)
     artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
 
   artwork.mus_current =
     getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
+  if (artwork.mus_current == NULL)
+    artwork.mus_current =
+      getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
   if (artwork.mus_current == NULL)
     artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
 
   if (artwork.mus_current == NULL)
     artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
 
@@ -2048,17 +3372,17 @@ void LoadArtworkInfo()
   artwork.snd_current_identifier = artwork.snd_current->identifier;
   artwork.mus_current_identifier = artwork.mus_current->identifier;
 
   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);
 #endif
 
   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);
 #endif
 
-  sortTreeInfo(&artwork.gfx_first, compareTreeInfoEntries);
-  sortTreeInfo(&artwork.snd_first, compareTreeInfoEntries);
-  sortTreeInfo(&artwork.mus_first, compareTreeInfoEntries);
+  sortTreeInfo(&artwork.gfx_first);
+  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);
   dumpTreeInfo(artwork.gfx_first, 0);
   dumpTreeInfo(artwork.snd_first, 0);
   dumpTreeInfo(artwork.mus_first, 0);
@@ -2068,42 +3392,51 @@ void LoadArtworkInfo()
 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
                                  LevelDirTree *level_node)
 {
 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
                                  LevelDirTree *level_node)
 {
+  int type = (*artwork_node)->type;
+
   /* recursively check all level directories for artwork sub-directories */
 
   while (level_node)
   {
   /* recursively check all level directories for artwork sub-directories */
 
   while (level_node)
   {
-    char *path = getPath2(getLevelDirFromTreeInfo(level_node),
-                         ARTWORK_DIRECTORY((*artwork_node)->type));
-
-#if 0
-    if (!level_node->parent_link)
-      printf("CHECKING '%s' ['%s', '%s'] ...\n", path,
-            level_node->filename, level_node->name);
-#endif
-
+    /* check all tree entries for artwork, but skip parent link entries */
     if (!level_node->parent_link)
     {
     if (!level_node->parent_link)
     {
-      TreeInfo *topnode_last = *artwork_node;
+      TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
+      boolean cached = (artwork_new != NULL);
 
 
-      LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path,
-                                   (*artwork_node)->type);
-
-      if (topnode_last != *artwork_node)
+      if (cached)
+      {
+       pushTreeInfo(artwork_node, artwork_new);
+      }
+      else
       {
       {
-       free((*artwork_node)->identifier);
-       free((*artwork_node)->name);
-       free((*artwork_node)->name_sorting);
+       TreeInfo *topnode_last = *artwork_node;
+       char *path = getPath2(getLevelDirFromTreeInfo(level_node),
+                             ARTWORK_DIRECTORY(type));
+
+       LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
+
+       if (topnode_last != *artwork_node)      /* check for newly added node */
+       {
+         artwork_new = *artwork_node;
+
+         setString(&artwork_new->identifier,   level_node->subdir);
+         setString(&artwork_new->name,         level_node->name);
+         setString(&artwork_new->name_sorting, level_node->name_sorting);
 
 
-       (*artwork_node)->identifier   = getStringCopy(level_node->filename);
-       (*artwork_node)->name         = getStringCopy(level_node->name);
-       (*artwork_node)->name_sorting = getStringCopy(level_node->name);
+         artwork_new->sort_priority = level_node->sort_priority;
+         artwork_new->color = LEVELCOLOR(artwork_new);
+       }
 
 
-       (*artwork_node)->sort_priority = level_node->sort_priority;
-       (*artwork_node)->color = LEVELCOLOR((*artwork_node));
+       free(path);
       }
       }
+
+      /* insert artwork info (from old cache or filesystem) into new cache */
+      if (artwork_new != NULL)
+       setArtworkInfoCacheEntry(artwork_new, level_node, type);
     }
 
     }
 
-    free(path);
+    DrawInitText(level_node->name, 150, FC_YELLOW);
 
     if (level_node->node_group != NULL)
       LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
 
     if (level_node->node_group != NULL)
       LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
@@ -2114,89 +3447,251 @@ void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
 
 void LoadLevelArtworkInfo()
 {
 
 void LoadLevelArtworkInfo()
 {
-  DrawInitText("Looking for custom level artwork:", 120, FC_GREEN);
+  print_timestamp_init("LoadLevelArtworkInfo");
 
 
-  LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first);
-  LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first);
-  LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first);
+  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 */
 
   /* needed for reloading level artwork not known at ealier stage */
-  if (strcmp(artwork.gfx_current_identifier, setup.graphics_set) != 0)
+
+  if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
   {
     artwork.gfx_current =
       getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
   {
     artwork.gfx_current =
       getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
+    if (artwork.gfx_current == NULL)
+      artwork.gfx_current =
+       getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
     if (artwork.gfx_current == NULL)
       artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
   }
 
     if (artwork.gfx_current == NULL)
       artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
   }
 
-  if (strcmp(artwork.snd_current_identifier, setup.sounds_set) != 0)
+  if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
   {
     artwork.snd_current =
       getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
   {
     artwork.snd_current =
       getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
+    if (artwork.snd_current == NULL)
+      artwork.snd_current =
+       getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
     if (artwork.snd_current == NULL)
       artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
   }
 
     if (artwork.snd_current == NULL)
       artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
   }
 
-  if (strcmp(artwork.mus_current_identifier, setup.music_set) != 0)
+  if (!strEqual(artwork.mus_current_identifier, setup.music_set))
   {
     artwork.mus_current =
       getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
   {
     artwork.mus_current =
       getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
+    if (artwork.mus_current == NULL)
+      artwork.mus_current =
+       getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
     if (artwork.mus_current == NULL)
       artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
   }
 
     if (artwork.mus_current == NULL)
       artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
   }
 
-  sortTreeInfo(&artwork.gfx_first, compareTreeInfoEntries);
-  sortTreeInfo(&artwork.snd_first, compareTreeInfoEntries);
-  sortTreeInfo(&artwork.mus_first, compareTreeInfoEntries);
+  print_timestamp_time("getTreeInfoFromIdentifier");
 
 
-#if 0
+  sortTreeInfo(&artwork.gfx_first);
+  sortTreeInfo(&artwork.snd_first);
+  sortTreeInfo(&artwork.mus_first);
+
+  print_timestamp_time("sortTreeInfo");
+
+#if ENABLE_UNUSED_CODE
   dumpTreeInfo(artwork.gfx_first, 0);
   dumpTreeInfo(artwork.snd_first, 0);
   dumpTreeInfo(artwork.mus_first, 0);
 #endif
   dumpTreeInfo(artwork.gfx_first, 0);
   dumpTreeInfo(artwork.snd_first, 0);
   dumpTreeInfo(artwork.mus_first, 0);
 #endif
+
+  print_timestamp_done("LoadLevelArtworkInfo");
 }
 
 }
 
-static void SaveUserLevelInfo()
+static boolean AddUserLevelSetToLevelInfoExt(char *level_subdir_new)
+{
+  // get level info tree node of first (original) user level set
+  char *level_subdir_old = getLoginName();
+  LevelDirTree *leveldir_old = getTreeInfoFromIdentifier(leveldir_first,
+                                                        level_subdir_old);
+  if (leveldir_old == NULL)            // should not happen
+    return FALSE;
+
+  int draw_deactivation_mask = GetDrawDeactivationMask();
+
+  // override draw deactivation mask (temporarily disable drawing)
+  SetDrawDeactivationMask(REDRAW_ALL);
+
+  // load new level set config and add it next to first user level set
+  LoadLevelInfoFromLevelConf(&leveldir_old->next, NULL,
+                            leveldir_old->basepath, level_subdir_new);
+
+  // set draw deactivation mask to previous value
+  SetDrawDeactivationMask(draw_deactivation_mask);
+
+  // get level info tree node of newly added user level set
+  LevelDirTree *leveldir_new = getTreeInfoFromIdentifier(leveldir_first,
+                                                        level_subdir_new);
+  if (leveldir_new == NULL)            // should not happen
+    return FALSE;
+
+  // correct top link and parent node link of newly created tree node
+  leveldir_new->node_top    = leveldir_old->node_top;
+  leveldir_new->node_parent = leveldir_old->node_parent;
+
+  // sort level info tree to adjust position of newly added level set
+  sortTreeInfo(&leveldir_first);
+
+  return TRUE;
+}
+
+void AddUserLevelSetToLevelInfo(char *level_subdir_new)
+{
+  if (!AddUserLevelSetToLevelInfoExt(level_subdir_new))
+    Error(ERR_EXIT, "internal level set structure corrupted -- aborting");
+}
+
+boolean UpdateUserLevelSet(char *level_subdir, char *level_name,
+                          char *level_author, int num_levels)
+{
+  char *filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
+  char *filename_tmp = getStringCat2(filename, ".tmp");
+  FILE *file = NULL;
+  FILE *file_tmp = NULL;
+  char line[MAX_LINE_LEN];
+  boolean success = FALSE;
+  LevelDirTree *leveldir = getTreeInfoFromIdentifier(leveldir_first,
+                                                    level_subdir);
+  // update values in level directory tree
+
+  if (level_name != NULL)
+    setString(&leveldir->name, level_name);
+
+  if (level_author != NULL)
+    setString(&leveldir->author, level_author);
+
+  if (num_levels != -1)
+    leveldir->levels = num_levels;
+
+  // update values that depend on other values
+
+  setString(&leveldir->name_sorting, leveldir->name);
+
+  leveldir->last_level = leveldir->first_level + leveldir->levels - 1;
+
+  // sort order of level sets may have changed
+  sortTreeInfo(&leveldir_first);
+
+  if ((file     = fopen(filename,     MODE_READ)) &&
+      (file_tmp = fopen(filename_tmp, MODE_WRITE)))
+  {
+    while (fgets(line, MAX_LINE_LEN, file))
+    {
+      if (strPrefix(line, "name:") && level_name != NULL)
+       fprintf(file_tmp, "%-32s%s\n", "name:", level_name);
+      else if (strPrefix(line, "author:") && level_author != NULL)
+       fprintf(file_tmp, "%-32s%s\n", "author:", level_author);
+      else if (strPrefix(line, "levels:") && num_levels != -1)
+       fprintf(file_tmp, "%-32s%d\n", "levels:", num_levels);
+      else
+       fputs(line, file_tmp);
+    }
+
+    success = TRUE;
+  }
+
+  if (file)
+    fclose(file);
+
+  if (file_tmp)
+    fclose(file_tmp);
+
+  if (success)
+    success = (rename(filename_tmp, filename) == 0);
+
+  free(filename);
+  free(filename_tmp);
+
+  return success;
+}
+
+boolean CreateUserLevelSet(char *level_subdir, char *level_name,
+                          char *level_author, int num_levels)
 {
 {
+  LevelDirTree *level_info;
   char *filename;
   FILE *file;
   int i;
 
   char *filename;
   FILE *file;
   int i;
 
-  filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
+  // create user level sub-directory, if needed
+  createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
+
+  filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
 
   if (!(file = fopen(filename, MODE_WRITE)))
   {
     Error(ERR_WARN, "cannot write level info file '%s'", filename);
     free(filename);
 
   if (!(file = fopen(filename, MODE_WRITE)))
   {
     Error(ERR_WARN, "cannot write level info file '%s'", filename);
     free(filename);
-    return;
+
+    return FALSE;
   }
 
   }
 
+  level_info = newTreeInfo();
+
   /* always start with reliable default values */
   /* always start with reliable default values */
-  setTreeInfoToDefaults(&ldi, TREE_TYPE_LEVEL_DIR);
-
-  ldi.name = getStringCopy(getLoginName());
-  ldi.author = getStringCopy(getRealName());
-  ldi.levels = 100;
-  ldi.first_level = 1;
-  ldi.sort_priority = LEVELCLASS_USER_START;
-  ldi.readonly = FALSE;
-  ldi.graphics_set = getStringCopy(GRAPHICS_SUBDIR);
-  ldi.sounds_set = getStringCopy(SOUNDS_SUBDIR);
-  ldi.music_set = getStringCopy(MUSIC_SUBDIR);
-
-  fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
-                                                getCookie("LEVELINFO")));
-
-  for (i=0; i<NUM_LEVELINFO_TOKENS; i++)
-    if (i != LEVELINFO_TOKEN_IDENTIFIER &&
-       i != LEVELINFO_TOKEN_NAME_SORTING &&
-       i != LEVELINFO_TOKEN_IMPORTED_FROM)
+  setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
+
+  setString(&level_info->name, level_name);
+  setString(&level_info->author, level_author);
+  level_info->levels = num_levels;
+  level_info->first_level = 1;
+  level_info->sort_priority = LEVELCLASS_PRIVATE_START;
+  level_info->readonly = FALSE;
+
+  token_value_position = TOKEN_VALUE_POSITION_SHORT;
+
+  fprintFileHeader(file, LEVELINFO_FILENAME);
+
+  ldi = *level_info;
+  for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
+  {
+    if (i == LEVELINFO_TOKEN_NAME ||
+       i == LEVELINFO_TOKEN_AUTHOR ||
+       i == LEVELINFO_TOKEN_LEVELS ||
+       i == LEVELINFO_TOKEN_FIRST_LEVEL ||
+       i == LEVELINFO_TOKEN_SORT_PRIORITY ||
+       i == LEVELINFO_TOKEN_READONLY)
       fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
 
       fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
 
+    /* just to make things nicer :) */
+    if (i == LEVELINFO_TOKEN_AUTHOR ||
+       i == LEVELINFO_TOKEN_FIRST_LEVEL)
+      fprintf(file, "\n");     
+  }
+
+  token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
+
   fclose(file);
   fclose(file);
-  free(filename);
 
   SetFilePermissions(filename, PERMS_PRIVATE);
 
   SetFilePermissions(filename, PERMS_PRIVATE);
+
+  freeTreeInfo(level_info);
+  free(filename);
+
+  return TRUE;
+}
+
+static void SaveUserLevelInfo()
+{
+  CreateUserLevelSet(getLoginName(), getLoginName(), getRealName(), 100);
 }
 
 char *getSetupValue(int type, void *value)
 }
 
 char *getSetupValue(int type, void *value)
@@ -2216,10 +3711,24 @@ char *getSetupValue(int type, void *value)
       strcpy(value_string, (*(boolean *)value ? "on" : "off"));
       break;
 
       strcpy(value_string, (*(boolean *)value ? "on" : "off"));
       break;
 
+    case TYPE_SWITCH3:
+      strcpy(value_string, (*(int *)value == AUTO  ? "auto" :
+                           *(int *)value == FALSE ? "off" : "on"));
+      break;
+
     case TYPE_YES_NO:
       strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
       break;
 
     case TYPE_YES_NO:
       strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
       break;
 
+    case TYPE_YES_NO_AUTO:
+      strcpy(value_string, (*(int *)value == AUTO  ? "auto" :
+                           *(int *)value == FALSE ? "no" : "yes"));
+      break;
+
+    case TYPE_ECS_AGA:
+      strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
+      break;
+
     case TYPE_KEY:
       strcpy(value_string, getKeyNameFromKey(*(Key *)value));
       break;
     case TYPE_KEY:
       strcpy(value_string, getKeyNameFromKey(*(Key *)value));
       break;
@@ -2233,6 +3742,9 @@ char *getSetupValue(int type, void *value)
       break;
 
     case TYPE_STRING:
       break;
 
     case TYPE_STRING:
+      if (*(char **)value == NULL)
+       return NULL;
+
       strcpy(value_string, *(char **)value);
       break;
 
       strcpy(value_string, *(char **)value);
       break;
 
@@ -2241,6 +3753,9 @@ char *getSetupValue(int type, void *value)
       break;
   }
 
       break;
   }
 
+  if (type & TYPE_GHOSTED)
+    strcpy(value_string, "n/a");
+
   return value_string;
 }
 
   return value_string;
 }
 
@@ -2266,12 +3781,12 @@ char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
     char *keyname = getKeyNameFromKey(key);
 
     /* add comment, if useful */
     char *keyname = getKeyNameFromKey(key);
 
     /* add comment, if useful */
-    if (strcmp(keyname, "(undefined)") != 0 &&
-       strcmp(keyname, "(unknown)") != 0)
+    if (!strEqual(keyname, "(undefined)") &&
+       !strEqual(keyname, "(unknown)"))
     {
       /* add at least one whitespace */
       strcat(line, " ");
     {
       /* add at least one whitespace */
       strcat(line, " ");
-      for (i=strlen(line); i<TOKEN_COMMENT_POSITION; i++)
+      for (i = strlen(line); i < token_comment_position; i++)
        strcat(line, " ");
 
       strcat(line, "# ");
        strcat(line, " ");
 
       strcat(line, "# ");
@@ -2284,17 +3799,23 @@ char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
 
 void LoadLevelSetup_LastSeries()
 {
 
 void LoadLevelSetup_LastSeries()
 {
-  char *filename;
+  /* ----------------------------------------------------------------------- */
+  /* ~/.<program>/levelsetup.conf                                            */
+  /* ----------------------------------------------------------------------- */
+
+  char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
   SetupFileHash *level_setup_hash = NULL;
 
   /* always start with reliable default values */
   leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
 
   SetupFileHash *level_setup_hash = NULL;
 
   /* always start with reliable default values */
   leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
 
-  /* ----------------------------------------------------------------------- */
-  /* ~/.<program>/levelsetup.conf                                            */
-  /* ----------------------------------------------------------------------- */
-
-  filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
+  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)))
   {
 
   if ((level_setup_hash = loadSetupFileHash(filename)))
   {
@@ -2306,113 +3827,111 @@ void LoadLevelSetup_LastSeries()
     if (leveldir_current == NULL)
       leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
 
     if (leveldir_current == NULL)
       leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
 
-    checkSetupFileHashIdentifier(level_setup_hash, getCookie("LEVELSETUP"));
-
     freeSetupFileHash(level_setup_hash);
   }
   else
     freeSetupFileHash(level_setup_hash);
   }
   else
-    Error(ERR_WARN, "using default setup values");
+  {
+    Error(ERR_DEBUG, "using default setup values");
+  }
 
   free(filename);
 }
 
 
   free(filename);
 }
 
-void SaveLevelSetup_LastSeries()
+static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
 {
 {
-  char *filename;
-  char *level_subdir = leveldir_current->filename;
-  FILE *file;
-
   /* ----------------------------------------------------------------------- */
   /* ~/.<program>/levelsetup.conf                                            */
   /* ----------------------------------------------------------------------- */
 
   /* ----------------------------------------------------------------------- */
   /* ~/.<program>/levelsetup.conf                                            */
   /* ----------------------------------------------------------------------- */
 
-  InitUserDataDirectory();
+  // check if the current level directory structure is available at this point
+  if (leveldir_current == NULL)
+    return;
+
+  char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
+  char *level_subdir = leveldir_current->subdir;
+  FILE *file;
 
 
-  filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
+  InitUserDataDirectory();
 
   if (!(file = fopen(filename, MODE_WRITE)))
   {
     Error(ERR_WARN, "cannot write setup file '%s'", filename);
 
   if (!(file = fopen(filename, MODE_WRITE)))
   {
     Error(ERR_WARN, "cannot write setup file '%s'", filename);
+
     free(filename);
     free(filename);
+
     return;
   }
 
     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");
+
   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
                                               level_subdir));
 
   fclose(file);
   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
                                               level_subdir));
 
   fclose(file);
-  free(filename);
 
   SetFilePermissions(filename, PERMS_PRIVATE);
 
   SetFilePermissions(filename, PERMS_PRIVATE);
+
+  free(filename);
+}
+
+void SaveLevelSetup_LastSeries()
+{
+  SaveLevelSetup_LastSeries_Ext(FALSE);
+}
+
+void SaveLevelSetup_LastSeries_Deactivate()
+{
+  SaveLevelSetup_LastSeries_Ext(TRUE);
 }
 
 static void checkSeriesInfo()
 {
   static char *level_directory = NULL;
 }
 
 static void checkSeriesInfo()
 {
   static char *level_directory = NULL;
-  DIR *dir;
-  struct dirent *dir_entry;
+  Directory *dir;
 
   /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
 
 
   /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
 
-  level_directory = getPath2((leveldir_current->user_defined ?
+  level_directory = getPath2((leveldir_current->in_user_dir ?
                              getUserLevelDir(NULL) :
                              options.level_directory),
                             leveldir_current->fullpath);
 
                              getUserLevelDir(NULL) :
                              options.level_directory),
                             leveldir_current->fullpath);
 
-  if ((dir = opendir(level_directory)) == NULL)
+  if ((dir = openDirectory(level_directory)) == NULL)
   {
     Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
   {
     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;
-      }
-    }
+    return;
   }
 
   }
 
-  closedir(dir);
+  closeDirectory(dir);
 }
 
 void LoadLevelSetup_SeriesInfo()
 {
   char *filename;
   SetupFileHash *level_setup_hash = NULL;
 }
 
 void LoadLevelSetup_SeriesInfo()
 {
   char *filename;
   SetupFileHash *level_setup_hash = NULL;
-  char *level_subdir = leveldir_current->filename;
+  char *level_subdir = leveldir_current->subdir;
+  int i;
 
   /* always start with reliable default values */
   level_nr = leveldir_current->first_level;
 
 
   /* always start with reliable default values */
   level_nr = leveldir_current->first_level;
 
-  checkSeriesInfo(leveldir_current);
+  for (i = 0; i < MAX_LEVELS; i++)
+  {
+    LevelStats_setPlayed(i, 0);
+    LevelStats_setSolved(i, 0);
+  }
+
+  checkSeriesInfo();
 
   /* ----------------------------------------------------------------------- */
   /* ~/.<program>/levelsetup/<level series>/levelsetup.conf                  */
   /* ----------------------------------------------------------------------- */
 
 
   /* ----------------------------------------------------------------------- */
   /* ~/.<program>/levelsetup/<level series>/levelsetup.conf                  */
   /* ----------------------------------------------------------------------- */
 
-  level_subdir = leveldir_current->filename;
+  level_subdir = leveldir_current->subdir;
 
   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
 
 
   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
 
@@ -2420,6 +3939,8 @@ void LoadLevelSetup_SeriesInfo()
   {
     char *token_value;
 
   {
     char *token_value;
 
+    /* get last played level in this level set */
+
     token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
 
     if (token_value)
     token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
 
     if (token_value)
@@ -2432,6 +3953,8 @@ void LoadLevelSetup_SeriesInfo()
        level_nr = leveldir_current->last_level;
     }
 
        level_nr = leveldir_current->last_level;
     }
 
+    /* get handicap level in this level set */
+
     token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
 
     if (token_value)
     token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
 
     if (token_value)
@@ -2443,18 +3966,43 @@ void LoadLevelSetup_SeriesInfo()
       if (level_nr > leveldir_current->last_level + 1)
        level_nr = leveldir_current->last_level;
 
       if (level_nr > leveldir_current->last_level + 1)
        level_nr = leveldir_current->last_level;
 
-      if (leveldir_current->user_defined)
+      if (leveldir_current->user_defined || !leveldir_current->handicap)
        level_nr = leveldir_current->last_level;
 
       leveldir_current->handicap_level = level_nr;
     }
 
        level_nr = leveldir_current->last_level;
 
       leveldir_current->handicap_level = level_nr;
     }
 
-    checkSetupFileHashIdentifier(level_setup_hash, getCookie("LEVELSETUP"));
+    /* get number of played and solved levels in this level set */
+
+    BEGIN_HASH_ITERATION(level_setup_hash, itr)
+    {
+      char *token = HASH_ITERATION_TOKEN(itr);
+      char *value = HASH_ITERATION_VALUE(itr);
+
+      if (strlen(token) == 3 &&
+         token[0] >= '0' && token[0] <= '9' &&
+         token[1] >= '0' && token[1] <= '9' &&
+         token[2] >= '0' && token[2] <= '9')
+      {
+       int level_nr = atoi(token);
+
+       if (value != NULL)
+         LevelStats_setPlayed(level_nr, atoi(value));  /* read 1st column */
+
+       value = strchr(value, ' ');
+
+       if (value != NULL)
+         LevelStats_setSolved(level_nr, atoi(value));  /* read 2nd column */
+      }
+    }
+    END_HASH_ITERATION(hash, itr)
 
     freeSetupFileHash(level_setup_hash);
   }
   else
 
     freeSetupFileHash(level_setup_hash);
   }
   else
-    Error(ERR_WARN, "using default setup values");
+  {
+    Error(ERR_DEBUG, "using default setup values");
+  }
 
   free(filename);
 }
 
   free(filename);
 }
@@ -2462,10 +4010,11 @@ void LoadLevelSetup_SeriesInfo()
 void SaveLevelSetup_SeriesInfo()
 {
   char *filename;
 void SaveLevelSetup_SeriesInfo()
 {
   char *filename;
-  char *level_subdir = leveldir_current->filename;
+  char *level_subdir = leveldir_current->subdir;
   char *level_nr_str = int2str(level_nr, 0);
   char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
   FILE *file;
   char *level_nr_str = int2str(level_nr, 0);
   char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
   FILE *file;
+  int i;
 
   /* ----------------------------------------------------------------------- */
   /* ~/.<program>/levelsetup/<level series>/levelsetup.conf                  */
 
   /* ----------------------------------------------------------------------- */
   /* ~/.<program>/levelsetup/<level series>/levelsetup.conf                  */
@@ -2482,15 +4031,66 @@ void SaveLevelSetup_SeriesInfo()
     return;
   }
 
     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", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
                                               level_nr_str));
-  fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
-                                              handicap_level_str));
+  fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
+                                                handicap_level_str));
+
+  for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
+       i++)
+  {
+    if (LevelStats_getPlayed(i) > 0 ||
+       LevelStats_getSolved(i) > 0)
+    {
+      char token[16];
+      char value[16];
+
+      sprintf(token, "%03d", i);
+      sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
+
+      fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
+    }
+  }
 
   fclose(file);
 
   fclose(file);
-  free(filename);
 
   SetFilePermissions(filename, PERMS_PRIVATE);
 
   SetFilePermissions(filename, PERMS_PRIVATE);
+
+  free(filename);
+}
+
+int LevelStats_getPlayed(int nr)
+{
+  return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
+}
+
+int LevelStats_getSolved(int nr)
+{
+  return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
+}
+
+void LevelStats_setPlayed(int nr, int value)
+{
+  if (nr >= 0 && nr < MAX_LEVELS)
+    level_stats[nr].played = value;
+}
+
+void LevelStats_setSolved(int nr, int value)
+{
+  if (nr >= 0 && nr < MAX_LEVELS)
+    level_stats[nr].solved = value;
+}
+
+void LevelStats_incPlayed(int nr)
+{
+  if (nr >= 0 && nr < MAX_LEVELS)
+    level_stats[nr].played++;
+}
+
+void LevelStats_incSolved(int nr)
+{
+  if (nr >= 0 && nr < MAX_LEVELS)
+    level_stats[nr].solved++;
 }
 }