rnd-20070113-1-src
[rocksndiamonds.git] / src / libgame / setup.c
index c96316f6e56fb4d9e117bb0a74a0a4f1b3980d73..496b867b42440951bdc8144d4bcef043f1ef99df 100644 (file)
@@ -1,7 +1,7 @@
 /***********************************************************
 * Artsoft Retro-Game Library                               *
 *----------------------------------------------------------*
-* (c) 1994-2002 Artsoft Entertainment                      *
+* (c) 1994-2006 Artsoft Entertainment                      *
 *               Holger Schemel                             *
 *               Detmolder Strasse 189                      *
 *               33604 Bielefeld                            *
 #include <string.h>
 #include <unistd.h>
 
+#include "platform.h"
+
+#if !defined(PLATFORM_WIN32)
+#include <pwd.h>
+#include <sys/param.h>
+#endif
+
 #include "setup.h"
 #include "joystick.h"
 #include "text.h"
@@ -79,17 +86,26 @@ static char *levelclass_desc[NUM_LEVELCLASS_DESC] =
 
 #define MAX_COOKIE_LEN                         256
 
+
+static void setTreeInfoToDefaults(TreeInfo *, int);
+static TreeInfo *getTreeInfoCopy(TreeInfo *ti);
+static int compareTreeInfoEntries(const void *, const void *);
+
 static int token_value_position   = TOKEN_VALUE_POSITION_DEFAULT;
 static int token_comment_position = TOKEN_COMMENT_POSITION_DEFAULT;
 
+static SetupFileHash *artworkinfo_cache_old = NULL;
+static SetupFileHash *artworkinfo_cache_new = NULL;
+static boolean use_artworkinfo_cache = TRUE;
+
 
 /* ------------------------------------------------------------------------- */
 /* 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];
@@ -100,7 +116,7 @@ static char *getLevelClassDescription(TreeInfo *ldi)
 static char *getUserLevelDir(char *level_subdir)
 {
   static char *userlevel_dir = NULL;
-  char *data_dir = getUserDataDir();
+  char *data_dir = getUserGameDataDir();
   char *userlevel_subdir = LEVELS_DIRECTORY;
 
   checked_free(userlevel_dir);
@@ -132,7 +148,7 @@ static char *getScoreDir(char *level_subdir)
 static char *getLevelSetupDir(char *level_subdir)
 {
   static char *levelsetup_dir = NULL;
-  char *data_dir = getUserDataDir();
+  char *data_dir = getUserGameDataDir();
   char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
 
   checked_free(levelsetup_dir);
@@ -145,6 +161,16 @@ static char *getLevelSetupDir(char *level_subdir)
   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;
@@ -168,7 +194,7 @@ char *getCurrentLevelDir()
 static char *getTapeDir(char *level_subdir)
 {
   static char *tape_dir = NULL;
-  char *data_dir = getUserDataDir();
+  char *data_dir = getUserGameDataDir();
   char *tape_subdir = TAPES_DIRECTORY;
 
   checked_free(tape_dir);
@@ -258,7 +284,7 @@ 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;
 }
@@ -268,7 +294,7 @@ static char *getUserSoundsDir()
   static char *usersounds_dir = NULL;
 
   if (usersounds_dir == NULL)
-    usersounds_dir = getPath2(getUserDataDir(), SOUNDS_DIRECTORY);
+    usersounds_dir = getPath2(getUserGameDataDir(), SOUNDS_DIRECTORY);
 
   return usersounds_dir;
 }
@@ -278,7 +304,7 @@ static char *getUserMusicDir()
   static char *usermusic_dir = NULL;
 
   if (usermusic_dir == NULL)
-    usermusic_dir = getPath2(getUserDataDir(), MUSIC_DIRECTORY);
+    usermusic_dir = getPath2(getUserGameDataDir(), MUSIC_DIRECTORY);
 
   return usermusic_dir;
 }
@@ -472,6 +498,35 @@ char *getLevelSetInfoFilename()
   return NULL;
 }
 
+char *getLevelSetMessageFilename()
+{
+  static char *filename = NULL;
+  char *basenames[] =
+  {
+    "MESSAGE",
+    "MESSAGE.TXT",
+    "MESSAGE.txt",
+    "Message",
+    "Message.txt",
+    "message",
+    "message.txt",
+
+    NULL
+  };
+  int i;
+
+  for (i = 0; basenames[i] != NULL; i++)
+  {
+    checked_free(filename);
+    filename = getPath2(getCurrentLevelDir(), basenames[i]);
+
+    if (fileExists(filename))
+      return filename;
+  }
+
+  return NULL;
+}
+
 static char *getCorrectedArtworkBasename(char *basename)
 {
   char *basename_corrected = basename;
@@ -764,7 +819,7 @@ char *getCustomMusicDirectory(void)
 
 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);
 }
@@ -782,9 +837,9 @@ void InitUserLevelDirectory(char *level_subdir)
 {
   if (!fileExists(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(level_subdir), "user level",PERMS_PRIVATE);
+    createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
 
     SaveUserLevelInfo();
   }
@@ -792,9 +847,15 @@ void InitUserLevelDirectory(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(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);
 }
 
 
@@ -807,6 +868,15 @@ TreeInfo *newTreeInfo()
   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;
@@ -917,7 +987,7 @@ TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
     }
     else if (!node->parent_link)
     {
-      if (strcmp(identifier, node->identifier) == 0)
+      if (strEqual(identifier, node->identifier))
        return node;
     }
 
@@ -940,9 +1010,13 @@ TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
     return cloneTreeNode(node_top, node_parent, node->next,
                         skip_sets_without_levels);
 
+#if 1
+  node_new = getTreeInfoCopy(node);            /* copy complete node */
+#else
   node_new = newTreeInfo();
 
   *node_new = *node;                           /* copy complete node */
