added abstractions for using getpwuid()
[rocksndiamonds.git] / src / libgame / setup.c
index 2eca885ac16b27fb5ced808c25871f15239dd250..f1b87e170499063fbc93b0b01d6afd404e47a27c 100644 (file)
@@ -4,7 +4,7 @@
 // (c) 1995-2014 by Artsoft Entertainment
 //                         Holger Schemel
 //                 info@artsoft.org
-//                 http://www.artsoft.org/
+//                 https://www.artsoft.org/
 // ----------------------------------------------------------------------------
 // setup.c
 // ============================================================================
 
 #include "platform.h"
 
-#if !defined(PLATFORM_WIN32)
-#include <pwd.h>
-#include <sys/param.h>
-#endif
-
 #include "setup.h"
 #include "joystick.h"
 #include "text.h"
@@ -99,6 +94,7 @@ static int token_comment_position = TOKEN_COMMENT_POSITION_DEFAULT;
 
 static SetupFileHash *artworkinfo_cache_old = NULL;
 static SetupFileHash *artworkinfo_cache_new = NULL;
+static SetupFileHash *optional_tokens_hash = NULL;
 static boolean use_artworkinfo_cache = TRUE;
 
 
@@ -178,7 +174,7 @@ static char *getNetworkDir(void)
   return network_dir;
 }
 
