rnd-20061030-3-src
[rocksndiamonds.git] / src / libgame / setup.c
index a004bc82fda34659823eb1b50811b43d578098a4..6d187a02ef97519c5125c1072ee20f5d39326dca 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,22 @@ static char *levelclass_desc[NUM_LEVELCLASS_DESC] =
 
 #define MAX_COOKIE_LEN                         256
 
+static void setTreeInfoToDefaults(TreeInfo *, int);
+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_hash = NULL;
+
 
 /* ------------------------------------------------------------------------- */
 /* 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 +112,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 +144,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);
@@ -168,7 +180,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 +270,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 +280,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 +290,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;
 }
@@ -764,7 +776,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,7 +794,7 @@ 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);
 
@@ -792,7 +804,7 @@ 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);
 }
@@ -807,6 +819,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;
@@ -1013,8 +1034,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;
@@ -1055,12 +1077,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"                                                 */
@@ -1108,14 +1135,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 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 (userdata_dir == NULL)
-    userdata_dir = getPath2(getHomeDir(), program.userdata_directory);
+      if ((pwd = getpwuid(getuid())) != NULL)
+       dir = getStringCopy(pwd->pw_dir);
+      else
+       dir = ".";
+    }
+  }
+#else
+  dir = ".";
+#endif
 
-  return userdata_dir;
+  return dir;
 }
 
 char *getCommonDataDir(void)
@@ -1129,7 +1178,7 @@ char *getCommonDataDir(void)
 
     if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
        && !strEqual(dir, ""))          /* empty for Windows 95/98 */
-      common_data_dir = getPath2(dir, program.userdata_directory);
+      common_data_dir = getPath2(dir, program.userdata_subdir);
     else
       common_data_dir = options.rw_base_directory;
   }
@@ -1141,9 +1190,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)
@@ -1183,7 +1282,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)
@@ -1607,6 +1706,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);
@@ -1690,164 +1810,188 @@ static struct TokenInfo levelinfo_tokens[] =
   { 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;
 
-  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 = (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_parent = NULL;
-  ldi->node_group = NULL;
-  ldi->next = NULL;
+  ti->node_parent = NULL;
+  ti->node_group = NULL;
+  ti->next = NULL;
 
-  ldi->cl_first = -1;
-  ldi->cl_cursor = -1;
+  ti->cl_first = -1;
+  ti->cl_cursor = -1;
 
-  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->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->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->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;
 
-  if (ldi->type == TREE_TYPE_LEVEL_DIR)
+  ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
+
+  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_ecs = NULL;
-    ldi->graphics_set_aga = 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;
+
+  ti->node_top = parent->node_top;
+  ti->node_parent = parent;
+  ti->node_group = NULL;
+  ti->next = NULL;
 
-  ldi->node_top = parent->node_top;
-  ldi->node_parent = parent;
-  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);
 
-  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->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 = 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);
+  ti->infotext = getStringCopy(parent->infotext);
 
-  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_ecs = NULL;
-    ldi->graphics_set_aga = 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 freeTreeInfo(TreeInfo *ldi)
+static void freeTreeInfo(TreeInfo *ti)
 {
-  checked_free(ldi->subdir);
-  checked_free(ldi->fullpath);
-  checked_free(ldi->basepath);
-  checked_free(ldi->identifier);
+  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_ecs);
-    checked_free(ldi->graphics_set_aga);
-    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);
   }
 }
 
@@ -1956,6 +2100,94 @@ static void createParentTreeInfoNode(TreeInfo *node_parent)
   pushTreeInfo(&node_parent->node_group, ti_new);
 }
 