+#endif
 
   node_new->node_top = node_top;               /* correct top node link */
   node_new->node_parent = node_parent;         /* correct parent node link */
@@ -964,6 +1038,34 @@ void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean 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;
@@ -985,8 +1087,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;
@@ -1027,12 +1130,17 @@ void sortTreeInfo(TreeInfo **node_first,
   while (node)
   {
     if (node->node_group != NULL)
-      sortTreeInfo(&node->node_group, compare_function);
+      sortTreeInfoBySortFunction(&node->node_group, compare_function);
 
     node = node->next;
   }
 }
 
+void sortTreeInfo(TreeInfo **node_first)
+{
+  sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
+}
+
 
 /* ========================================================================= */
 /* some stuff from "files.c"                                                 */
@@ -1080,14 +1188,36 @@ void sortTreeInfo(TreeInfo **node_first,
 #define FILE_PERMS_PRIVATE     (MODE_R_ALL | MODE_W_PRIVATE)
 #define FILE_PERMS_PUBLIC      (MODE_R_ALL | MODE_W_PUBLIC)
 
-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)
@@ -1100,8 +1230,8 @@ char *getCommonDataDir(void)
     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;
   }
@@ -1113,9 +1243,59 @@ char *getCommonDataDir(void)
   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 (user_game_data_dir == NULL)
+    user_game_data_dir = getPath2(getPersonalDataDir(),
+                                 program.userdata_subdir);
+
+  return user_game_data_dir;
+}
+
+void updateUserGameDataDir()
+{
+#if defined(PLATFORM_MACOSX)
+  char *userdata_dir_old = getPath2(getHomeDir(), program.userdata_subdir_unix);
+  char *userdata_dir_new = getUserGameDataDir();       /* do not free() this */
+
+  /* convert old Unix style game data directory to Mac OS X style, if needed */
+  if (fileExists(userdata_dir_old) && !fileExists(userdata_dir_new))
+  {
+    if (rename(userdata_dir_old, userdata_dir_new) != 0)
+    {
+      Error(ERR_WARN, "cannot move game data directory '%s' to '%s'",
+           userdata_dir_old, userdata_dir_new);
+
+      /* continue using Unix style data directory -- this should not happen */
+      program.userdata_path = getPath2(getPersonalDataDir(),
+                                      program.userdata_subdir_unix);
+    }
+  }
+
+  free(userdata_dir_old);
+#endif
+}
+
 char *getSetupDir()
 {
-  return getUserDataDir();
+  return getUserGameDataDir();
 }
 
 static mode_t posix_umask(mode_t mask)
@@ -1155,7 +1335,7 @@ void createDirectory(char *dir, char *text, int permission_class)
 
 void InitUserDataDirectory()
 {
-  createDirectory(getUserDataDir(), "user data", PERMS_PRIVATE);
+  createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
 }
 
 void SetFilePermissions(char *filename, int permission_class)