-static char *getLevelDirFromTreeInfo(TreeInfo *node)
+char *getLevelDirFromTreeInfo(TreeInfo *node)
 {
   static char *level_dir = NULL;
 
@@ -457,7 +453,7 @@ char *getProgramMainDataPath(char *command_filename, char *base_path)
 {
   // check if the program's main data base directory is configured
   if (!strEqual(base_path, "."))
-    return base_path;
+    return getStringCopy(base_path);
 
   /* if the program is configured to start from current directory (default),
      determine program package directory from program binary (some versions
@@ -494,22 +490,41 @@ char *getProgramMainDataPath(char *command_filename, char *base_path)
 
 char *getProgramConfigFilename(char *command_filename)
 {
-  char *command_filename_1 = getStringCopy(command_filename);
+  static char *config_filename_1 = NULL;
+  static char *config_filename_2 = NULL;
+  static char *config_filename_3 = NULL;
+  static boolean initialized = FALSE;
+
+  if (!initialized)
+  {
+    char *command_filename_1 = getStringCopy(command_filename);
+
+    // strip trailing executable suffix from command filename
+    if (strSuffix(command_filename_1, ".exe"))
+      command_filename_1[strlen(command_filename_1) - 4] = '\0';
+
+    char *ro_base_path = getProgramMainDataPath(command_filename, RO_BASE_PATH);
+    char *conf_directory = getPath2(ro_base_path, CONF_DIRECTORY);
+
+    char *command_basepath = getBasePath(command_filename);
+    char *command_basename = getBaseNameNoSuffix(command_filename);
+    char *command_filename_2 = getPath2(command_basepath, command_basename);
+
+    config_filename_1 = getStringCat2(command_filename_1, ".conf");
+    config_filename_2 = getStringCat2(command_filename_2, ".conf");
+    config_filename_3 = getPath2(conf_directory, SETUP_FILENAME);
 
-  // strip trailing executable suffix from command filename
-  if (strSuffix(command_filename_1, ".exe"))
-    command_filename_1[strlen(command_filename_1) - 4] = '\0';
+    checked_free(ro_base_path);
+    checked_free(conf_directory);
 
-  char *ro_base_path = getProgramMainDataPath(command_filename, RO_BASE_PATH);
-  char *conf_directory = getPath2(ro_base_path, CONF_DIRECTORY);
+    checked_free(command_basepath);
+    checked_free(command_basename);
 
-  char *command_basepath = getBasePath(command_filename);
-  char *command_basename = getBaseNameNoSuffix(command_filename);
-  char *command_filename_2 = getPath2(command_basepath, command_basename);
+    checked_free(command_filename_1);
+    checked_free(command_filename_2);
 
-  char *config_filename_1 = getStringCat2(command_filename_1, ".conf");
-  char *config_filename_2 = getStringCat2(command_filename_2, ".conf");
-  char *config_filename_3 = getPath2(conf_directory, SETUP_FILENAME);
+    initialized = TRUE;
+  }
 
   // 1st try: look for config file that exactly matches the binary filename
   if (fileExists(config_filename_1))
@@ -800,9 +815,7 @@ char *getCustomImageFilename(char *basename)
   {
     free(filename);
 
-    if (options.debug)
-      Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)",
-           basename);
+    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)
@@ -873,9 +886,7 @@ char *getCustomSoundFilename(char *basename)
   {
     free(filename);
 
-    if (options.debug)
-      Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)",
-           basename);
+    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)
@@ -946,9 +957,7 @@ char *getCustomMusicFilename(char *basename)
   {
     free(filename);
 
-    if (options.debug)
-      Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)",
-           basename);
+    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)
@@ -1074,7 +1083,8 @@ void InitUserLevelDirectory(char *level_subdir)
     createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
     createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
 
-    SaveUserLevelInfo();
+    if (setup.internal.create_user_levelset)
+      SaveUserLevelInfo();
   }
 }
 
@@ -1282,21 +1292,59 @@ static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
 
   while (node)
   {
-    if (node->graphics_set_ecs && !setup.prefer_aga_graphics &&
-       !strEqual(node->graphics_set, node->graphics_set_ecs))
+    boolean want_ecs = (setup.prefer_aga_graphics == FALSE);
+    boolean want_aga = (setup.prefer_aga_graphics == TRUE);
+    boolean has_only_ecs = (!node->graphics_set && !node->graphics_set_aga);
+    boolean has_only_aga = (!node->graphics_set && !node->graphics_set_ecs);
+    char *graphics_set = NULL;
+
+    if (node->graphics_set_ecs && (want_ecs || has_only_ecs))
+      graphics_set = node->graphics_set_ecs;
+
+    if (node->graphics_set_aga && (want_aga || has_only_aga))
+      graphics_set = node->graphics_set_aga;
+
+    if (graphics_set && !strEqual(node->graphics_set, graphics_set))
     {
-      setString(&node->graphics_set, node->graphics_set_ecs);
+      setString(&node->graphics_set, graphics_set);
       settings_changed = TRUE;
     }
-    else if (node->graphics_set_aga && setup.prefer_aga_graphics &&
-            !strEqual(node->graphics_set, node->graphics_set_aga))
+
+    if (node->node_group != NULL)
+      settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
+
+    node = node->next;
+  }
+
+  return settings_changed;
+}
+
+static boolean adjustTreeSoundsForEMC(TreeInfo *node)
+{
+  boolean settings_changed = FALSE;
+
+  while (node)
+  {
+    boolean want_default = (setup.prefer_lowpass_sounds == FALSE);
+    boolean want_lowpass = (setup.prefer_lowpass_sounds == TRUE);
+    boolean has_only_default = (!node->sounds_set && !node->sounds_set_lowpass);
+    boolean has_only_lowpass = (!node->sounds_set && !node->sounds_set_default);
+    char *sounds_set = NULL;
+
+    if (node->sounds_set_default && (want_default || has_only_default))
+      sounds_set = node->sounds_set_default;
+
+    if (node->sounds_set_lowpass && (want_lowpass || has_only_lowpass))
+      sounds_set = node->sounds_set_lowpass;
+
+    if (sounds_set && !strEqual(node->sounds_set, sounds_set))
     {
-      setString(&node->graphics_set, node->graphics_set_aga);
+      setString(&node->sounds_set, sounds_set);
       settings_changed = TRUE;
     }
 
     if (node->node_group != NULL)
-      settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
+      settings_changed |= adjustTreeSoundsForEMC(node->node_group);
 
     node = node->next;
   }
@@ -1308,19 +1356,19 @@ void dumpTreeInfo(TreeInfo *node, int depth)
 {
   int i;
 
-  printf("Dumping TreeInfo:\n");
+  Debug("tree", "Dumping TreeInfo:");
 
   while (node)
   {
     for (i = 0; i < (depth + 1) * 3; i++)
-      printf(" ");
+      DebugContinued("", " ");
 
-    printf("'%s' / '%s'\n", node->identifier, node->name);
+    DebugContinued("tree", "'%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);
+    Debug("tree", "subdir == '%s' ['%s', '%s'] [%d])",
+         node->subdir, node->fullpath, node->basepath, node->in_user_dir);
     */
 
     if (node->node_group != NULL)
@@ -1451,10 +1499,10 @@ char *getHomeDir(void)
   {
     if ((dir = getenv("HOME")) == NULL)
     {
-      struct passwd *pwd;
+      dir = getUnixHomeDir();
 
-      if ((pwd = getpwuid(getuid())) != NULL)
-       dir = getStringCopy(pwd->pw_dir);
+      if (dir != NULL)
+       dir = getStringCopy(dir);
       else
        dir = ".";
     }
@@ -1580,8 +1628,7 @@ void createDirectory(char *dir, char *text, int permission_class)
   }
 
   if (posix_mkdir(dir, dir_mode) != 0)
-    Error(ERR_WARN, "cannot create %s directory '%s': %s",
-         text, dir, strerror(errno));
+    Warn("cannot create %s directory '%s': %s", text, dir, strerror(errno));
 
   if (permission_class == PERMS_PUBLIC && !running_setgid)
     chmod(dir, dir_mode);
@@ -1778,8 +1825,8 @@ static void printSetupFileList(SetupFileList *list)
   if (!list)
     return;
 
-  printf("token: '%s'\n", list->token);
-  printf("value: '%s'\n", list->value);
+  Debug("setup:printSetupFileList", "token: '%s'", list->token);
+  Debug("setup:printSetupFileList", "value: '%s'", list->value);
 
   printSetupFileList(list->next);
 }
@@ -1839,7 +1886,7 @@ SetupFileHash *newSetupFileHash(void)
     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");
+    Fail("create_hashtable() failed -- out of memory");
 
   return new_hash;
 }