+
+/* -------------------------------------------------------------------------- */
+/* functions for handling custom artwork info cache                           */
+/* -------------------------------------------------------------------------- */
+
+#define ARTWORKINFO_CACHE_FILENAME     "cache.conf"
+
+static void LoadArtworkInfoCache()
+{
+  if (artworkinfo_hash == NULL)
+  {
+    char *filename = getPath2(getSetupDir(), ARTWORKINFO_CACHE_FILENAME);
+
+    /* try to load artwork info hash from already existing cache file */
+    artworkinfo_hash = loadSetupFileHash(filename);
+
+    /* if no artwork info cache file was found, start with empty hash */
+    if (artworkinfo_hash == NULL)
+      artworkinfo_hash = newSetupFileHash();
+
+    free(filename);
+  }
+}
+
+static void SaveArtworkInfoCache()
+{
+  char *filename = getPath2(getSetupDir(), ARTWORKINFO_CACHE_FILENAME);
+
+  saveSetupFileHash(artworkinfo_hash, filename);
+
+  free(filename);
+}
+
+static TreeInfo *getArtworkInfoFromCache(char *identifier, int type)
+{
+  char *type_string = ARTWORK_DIRECTORY(type);
+  char *token_prefix = getStringCat2WithSeparator(type_string, identifier, ".");
+  char *cache_entry = getHashEntry(artworkinfo_hash, token_prefix);
+  boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
+  TreeInfo *artwork_new = NULL;
+
+  if (cached)
+  {
+    int i;
+
+    printf("::: LOADING existing hash entry for '%s' ...\n", identifier);
+
+    artwork_new = newTreeInfo();
+    setTreeInfoToDefaults(artwork_new, type);
+
+    /* set all structure fields according to the token/value pairs */
+    ldi = *artwork_new;
+    for (i = 0; artworkinfo_tokens[i].type != -1; i++)
+    {
+      char *token = getStringCat2WithSeparator(token_prefix,
+                                              artworkinfo_tokens[i].text, ".");
+      char *value = getHashEntry(artworkinfo_hash, token);
+
+      printf("::: - setting '%s' => '%s'\n", token, value);
+
+      setSetupInfo(artworkinfo_tokens, i, value);
+
+      /* check if cache entry for this item is invalid or incomplete */
+      if (value == NULL)
+      {
+       printf("::: - WARNING: cache entry '%s' invalid\n", token);
+
+       cached = FALSE;
+      }
+
+      checked_free(token);
+    }
+    *artwork_new = ldi;
+
+    if (!cached)
+      freeTreeInfo(artwork_new);
+  }
+
+  free(token_prefix);
+
+  return artwork_new;
+}
+
+
+/* -------------------------------------------------------------------------- */
+/* functions for loading level info and custom artwork info                   */
+/* -------------------------------------------------------------------------- */
+
 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
 
@@ -2183,14 +2415,12 @@ void LoadLevelInfo()
   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();
 
@@ -2200,7 +2430,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);
@@ -2430,6 +2660,8 @@ static TreeInfo *getDummyArtworkInfo(int type)
 
 void LoadArtworkInfo()
 {
+  LoadArtworkInfoCache();
+
   DrawInitText("Looking for custom artwork:", 120, FC_GREEN);
 
   LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
@@ -2495,9 +2727,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);
@@ -2519,11 +2751,26 @@ void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
       TreeInfo *topnode_last = *artwork_node;
       char *path = getPath2(getLevelDirFromTreeInfo(level_node),
                            ARTWORK_DIRECTORY((*artwork_node)->type));
+      TreeInfo *artwork_new = getArtworkInfoFromCache(level_node->subdir,
+                                                     (*artwork_node)->type);
+      boolean cached = FALSE;
 
-      LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path,
-                                   (*artwork_node)->type);
+      if (artwork_new != NULL)
+      {
+       pushTreeInfo(artwork_node, artwork_new);
+       cached = TRUE;
+      }
+      else
+      {
+       LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path,
+                                     (*artwork_node)->type);
+      }
 
+#if 1
+      if (!cached && topnode_last != *artwork_node)
+#else
       if (topnode_last != *artwork_node)
+#endif
       {
        free((*artwork_node)->identifier);
        free((*artwork_node)->name);
@@ -2535,6 +2782,41 @@ void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
 
        (*artwork_node)->sort_priority = level_node->sort_priority;
        (*artwork_node)->color = LEVELCOLOR((*artwork_node));
+
+#if 1
+       {
+         char *identifier = level_node->subdir;
+         char *type_string = ARTWORK_DIRECTORY((*artwork_node)->type);
+         char *type_identifier =
+           getStringCat2WithSeparator(type_string, identifier, ".");
+         int i;
+
+         printf("::: adding hash entry for set '%s' ...\n", type_identifier);
+
+         setHashEntry(artworkinfo_hash, type_identifier, "true");
+
+         ldi = **artwork_node;
+         for (i = 0; artworkinfo_tokens[i].type != -1; i++)
+         {
+           char *token = getStringCat2WithSeparator(type_identifier,
+                                                    artworkinfo_tokens[i].text,
+                                                    ".");
+           char *value = getSetupValue(artworkinfo_tokens[i].type,
+                                       artworkinfo_tokens[i].value);
+           if (value != NULL)
+           {
+             setHashEntry(artworkinfo_hash, token, value);
+
+             printf("::: - setting '%s' => '%s'\n\n",
+                    token, value);
+           }
+
+           checked_free(token);
+         }
+
+         free(type_identifier);
+       }
+#endif
       }
 
       free(path);
@@ -2555,6 +2837,8 @@ void LoadLevelArtworkInfo()
   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 (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
@@ -2590,9 +2874,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);
@@ -2694,6 +2978,9 @@ char *getSetupValue(int type, void *value)
       break;
 
     case TYPE_STRING:
+      if (*(char **)value == NULL)
+       return NULL;
+
       strcpy(value_string, *(char **)value);
       break;