@@ -1281,7 +1461,7 @@ 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);
@@ -1292,7 +1472,7 @@ SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
   if (list == NULL)
     return NULL;
 
-  if (strcmp(list->token, token) == 0)
+  if (strEqual(list->token, token))
   {
     checked_free(list->value);
 
@@ -1374,7 +1554,7 @@ static unsigned int get_hash_from_key(void *key)
 
 static int keys_are_equal(void *key1, void *key2)
 {
-  return (strcmp((char *)key1, (char *)key2) == 0);
+  return (strEqual((char *)key1, (char *)key2));
 }
 
 SetupFileHash *newSetupFileHash()
@@ -1527,7 +1707,11 @@ static void *loadSetupFileData(char *filename, boolean use_hash)
     /* find end of token to determine start of value */
     for (line_ptr = token; *line_ptr; line_ptr++)
     {
+#if 1
+      if (*line_ptr == ':' || *line_ptr == '=')
+#else
       if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
+#endif
       {
        *line_ptr = '\0';               /* terminate token string */
        value = line_ptr + 1;           /* set beginning of value */
@@ -1536,6 +1720,11 @@ static void *loadSetupFileData(char *filename, boolean use_hash)
       }
     }
 
+    /* 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')
@@ -1579,6 +1768,27 @@ static void *loadSetupFileData(char *filename, boolean use_hash)
   return setup_file_data;
 }
 
+void saveSetupFileHash(SetupFileHash *hash, char *filename)
+{
+  FILE *file;
+
+  if (!(file = fopen(filename, MODE_WRITE)))
+  {
+    Error(ERR_WARN, "cannot write configuration file '%s'", filename);
+
+    return;
+  }
+
+  BEGIN_HASH_ITERATION(hash, itr)
+  {
+    fprintf(file, "%s\n", getFormattedSetupEntry(HASH_ITERATION_TOKEN(itr),
+                                                HASH_ITERATION_VALUE(itr)));
+  }
+  END_HASH_ITERATION(hash, itr)
+
+  fclose(file);
+}
+
 SetupFileList *loadSetupFileList(char *filename)
 {
   return (SetupFileList *)loadSetupFileData(filename, FALSE);
@@ -1605,212 +1815,313 @@ void checkSetupFileHashIdentifier(SetupFileHash *setup_file_hash,
 /* 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 */
-#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_IMPORTED_BY    5
-#define LEVELINFO_TOKEN_LEVELS         6
-#define LEVELINFO_TOKEN_FIRST_LEVEL    7
-#define LEVELINFO_TOKEN_SORT_PRIORITY  8
-#define LEVELINFO_TOKEN_LATEST_ENGINE  9
-#define LEVELINFO_TOKEN_LEVEL_GROUP    10
-#define LEVELINFO_TOKEN_READONLY       11
-#define LEVELINFO_TOKEN_GRAPHICS_SET   12
-#define LEVELINFO_TOKEN_SOUNDS_SET     13
-#define LEVELINFO_TOKEN_MUSIC_SET      14
-#define LEVELINFO_TOKEN_FILENAME       15
-#define LEVELINFO_TOKEN_FILETYPE       16
-#define LEVELINFO_TOKEN_HANDICAP       17
-#define LEVELINFO_TOKEN_SKIP_LEVELS    18
-
-#define NUM_LEVELINFO_TOKENS           19
+#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_IMPORTED_BY            5
+#define LEVELINFO_TOKEN_LEVELS                 6
+#define LEVELINFO_TOKEN_FIRST_LEVEL            7
+#define LEVELINFO_TOKEN_SORT_PRIORITY          8
+#define LEVELINFO_TOKEN_LATEST_ENGINE          9
+#define LEVELINFO_TOKEN_LEVEL_GROUP            10
+#define LEVELINFO_TOKEN_READONLY               11
+#define LEVELINFO_TOKEN_GRAPHICS_SET_ECS       12
+#define LEVELINFO_TOKEN_GRAPHICS_SET_AGA       13
+#define LEVELINFO_TOKEN_GRAPHICS_SET           14
+#define LEVELINFO_TOKEN_SOUNDS_SET             15
+#define LEVELINFO_TOKEN_MUSIC_SET              16
+#define LEVELINFO_TOKEN_FILENAME               17
+#define LEVELINFO_TOKEN_FILETYPE               18
+#define LEVELINFO_TOKEN_HANDICAP               19
+#define LEVELINFO_TOKEN_SKIP_LEVELS            20
+
+#define NUM_LEVELINFO_TOKENS                   21
 
 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_STRING,       &ldi.imported_by,       "imported_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,      "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_BOOLEAN,      &ldi.handicap,          "handicap"      },