@@ -1872,7 +1919,7 @@ void setHashEntry(SetupFileHash *hash, char *token, char *value)
   // change value; if it does not exist, insert it as new
   if (!change_hash_entry(hash, token, value_copy))
     if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
-      Error(ERR_EXIT, "cannot insert into hash -- aborting");
+      Fail("cannot insert into hash -- aborting");
 }
 
 char *removeHashEntry(SetupFileHash *hash, char *token)
@@ -1889,8 +1936,8 @@ static void printSetupFileHash(SetupFileHash *hash)
 {
   BEGIN_HASH_ITERATION(hash, itr)
   {
-    printf("token: '%s'\n", HASH_ITERATION_TOKEN(itr));
-    printf("value: '%s'\n", HASH_ITERATION_VALUE(itr));
+    Debug("setup:printSetupFileHash", "token: '%s'", HASH_ITERATION_TOKEN(itr));
+    Debug("setup:printSetupFileHash", "value: '%s'", HASH_ITERATION_VALUE(itr));
   }
   END_HASH_ITERATION(hash, itr)
 }
@@ -1995,25 +2042,25 @@ static boolean getTokenValueFromSetupLineExt(char *line,
     {
       if (!token_value_separator_warning)
       {
-       Error(ERR_INFO_LINE, "-");
+       Debug("setup", "---");
 
        if (filename != NULL)
        {
-         Error(ERR_WARN, "missing token/value separator(s) in config file:");
-         Error(ERR_INFO, "- config file: '%s'", filename);
+         Debug("setup", "missing token/value separator(s) in config file:");
+         Debug("setup", "- config file: '%s'", filename);
        }
        else
        {
-         Error(ERR_WARN, "missing token/value separator(s):");
+         Debug("setup", "missing token/value separator(s):");
        }
 
        token_value_separator_warning = TRUE;
       }
 
       if (filename != NULL)
-       Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
+       Debug("setup", "- line %d: '%s'", line_nr, line_raw);
       else
-       Error(ERR_INFO, "- line: '%s'", line_raw);
+       Debug("setup", "- line: '%s'", line_raw);
     }
 #endif
   }
@@ -2066,7 +2113,7 @@ static boolean loadSetupFileData(void *setup_file_data, char *filename,
   if (!(file = openFile(filename, MODE_READ)))
   {
 #if DEBUG_NO_CONFIG_FILE
-    Error(ERR_DEBUG, "cannot open configuration file '%s'", filename);
+    Debug("setup", "cannot open configuration file '%s'", filename);
 #endif
 
     return FALSE;
@@ -2147,7 +2194,7 @@ static boolean loadSetupFileData(void *setup_file_data, char *filename,
        }
        else
        {
-         Error(ERR_WARN, "ignoring already processed file '%s'", value);
+         Warn("ignoring already processed file '%s'", value);
        }
       }
       else
@@ -2162,16 +2209,16 @@ static boolean loadSetupFileData(void *setup_file_data, char *filename,
          {
            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);
+             Debug("setup", "---");
+             Debug("setup", "duplicate token(s) found in config file:");
+             Debug("setup", "- 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);
+           Debug("setup", "- token: '%s' (in line %d)", token, line_nr);
+           Debug("setup", "  old value: '%s'", old_value);
+           Debug("setup", "  new value: '%s'", value);
          }
 #endif
 
@@ -2191,16 +2238,16 @@ static boolean loadSetupFileData(void *setup_file_data, char *filename,
 
 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
   if (token_value_separator_warning)
-    Error(ERR_INFO_LINE, "-");
+    Debug("setup", "---");
 #endif
 
 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
   if (token_already_exists_warning)
-    Error(ERR_INFO_LINE, "-");
+    Debug("setup", "---");
 #endif
 
   if (token_count == 0 && include_count == 0)
-    Error(ERR_WARN, "configuration file '%s' is empty", filename);
+    Warn("configuration file '%s' is empty", filename);
 
   if (top_recursion_level)
     freeSetupFileHash(include_filename_hash);
@@ -2214,7 +2261,7 @@ static void saveSetupFileHash(SetupFileHash *hash, char *filename)
 
   if (!(file = fopen(filename, MODE_WRITE)))
   {
-    Error(ERR_WARN, "cannot write configuration file '%s'", filename);
+    Warn("cannot write configuration file '%s'", filename);
 
     return;
   }
@@ -2294,15 +2341,18 @@ SetupFileHash *loadSetupFileHash(char *filename)
 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS       17
 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA       18
 #define LEVELINFO_TOKEN_GRAPHICS_SET           19
-#define LEVELINFO_TOKEN_SOUNDS_SET             20
-#define LEVELINFO_TOKEN_MUSIC_SET              21
-#define LEVELINFO_TOKEN_FILENAME               22
-#define LEVELINFO_TOKEN_FILETYPE               23
-#define LEVELINFO_TOKEN_SPECIAL_FLAGS          24
-#define LEVELINFO_TOKEN_HANDICAP               25
-#define LEVELINFO_TOKEN_SKIP_LEVELS            26
-
-#define NUM_LEVELINFO_TOKENS                   27
+#define LEVELINFO_TOKEN_SOUNDS_SET_DEFAULT     20
+#define LEVELINFO_TOKEN_SOUNDS_SET_LOWPASS     21
+#define LEVELINFO_TOKEN_SOUNDS_SET             22
+#define LEVELINFO_TOKEN_MUSIC_SET              23
+#define LEVELINFO_TOKEN_FILENAME               24
+#define LEVELINFO_TOKEN_FILETYPE               25
+#define LEVELINFO_TOKEN_SPECIAL_FLAGS          26
+#define LEVELINFO_TOKEN_HANDICAP               27
+#define LEVELINFO_TOKEN_SKIP_LEVELS            28
+#define LEVELINFO_TOKEN_USE_EMC_TILES          29
+
+#define NUM_LEVELINFO_TOKENS                   30
 
 static LevelDirTree ldi;
 
@@ -2329,13 +2379,16 @@ static struct TokenInfo levelinfo_tokens[] =
   { 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_default,"sounds_set.default"    },
+  { TYPE_STRING,       &ldi.sounds_set_lowpass,"sounds_set.lowpass"    },
   { 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"           }
+  { TYPE_BOOLEAN,      &ldi.skip_levels,       "skip_levels"           },
+  { TYPE_BOOLEAN,      &ldi.use_emc_tiles,     "use_emc_tiles"         }
 };
 
 static struct TokenInfo artworkinfo_tokens[] =
@@ -2359,6 +2412,15 @@ static struct TokenInfo artworkinfo_tokens[] =
   { -1,                        NULL,                   NULL                    },
 };
 
+static char *optional_tokens[] =
+{
+  "program_title",
+  "program_copyright",
+  "program_company",
+
+  NULL
+};
+
 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
 {
   ti->type = type;
@@ -2408,6 +2470,8 @@ static void setTreeInfoToDefaults(TreeInfo *ti, int type)
     ti->graphics_set_ecs = NULL;
     ti->graphics_set_aga = NULL;
     ti->graphics_set = NULL;
+    ti->sounds_set_default = NULL;
+    ti->sounds_set_lowpass = NULL;
     ti->sounds_set = NULL;
     ti->music_set = NULL;
     ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
@@ -2427,6 +2491,8 @@ static void setTreeInfoToDefaults(TreeInfo *ti, int type)
     ti->readonly = TRUE;
     ti->handicap = TRUE;
     ti->skip_levels = FALSE;
+
+    ti->use_emc_tiles = FALSE;
   }
 }
 
@@ -2434,7 +2500,7 @@ static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
 {
   if (parent == NULL)
   {
-    Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
+    Warn("setTreeInfoToDefaultsFromParent(): parent == NULL");
 
     setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
 
@@ -2485,6 +2551,8 @@ static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
     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_default = getStringCopy(parent->sounds_set_default);
+    ti->sounds_set_lowpass = getStringCopy(parent->sounds_set_lowpass);
     ti->sounds_set = getStringCopy(parent->sounds_set);
     ti->music_set = getStringCopy(parent->music_set);
     ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
@@ -2504,6 +2572,8 @@ static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
     ti->readonly = parent->readonly;
     ti->handicap = parent->handicap;
     ti->skip_levels = parent->skip_levels;
+
+    ti->use_emc_tiles = parent->use_emc_tiles;
   }
 }
 
@@ -2543,6 +2613,8 @@ static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
   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_default  = getStringCopy(ti->sounds_set_default);
+  ti_copy->sounds_set_lowpass  = getStringCopy(ti->sounds_set_lowpass);
   ti_copy->sounds_set          = getStringCopy(ti->sounds_set);
   ti_copy->music_set           = getStringCopy(ti->music_set);
   ti_copy->graphics_path       = getStringCopy(ti->graphics_path);
@@ -2569,6 +2641,8 @@ static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
   ti_copy->handicap            = ti->handicap;
   ti_copy->skip_levels         = ti->skip_levels;
 
+  ti_copy->use_emc_tiles       = ti->use_emc_tiles;
+
   ti_copy->color               = ti->color;
   ti_copy->class_desc          = getStringCopy(ti->class_desc);
   ti_copy->handicap_level      = ti->handicap_level;
@@ -2610,6 +2684,8 @@ void freeTreeInfo(TreeInfo *ti)
     checked_free(ti->graphics_set_ecs);
     checked_free(ti->graphics_set_aga);
     checked_free(ti->graphics_set);
+    checked_free(ti->sounds_set_default);
+    checked_free(ti->sounds_set_lowpass);
     checked_free(ti->sounds_set);
     checked_free(ti->music_set);
 
@@ -2879,6 +2955,16 @@ static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
   if (!use_artworkinfo_cache)
     return NULL;
 
+  if (optional_tokens_hash == NULL)
+  {
+    int i;
+
+    // create hash from list of optional tokens (for quick access)
+    optional_tokens_hash = newSetupFileHash();
+    for (i = 0; optional_tokens[i] != NULL; i++)
+      setHashEntry(optional_tokens_hash, optional_tokens[i], "");
+  }
+
   if (cached)
   {
     int i;
@@ -2890,16 +2976,28 @@ static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
     ldi = *artwork_info;
     for (i = 0; artworkinfo_tokens[i].type != -1; i++)
     {
-      char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
+      char *token_suffix = artworkinfo_tokens[i].text;
+      char *token = getCacheToken(token_prefix, token_suffix);
       char *value = getHashEntry(artworkinfo_cache_old, token);
+      boolean optional =
+       (getHashEntry(optional_tokens_hash, token_suffix) != NULL);
 
-      // if defined, use value from cache, else keep default value
-      if (value != NULL)
-       setSetupInfo(artworkinfo_tokens, i, value);
+      setSetupInfo(artworkinfo_tokens, i, value);
+
+      // check if cache entry for this item is mandatory, but missing
+      if (value == NULL && !optional)
+      {
+       Warn("missing cache entry '%s'", token);
+
+       cached = FALSE;
+      }
     }
 
     *artwork_info = ldi;
+  }
 
+  if (cached)
+  {
     char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
                                        LEVELINFO_FILENAME);
     char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
@@ -3126,7 +3224,7 @@ char *ExtractZipFileIntoDirectory(char *zip_filename, char *directory,
 
   if (!zip_file_valid)
   {
-    Error(ERR_WARN, "zip file '%s' rejected!", zip_filename);
+    Warn("zip file '%s' rejected!", zip_filename);
 
     return NULL;
   }
@@ -3135,12 +3233,12 @@ char *ExtractZipFileIntoDirectory(char *zip_filename, char *directory,
 
   if (zip_entries == NULL)
   {
-    Error(ERR_WARN, "zip file '%s' could not be extracted!", zip_filename);
+    Warn("zip file '%s' could not be extracted!", zip_filename);
 
     return NULL;
   }
 
-  Error(ERR_INFO, "zip file '%s' successfully extracted!", zip_filename);
+  Info("zip file '%s' successfully extracted!", zip_filename);
 
   // first zip file entry contains top level directory
   char *top_dir = zip_entries[0];
@@ -3161,7 +3259,7 @@ static void ProcessZipFilesInDirectory(char *directory, int tree_type)
     // display error if directory is main "options.graphics_directory" etc.
     if (tree_type == TREE_TYPE_LEVEL_DIR ||
        directory == OPTIONS_ARTWORK_DIRECTORY(tree_type))
-      Error(ERR_WARN, "cannot read directory '%s'", directory);
+      Warn("cannot read directory '%s'", directory);
 
     return;
   }
@@ -3227,7 +3325,7 @@ static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
   if (setup_file_hash == NULL)
   {
 #if DEBUG_NO_CONFIG_FILE
-    Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
+    Debug("setup", "ignoring level directory '%s'", directory_path);
 #endif
 
     free(directory_path);
@@ -3338,7 +3436,7 @@ static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
 
   if ((dir = openDirectory(level_directory)) == NULL)
   {
-    Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
+    Warn("cannot read level directory '%s'", level_directory);
 
     return;
   }
@@ -3388,7 +3486,7 @@ static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
   }
 
   if (!valid_entry_found)
-    Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
+    Warn("cannot find any valid level series in directory '%s'",
          level_directory);
 }
 
@@ -3402,6 +3500,16 @@ boolean AdjustGraphicsForEMC(void)
   return settings_changed;
 }
 