-  { TYPE_BOOLEAN,      &ldi.skip_levels,       "skip_levels"   }
+  { 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_STRING,       &ldi.imported_by,       "imported_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_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)
 {
-  ldi->type = 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);
 
-  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_parent = NULL;
+  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(ANONYMOUS_NAME);
 
-  ldi->subdir = 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 = 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;
 
-  ldi->sort_priority = LEVELCLASS_UNDEFINED;   /* default: least priority */
-  ldi->latest_engine = FALSE;                  /* default: get from level */
-  ldi->parent_link = FALSE;
-  ldi->in_user_dir = FALSE;
-  ldi->user_defined = FALSE;
-  ldi->color = 0;
-  ldi->class_desc = NULL;
+  ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
 
-  if (ldi->type == TREE_TYPE_LEVEL_DIR)
+  if (ti->type == TREE_TYPE_LEVEL_DIR)
   {
-    ldi->imported_from = NULL;
-    ldi->imported_by = NULL;
+    ti->imported_from = NULL;
+    ti->imported_by = 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);
+    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);
 
-    ldi->level_filename = NULL;
-    ldi->level_filetype = NULL;
+    ti->level_filename = NULL;
+    ti->level_filetype = NULL;
 
-    ldi->levels = 0;
-    ldi->first_level = 0;
-    ldi->last_level = 0;
-    ldi->level_group = FALSE;
-    ldi->handicap_level = 0;
-    ldi->readonly = TRUE;
-    ldi->handicap = TRUE;
-    ldi->skip_levels = FALSE;
+    ti->levels = 0;
+    ti->first_level = 0;
+    ti->last_level = 0;
+    ti->level_group = FALSE;
+    ti->handicap_level = 0;
+    ti->readonly = TRUE;
+    ti->handicap = TRUE;
+    ti->skip_levels = FALSE;
   }
 }
 
-static void setTreeInfoToDefaultsFromParent(TreeInfo *ldi, TreeInfo *parent)
+static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
 {
   if (parent == NULL)
   {
     Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
 
-    setTreeInfoToDefaults(ldi, TREE_TYPE_UNDEFINED);
+    setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
 
     return;
   }
 
   /* copy all values from the parent structure */
 
-  ldi->type = parent->type;
+  ti->type = parent->type;
 
-  ldi->node_top = parent->node_top;
-  ldi->node_parent = parent;
-  ldi->node_group = NULL;
-  ldi->next = NULL;
+  ti->node_top = parent->node_top;
+  ti->node_parent = parent;
+  ti->node_group = NULL;
+  ti->next = NULL;
+
+  ti->cl_first = -1;
+  ti->cl_cursor = -1;
+
+  ti->subdir = NULL;
+  ti->fullpath = NULL;
+  ti->basepath = NULL;
+  ti->identifier = NULL;
+  ti->name = getStringCopy(ANONYMOUS_NAME);
+  ti->name_sorting = NULL;
+  ti->author = getStringCopy(parent->author);
 
-  ldi->cl_first = -1;
-  ldi->cl_cursor = -1;
+  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->subdir = NULL;
-  ldi->fullpath = NULL;
-  ldi->basepath = NULL;
-  ldi->identifier = NULL;
-  ldi->name = getStringCopy(ANONYMOUS_NAME);
-  ldi->name_sorting = NULL;
-  ldi->author = getStringCopy(parent->author);
+  ti->infotext = getStringCopy(parent->infotext);
 
-  ldi->sort_priority = parent->sort_priority;
-  ldi->latest_engine = parent->latest_engine;
-  ldi->parent_link = FALSE;
-  ldi->in_user_dir = parent->in_user_dir;
-  ldi->user_defined = parent->user_defined;
-  ldi->color = parent->color;
-  ldi->class_desc = getStringCopy(parent->class_desc);
-
-  if (ldi->type == TREE_TYPE_LEVEL_DIR)
+  if (ti->type == TREE_TYPE_LEVEL_DIR)
   {
-    ldi->imported_from = getStringCopy(parent->imported_from);
-    ldi->imported_by = getStringCopy(parent->imported_by);
+    ti->imported_from = getStringCopy(parent->imported_from);
+    ti->imported_by = getStringCopy(parent->imported_by);
 
-    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);
+    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);
 