+boolean AdjustSoundsForEMC(void)
+{
+  boolean settings_changed = FALSE;
+
+  settings_changed |= adjustTreeSoundsForEMC(leveldir_first_all);
+  settings_changed |= adjustTreeSoundsForEMC(leveldir_first);
+
+  return settings_changed;
+}
+
 void LoadLevelInfo(void)
 {
   InitUserLevelDirectory(getLoginName());
@@ -3421,12 +3529,13 @@ void LoadLevelInfo(void)
   cloneTree(&leveldir_first, leveldir_first_all, TRUE);
 
   AdjustGraphicsForEMC();
+  AdjustSoundsForEMC();
 
   // 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");
+    Fail("cannot find any valid level series in any directory");
 
   sortTreeInfo(&leveldir_first);
 
@@ -3474,7 +3583,7 @@ static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
     {
 #if DEBUG_NO_CONFIG_FILE
       if (!strEqual(directory_name, "."))
-       Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
+       Debug("setup", "ignoring artwork directory '%s'", directory_path);
 #endif
 
       free(directory_path);
@@ -3589,7 +3698,7 @@ static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
   {
     // 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);
+      Warn("cannot read directory '%s'", base_directory);
 
     return;
   }
@@ -3631,8 +3740,7 @@ static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
                                                      base_directory, ".",
                                                      type);
   if (!valid_entry_found)
-    Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
-         base_directory);
+    Warn("cannot find any valid artwork in directory '%s'", base_directory);
 }
 
 static TreeInfo *getDummyArtworkInfo(int type)