-    ldi->level_filename = NULL;
-    ldi->level_filetype = NULL;
+    ti->level_filename = NULL;
+    ti->level_filetype = NULL;
 
-    ldi->levels = 0;
-    ldi->first_level = 0;
-    ldi->last_level = 0;
-    ldi->level_group = FALSE;
-    ldi->handicap_level = 0;
-    ldi->readonly = TRUE;
-    ldi->handicap = TRUE;
-    ldi->skip_levels = FALSE;
+    ti->levels = 0;
+    ti->first_level = 0;
+    ti->last_level = 0;
+    ti->level_group = FALSE;
+    ti->handicap_level = 0;
+    ti->readonly = TRUE;
+    ti->handicap = TRUE;
+    ti->skip_levels = FALSE;
   }
 }
 
-static void freeTreeInfo(TreeInfo *ldi)
+static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
 {
-  checked_free(ldi->subdir);
-  checked_free(ldi->fullpath);
-  checked_free(ldi->basepath);
-  checked_free(ldi->identifier);
+  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->imported_from       = getStringCopy(ti->imported_from);
+  ti_copy->imported_by         = getStringCopy(ti->imported_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->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;
+}
+
+static void freeTreeInfo(TreeInfo *ti)
+{
+  if (ti == NULL)
+    return;
+
+  checked_free(ti->subdir);
+  checked_free(ti->fullpath);
+  checked_free(ti->basepath);
+  checked_free(ti->identifier);
 
-  checked_free(ldi->name);
-  checked_free(ldi->name_sorting);
-  checked_free(ldi->author);
+  checked_free(ti->name);
+  checked_free(ti->name_sorting);
+  checked_free(ti->author);
 
-  checked_free(ldi->class_desc);
+  checked_free(ti->class_desc);
 
-  if (ldi->type == TREE_TYPE_LEVEL_DIR)
+  checked_free(ti->infotext);
+
+  if (ti->type == TREE_TYPE_LEVEL_DIR)
   {
-    checked_free(ldi->imported_from);
-    checked_free(ldi->imported_by);
+    checked_free(ti->imported_from);
+    checked_free(ti->imported_by);
 
-    checked_free(ldi->graphics_set);
-    checked_free(ldi->sounds_set);
-    checked_free(ldi->music_set);
+    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(ldi->graphics_path);
-    checked_free(ldi->sounds_path);
-    checked_free(ldi->music_path);
+    checked_free(ti->graphics_path);
+    checked_free(ti->sounds_path);
+    checked_free(ti->music_path);
 
-    checked_free(ldi->level_filename);
-    checked_free(ldi->level_filetype);
+    checked_free(ti->level_filename);
+    checked_free(ti->level_filetype);
   }
+
+  checked_free(ti);
 }
 
 void setSetupInfo(struct TokenInfo *token_info,
@@ -1918,6 +2229,221 @@ static void createParentTreeInfoNode(TreeInfo *node_parent)
   pushTreeInfo(&node_parent->node_group, 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 *getFileTimestamp(char *filename)
+{
+  struct stat file_status;
+
+  if (stat(filename, &file_status) != 0)       /* cannot stat file */
+    return getStringCopy(i_to_a(0));
+
+  return getStringCopy(i_to_a(file_status.st_mtime));
+}
+
+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)
+      {
+#if 1
+       Error(ERR_WARN, "cache entry '%s' invalid", token);
+#endif
+
+       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;
+
+#if 0
+    if (!cached)
+      printf("::: '%s': INVALIDATED FROM CACHE BY TIMESTAMP\n", identifier);
+#endif
+
+    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 = getFileTimestamp(filename_levelinfo);
+    char *timestamp_artworkinfo = getFileTimestamp(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 *);
 
@@ -1926,6 +2452,8 @@ static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
                                          char *level_directory,
                                          char *directory_name)
 {
+  static unsigned long progress_delay = 0;
+  unsigned long progress_delay_value = 100;    /* (in milliseconds) */
   char *directory_path = getPath2(level_directory, directory_name);
   char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
   SetupFileHash *setup_file_hash;
@@ -1972,11 +2500,9 @@ static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
                 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
   *leveldir_new = ldi;
 
-  if (strcmp(leveldir_new->name, ANONYMOUS_NAME) == 0)
+  if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
     setString(&leveldir_new->name, leveldir_new->subdir);
 
-  DrawInitText(leveldir_new->name, 150, FC_YELLOW);
-
   if (leveldir_new->identifier == NULL)
     leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
 
@@ -2003,14 +2529,14 @@ static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
     leveldir_new->first_level + leveldir_new->levels - 1;
 
   leveldir_new->in_user_dir =
-    (strcmp(leveldir_new->basepath, options.level_directory) != 0);
+    (!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 &&
-      (strcmp(leveldir_new->subdir, getLoginName()) == 0 ||
-       strcmp(leveldir_new->name,   getLoginName()) == 0 ||
-       strcmp(leveldir_new->author, getRealName())  == 0))
+      (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;
@@ -2027,6 +2553,14 @@ static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
     (leveldir_new->user_defined || !leveldir_new->handicap ?
      leveldir_new->last_level : leveldir_new->first_level);
 
+#if 1
+  if (leveldir_new->level_group ||
+      DelayReached(&progress_delay, progress_delay_value))
+    DrawInitText(leveldir_new->name, 150, FC_YELLOW);
+#else
+  DrawInitText(leveldir_new->name, 150, FC_YELLOW);
+#endif
+
 #if 0
   /* !!! don't skip sets without levels (else artwork base sets are missing) */
 #if 1
@@ -2052,7 +2586,7 @@ static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
     /* 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);
   }
@@ -2084,8 +2618,8 @@ static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
     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);
       continue;
@@ -2101,9 +2635,9 @@ static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
 
     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,
@@ -2113,7 +2647,8 @@ static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
 
   closedir(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,
@@ -2125,23 +2660,33 @@ static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
          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());
 
-  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));
 
-#if 1
   /* 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);
-#endif
+
+  AdjustGraphicsForEMC();
 
   /* before sorting, the first entries will be from the user directory */
   leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
@@ -2149,7 +2694,7 @@ void LoadLevelInfo()
   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
   dumpTreeInfo(leveldir_first, 0);
@@ -2194,7 +2739,7 @@ static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
 
     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);
@@ -2226,13 +2771,9 @@ static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
                   getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
     *artwork_new = ldi;
 
-    if (strcmp(artwork_new->name, ANONYMOUS_NAME) == 0)
+    if (strEqual(artwork_new->name, ANONYMOUS_NAME))
       setString(&artwork_new->name, artwork_new->subdir);
 
-#if 0
-    DrawInitText(artwork_new->name, 150, FC_YELLOW);
-#endif
-
     if (artwork_new->identifier == NULL)
       artwork_new->identifier = getStringCopy(artwork_new->subdir);
 
@@ -2252,7 +2793,7 @@ static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
   }
 
   artwork_new->in_user_dir =
-    (strcmp(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)) != 0);
+    (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
 
   /* (may use ".sort_priority" from "setup_file_hash" above) */
   artwork_new->color = ARTWORKCOLOR(artwork_new);
@@ -2261,7 +2802,7 @@ static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
 
   if (setup_file_hash == NULL) /* (after determining ".user_defined") */
   {
-    if (strcmp(artwork_new->subdir, ".") == 0)
+    if (strEqual(artwork_new->subdir, "."))
     {
       if (artwork_new->user_defined)
       {
@@ -2289,7 +2830,9 @@ static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
     setString(&artwork_new->name_sorting, artwork_new->name);
   }
 
+#if 0
   DrawInitText(artwork_new->name, 150, FC_YELLOW);
+#endif
 
   pushTreeInfo(node_first, artwork_new);
 
@@ -2311,8 +2854,10 @@ static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
 
   if ((dir = opendir(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);
+
     return;
   }
 
@@ -2322,15 +2867,15 @@ static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
     char *directory_name = dir_entry->d_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);
       continue;
     }
 
-    /* find out if directory entry is itself a directory */
+    /* skip directory entries which are not a directory or are not accessible */
     if (stat(directory_path, &file_status) != 0 ||     /* cannot stat file */
        (file_status.st_mode & S_IFMT) != S_IFDIR)      /* not a directory */
     {
@@ -2341,7 +2886,7 @@ static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
     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);
   }
@@ -2349,7 +2894,7 @@ static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
   closedir(dir);
 
   /* 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)
@@ -2377,7 +2922,9 @@ static TreeInfo *getDummyArtworkInfo(int type)
 
 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,
@@ -2442,9 +2989,9 @@ void LoadArtworkInfo()
   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
   dumpTreeInfo(artwork.gfx_first, 0);
@@ -2456,36 +3003,57 @@ void LoadArtworkInfo()
 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
                                  LevelDirTree *level_node)
 {
+  static unsigned long progress_delay = 0;
+  unsigned long progress_delay_value = 100;    /* (in milliseconds) */
+  int type = (*artwork_node)->type;
+
   /* recursively check all level directories for artwork sub-directories */
 
   while (level_node)
   {
-    char *path = getPath2(getLevelDirFromTreeInfo(level_node),
-                         ARTWORK_DIRECTORY((*artwork_node)->type));
-
+    /* check all tree entries for artwork, but skip parent link entries */
     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)
       {
-       free((*artwork_node)->identifier);
-       free((*artwork_node)->name);
-       free((*artwork_node)->name_sorting);
+       pushTreeInfo(artwork_node, artwork_new);
+      }
+      else
+      {
+       TreeInfo *topnode_last = *artwork_node;
+       char *path = getPath2(getLevelDirFromTreeInfo(level_node),
+                             ARTWORK_DIRECTORY(type));
+
+       LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
 
-       (*artwork_node)->identifier   = getStringCopy(level_node->subdir);
-       (*artwork_node)->name         = getStringCopy(level_node->name);
-       (*artwork_node)->name_sorting = getStringCopy(level_node->name);
+       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_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);
+#if 1
+    if (level_node->level_group ||
+       DelayReached(&progress_delay, progress_delay_value))
+      DrawInitText(level_node->name, 150, FC_YELLOW);
+#endif
 
     if (level_node->node_group != NULL)
       LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
@@ -2496,15 +3064,17 @@ void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
 
 void LoadLevelArtworkInfo()
 {
-  DrawInitText("Looking for custom level artwork:", 120, FC_GREEN);
+  DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
 
   LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
   LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
   LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
 
+  SaveArtworkInfoCache();
+
   /* 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);
@@ -2515,7 +3085,7 @@ void LoadLevelArtworkInfo()
       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);
@@ -2526,7 +3096,7 @@ void LoadLevelArtworkInfo()
       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);
@@ -2537,9 +3107,9 @@ void LoadLevelArtworkInfo()
       artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
   }
 
-  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
   dumpTreeInfo(artwork.gfx_first, 0);
@@ -2624,6 +3194,10 @@ char *getSetupValue(int type, void *value)
       strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
       break;
 
+    case TYPE_ECS_AGA:
+      strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
+      break;
+
     case TYPE_KEY:
       strcpy(value_string, getKeyNameFromKey(*(Key *)value));
       break;
@@ -2637,6 +3211,9 @@ char *getSetupValue(int type, void *value)
       break;
 
     case TYPE_STRING:
+      if (*(char **)value == NULL)
+       return NULL;
+
       strcpy(value_string, *(char **)value);
       break;
 
@@ -2645,6 +3222,9 @@ char *getSetupValue(int type, void *value)
       break;
   }
 
+  if (type & TYPE_GHOSTED)
+    strcpy(value_string, "n/a");
+
   return value_string;
 }
 
@@ -2670,8 +3250,8 @@ char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
     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, " ");
@@ -2773,7 +3353,7 @@ static void checkSeriesInfo()
   {
     if (strlen(dir_entry->d_name) > 4 &&
        dir_entry->d_name[3] == '.' &&
-       strcmp(&dir_entry->d_name[4], LEVELFILE_EXTENSION) == 0)
+       strEqual(&dir_entry->d_name[4], LEVELFILE_EXTENSION))
     {
       char levelnum_str[4];
       int levelnum_value;