@@ -3717,9 +3825,12 @@ void LoadArtworkInfo(void)
   artwork.mus_current_identifier = artwork.mus_current->identifier;
 
 #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);
+  Debug("setup:LoadArtworkInfo", "graphics set == %s",
+       artwork.gfx_current_identifier);
+  Debug("setup:LoadArtworkInfo", "sounds set == %s",
+       artwork.snd_current_identifier);
+  Debug("setup:LoadArtworkInfo", "music set == %s",
+       artwork.mus_current_identifier);
 #endif
 
   sortTreeInfo(&artwork.gfx_first);
@@ -3860,88 +3971,89 @@ void LoadLevelArtworkInfo(void)
   print_timestamp_done("LoadLevelArtworkInfo");
 }
 
-static boolean AddUserLevelSetToLevelInfoExt(char *level_subdir_new)
+static boolean AddTreeSetToTreeInfoExt(TreeInfo *tree_node_old, char *tree_dir,
+                                      char *tree_subdir_new, int type)
 {
-  // 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);
+  if (tree_node_old == NULL)
+  {
+    if (type == TREE_TYPE_LEVEL_DIR)
+    {
+      // get level info tree node of personal user level set
+      tree_node_old = getTreeInfoFromIdentifier(leveldir_first, getLoginName());
 
-  // 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);
+      // this may happen if "setup.internal.create_user_levelset" is FALSE
+      // or if file "levelinfo.conf" is missing in personal user level set
+      if (tree_node_old == NULL)
+       tree_node_old = leveldir_first->node_group;
+    }
+    else
+    {
+      // get artwork info tree node of first artwork set
+      tree_node_old = ARTWORK_FIRST_NODE(artwork, type);
+    }
+  }
 
-  // set draw deactivation mask to previous value
-  SetDrawDeactivationMask(draw_deactivation_mask);
+  if (tree_dir == NULL)
+    tree_dir = TREE_USERDIR(type);
 
-  // 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
+  if (tree_node_old   == NULL ||
+      tree_dir        == NULL ||
+      tree_subdir_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");
-}
-
-static boolean AddUserArtworkSetToArtworkInfoExt(char *artwork_subdir_new,
-                                                int type)
-{
-  // get artwork info tree node of first artwork set
-  TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
-  char *artwork_user_dir = USER_ARTWORK_DIRECTORY(type);
-
   int draw_deactivation_mask = GetDrawDeactivationMask();
 
   // override draw deactivation mask (temporarily disable drawing)
   SetDrawDeactivationMask(REDRAW_ALL);
 
-  // load new artwork set config and add it next to first artwork set
-  LoadArtworkInfoFromArtworkConf(&artwork_first_node->next, NULL,
-                                artwork_user_dir, artwork_subdir_new, type);
+  if (type == TREE_TYPE_LEVEL_DIR)
+  {
+    // load new level set config and add it next to first user level set
+    LoadLevelInfoFromLevelConf(&tree_node_old->next,
+                              tree_node_old->node_parent,
+                              tree_dir, tree_subdir_new);
+  }
+  else
+  {
+    // load new artwork set config and add it next to first artwork set
+    LoadArtworkInfoFromArtworkConf(&tree_node_old->next,
+                                  tree_node_old->node_parent,
+                                  tree_dir, tree_subdir_new, type);
+  }
 
   // set draw deactivation mask to previous value
   SetDrawDeactivationMask(draw_deactivation_mask);
 
-  // get artwork info tree node of newly added artwork set
-  LevelDirTree *artwork_new = getTreeInfoFromIdentifier(artwork_first_node,
-                                                       artwork_subdir_new);
-  if (artwork_new == NULL)             // should not happen
+  // get first node of level or artwork info tree
+  TreeInfo **tree_node_first = TREE_FIRST_NODE_PTR(type);
+
+  // get tree info node of newly added level or artwork set
+  TreeInfo *tree_node_new = getTreeInfoFromIdentifier(*tree_node_first,
+                                                     tree_subdir_new);
+
+  if (tree_node_new == NULL)           // should not happen
     return FALSE;
 
   // correct top link and parent node link of newly created tree node
-  artwork_new->node_top    = artwork_first_node->node_top;
-  artwork_new->node_parent = artwork_first_node->node_parent;
+  tree_node_new->node_top    = tree_node_old->node_top;
+  tree_node_new->node_parent = tree_node_old->node_parent;
 
-  // sort artwork info tree to adjust position of newly added artwork set
-  sortTreeInfo(&artwork_first_node);
+  // sort tree info to adjust position of newly added tree set
+  sortTreeInfo(tree_node_first);
 
   return TRUE;
 }
 
-void AddUserArtworkSetToArtworkInfo(char *artwork_subdir_new, int type)
+void AddTreeSetToTreeInfo(TreeInfo *tree_node, char *tree_dir,
+                         char *tree_subdir_new, int type)
 {
-  if (!AddUserArtworkSetToArtworkInfoExt(artwork_subdir_new, type))
-    Error(ERR_EXIT, "internal artwork set structure corrupted -- aborting");
+  if (!AddTreeSetToTreeInfoExt(tree_node, tree_dir, tree_subdir_new, type))
+    Fail("internal tree info structure corrupted -- aborting");
+}
+
+void AddUserLevelSetToLevelInfo(char *level_subdir_new)
+{
+  AddTreeSetToTreeInfo(NULL, NULL, level_subdir_new, TREE_TYPE_LEVEL_DIR);
 }
 
 char *getArtworkIdentifierForUserLevelSet(int type)
@@ -3971,8 +4083,17 @@ TreeInfo *getArtworkTreeInfoForUserLevelSet(int type)
 {
   char *artwork_set = getArtworkIdentifierForUserLevelSet(type);
   TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
+  TreeInfo *ti = getTreeInfoFromIdentifier(artwork_first_node, artwork_set);
+
+  if (ti == NULL)
+  {
+    ti = getTreeInfoFromIdentifier(artwork_first_node,
+                                  ARTWORK_DEFAULT_SUBDIR(type));
+    if (ti == NULL)
+      Fail("cannot find default graphics -- should not happen");
+  }
 
-  return getTreeInfoFromIdentifier(artwork_first_node, artwork_set);
+  return ti;
 }
 
 boolean checkIfCustomArtworkExistsForCurrentLevelSet(void)
@@ -4069,7 +4190,8 @@ boolean CreateUserLevelSet(char *level_subdir, char *level_name,
 
   if (!(file = fopen(filename, MODE_WRITE)))
   {
-    Error(ERR_WARN, "cannot write level info file '%s'", filename);
+    Warn("cannot write level info file '%s'", filename);
+
     free(filename);
 
     return FALSE;
@@ -4280,7 +4402,7 @@ void LoadLevelSetup_LastSeries(void)
   }
   else
   {
-    Error(ERR_DEBUG, "using default setup values");
+    Debug("setup", "using default setup values");
   }
 
   free(filename);
@@ -4304,7 +4426,7 @@ static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
 
   if (!(file = fopen(filename, MODE_WRITE)))
   {
-    Error(ERR_WARN, "cannot write setup file '%s'", filename);
+    Warn("cannot write setup file '%s'", filename);
 
     free(filename);
 
@@ -4340,6 +4462,11 @@ static void checkSeriesInfo(void)
 {
   static char *level_directory = NULL;
   Directory *dir;
+#if 0
+  DirectoryEntry *dir_entry;
+#endif
+
+  checked_free(level_directory);
 
   // check for more levels besides the 'levels' field of 'levelinfo.conf'
 
@@ -4350,11 +4477,42 @@ static void checkSeriesInfo(void)
 
   if ((dir = openDirectory(level_directory)) == NULL)
   {
-    Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
+    Warn("cannot read level directory '%s'", level_directory);
 
     return;
   }
 
+#if 0
+  while ((dir_entry = readDirectory(dir)) != NULL)     // loop all entries
+  {
+    if (strlen(dir_entry->basename) > 4 &&
+       dir_entry->basename[3] == '.' &&
+       strEqual(&dir_entry->basename[4], LEVELFILE_EXTENSION))
+    {
+      char levelnum_str[4];
+      int levelnum_value;
+
+      strncpy(levelnum_str, dir_entry->basename, 3);
+      levelnum_str[3] = '\0';
+
+      levelnum_value = atoi(levelnum_str);
+
+      if (levelnum_value < leveldir_current->first_level)
+      {
+       Warn("additional level %d found", levelnum_value);
+
+       leveldir_current->first_level = levelnum_value;
+      }
+      else if (levelnum_value > leveldir_current->last_level)
+      {
+       Warn("additional level %d found", levelnum_value);
+
+       leveldir_current->last_level = levelnum_value;
+      }
+    }
+  }
+#endif
+
   closeDirectory(dir);
 }
 
@@ -4450,7 +4608,7 @@ void LoadLevelSetup_SeriesInfo(void)
   }
   else
   {
-    Error(ERR_DEBUG, "using default setup values");
+    Debug("setup", "using default setup values");
   }
 
   free(filename);
@@ -4475,8 +4633,10 @@ void SaveLevelSetup_SeriesInfo(void)
 
   if (!(file = fopen(filename, MODE_WRITE)))
   {
-    Error(ERR_WARN, "cannot write setup file '%s'", filename);
+    Warn("cannot write setup file '%s'", filename);
+
     free(filename);
+
     return;
   }