removed unused option to set first level of user level sets not to be '1'
[rocksndiamonds.git] / src / libgame / setup.c
index e60c8424f5409b67334bc5894912ae4390877212..8662b204bb5f336ca426408be1b2fe841efa58a7 100644 (file)
@@ -30,7 +30,6 @@
 #include "hash.h"
 
 
-#define USE_FILE_IDENTIFIERS   FALSE   /* do not use identifiers anymore */
 #define ENABLE_UNUSED_CODE     FALSE   /* for currently unused functions */
 
 #define NUM_LEVELCLASS_DESC    8
@@ -134,15 +133,25 @@ static char *getUserLevelDir(char *level_subdir)
 static char *getScoreDir(char *level_subdir)
 {
   static char *score_dir = NULL;
-  char *data_dir = getCommonDataDir();
+  static char *score_level_dir = NULL;
   char *score_subdir = SCORES_DIRECTORY;
 
-  checked_free(score_dir);
+  if (score_dir == NULL)
+  {
+    if (program.global_scores)
+      score_dir = getPath2(getCommonDataDir(),   score_subdir);
+    else
+      score_dir = getPath2(getUserGameDataDir(), score_subdir);
+  }
 
   if (level_subdir != NULL)
-    score_dir = getPath3(data_dir, score_subdir, level_subdir);
-  else
-    score_dir = getPath2(data_dir, score_subdir);
+  {
+    checked_free(score_level_dir);
+
+    score_level_dir = getPath2(score_dir, level_subdir);
+
+    return score_level_dir;
+  }
 
   return score_dir;
 }
@@ -193,6 +202,28 @@ char *getCurrentLevelDir()
   return getLevelDirFromTreeInfo(leveldir_current);
 }
 
+char *getNewUserLevelSubdir()
+{
+  static char *new_level_subdir = NULL;
+  char *subdir_prefix = getLoginName();
+  char subdir_suffix[10];
+  int max_suffix_number = 1000;
+  int i = 0;
+
+  while (++i < max_suffix_number)
+  {
+    sprintf(subdir_suffix, "_%d", i);
+
+    checked_free(new_level_subdir);
+    new_level_subdir = getStringCat2(subdir_prefix, subdir_suffix);
+
+    if (!directoryExists(getUserLevelDir(new_level_subdir)))
+      break;
+  }
+
+  return new_level_subdir;
+}
+
 static char *getTapeDir(char *level_subdir)
 {
   static char *tape_dir = NULL;
@@ -315,6 +346,9 @@ static char *getSetupArtworkDir(TreeInfo *ti)
 {
   static char *artwork_dir = NULL;
 
+  if (ti == NULL)
+    return NULL;
+
   checked_free(artwork_dir);
 
   artwork_dir = getPath2(ti->basepath, ti->fullpath);
@@ -391,6 +425,29 @@ inline static char *getLevelArtworkDir(int type)
   return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
 }
 
+char *getProgramConfigFilename(char *command_filename_ptr)
+{
+  char *command_filename_1 = getStringCopy(command_filename_ptr);
+
+  // strip trailing executable suffix from command filename
+  if (strSuffix(command_filename_1, ".exe"))
+    command_filename_1[strlen(command_filename_1) - 4] = '\0';
+
+  char *command_basepath = getBasePath(command_filename_ptr);
+  char *command_basename = getBaseNameNoSuffix(command_filename_ptr);
+  char *command_filename_2 = getPath2(command_basepath, command_basename);
+
+  char *config_filename_1 = getStringCat2(command_filename_1, ".conf");
+  char *config_filename_2 = getStringCat2(command_filename_2, ".conf");
+
+  // 1st try: look for config file that exactly matches the binary filename
+  if (fileExists(config_filename_1))
+    return config_filename_1;
+
+  // 2nd try: return config filename that matches binary filename without suffix
+  return config_filename_2;
+}
+
 char *getTapeFilename(int nr)
 {
   static char *filename = NULL;
@@ -454,6 +511,11 @@ char *getSetupFilename()
   return filename;
 }
 
+char *getDefaultSetupFilename()
+{
+  return program.config_filename;
+}
+
 char *getEditorSetupFilename()
 {
   static char *filename = NULL;
@@ -614,7 +676,7 @@ char *getCustomImageFilename(char *basename)
   if (!gfx.override_level_graphics)
   {
     /* 1st try: look for special artwork in current level series directory */
-    filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
+    filename = getImg3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
     if (fileExists(filename))
       return filename;
 
@@ -624,7 +686,7 @@ char *getCustomImageFilename(char *basename)
     if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
     {
       /* 2nd try: look for special artwork configured in level series config */
-      filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
+      filename = getImg2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
       if (fileExists(filename))
        return filename;
 
@@ -638,7 +700,7 @@ char *getCustomImageFilename(char *basename)
   if (!skip_setup_artwork)
   {
     /* 3rd try: look for special artwork in configured artwork directory */
-    filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
+    filename = getImg2(getSetupArtworkDir(artwork.gfx_current), basename);
     if (fileExists(filename))
       return filename;
 
@@ -646,29 +708,31 @@ char *getCustomImageFilename(char *basename)
   }
 
   /* 4th try: look for default artwork in new default artwork directory */
-  filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
+  filename = getImg2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
   if (fileExists(filename))
     return filename;
 
   free(filename);
 
   /* 5th try: look for default artwork in old default artwork directory */
-  filename = getPath2(options.graphics_directory, basename);
+  filename = getImg2(options.graphics_directory, basename);
   if (fileExists(filename))
     return filename;
 
-#if defined(CREATE_SPECIAL_EDITION)
-  free(filename);
+  if (!strEqual(GFX_FALLBACK_FILENAME, UNDEFINED_FILENAME))
+  {
+    free(filename);
 
-  if (options.debug)
-    Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)", basename);
+    if (options.debug)
+      Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)",
+           basename);
 
-  /* 6th try: look for fallback artwork in old default artwork directory */
-  /* (needed to prevent errors when trying to access unused artwork files) */
-  filename = getPath2(options.graphics_directory, GFX_FALLBACK_FILENAME);
-  if (fileExists(filename))
-    return filename;
-#endif
+    /* 6th try: look for fallback artwork in old default artwork directory */
+    /* (needed to prevent errors when trying to access unused artwork files) */
+    filename = getImg2(options.graphics_directory, GFX_FALLBACK_FILENAME);
+    if (fileExists(filename))
+      return filename;
+  }
 
   return NULL;         /* cannot find specified artwork file anywhere */
 }
@@ -728,18 +792,20 @@ char *getCustomSoundFilename(char *basename)
   if (fileExists(filename))
     return filename;
 
-#if defined(CREATE_SPECIAL_EDITION)
-  free(filename);
+  if (!strEqual(SND_FALLBACK_FILENAME, UNDEFINED_FILENAME))
+  {
+    free(filename);
 
-  if (options.debug)
-    Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)", basename);
+    if (options.debug)
+      Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)",
+           basename);
 
-  /* 6th try: look for fallback artwork in old default artwork directory */
-  /* (needed to prevent errors when trying to access unused artwork files) */
-  filename = getPath2(options.sounds_directory, SND_FALLBACK_FILENAME);
-  if (fileExists(filename))
-    return filename;
-#endif
+    /* 6th try: look for fallback artwork in old default artwork directory */
+    /* (needed to prevent errors when trying to access unused artwork files) */
+    filename = getPath2(options.sounds_directory, SND_FALLBACK_FILENAME);
+    if (fileExists(filename))
+      return filename;
+  }
 
   return NULL;         /* cannot find specified artwork file anywhere */
 }
@@ -799,18 +865,20 @@ char *getCustomMusicFilename(char *basename)
   if (fileExists(filename))
     return filename;
 
-#if defined(CREATE_SPECIAL_EDITION)
-  free(filename);
+  if (!strEqual(MUS_FALLBACK_FILENAME, UNDEFINED_FILENAME))
+  {
+    free(filename);
 
-  if (options.debug)
-    Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)", basename);
+    if (options.debug)
+      Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)",
+           basename);
 
-  /* 6th try: look for fallback artwork in old default artwork directory */
-  /* (needed to prevent errors when trying to access unused artwork files) */
-  filename = getPath2(options.music_directory, MUS_FALLBACK_FILENAME);
-  if (fileExists(filename))
-    return filename;
-#endif
+    /* 6th try: look for fallback artwork in old default artwork directory */
+    /* (needed to prevent errors when trying to access unused artwork files) */
+    filename = getPath2(options.music_directory, MUS_FALLBACK_FILENAME);
+    if (fileExists(filename))
+      return filename;
+  }
 
   return NULL;         /* cannot find specified artwork file anywhere */
 }
@@ -908,9 +976,15 @@ void InitTapeDirectory(char *level_subdir)
 
 void InitScoreDirectory(char *level_subdir)
 {
-  createDirectory(getCommonDataDir(), "common data", PERMS_PUBLIC);
-  createDirectory(getScoreDir(NULL), "main score", PERMS_PUBLIC);
-  createDirectory(getScoreDir(level_subdir), "level score", PERMS_PUBLIC);
+  int permissions = (program.global_scores ? PERMS_PUBLIC : PERMS_PRIVATE);
+
+  if (program.global_scores)
+    createDirectory(getCommonDataDir(), "common data", permissions);
+  else
+    createDirectory(getUserGameDataDir(), "user data", permissions);
+
+  createDirectory(getScoreDir(NULL), "main score", permissions);
+  createDirectory(getScoreDir(level_subdir), "level score", permissions);
 }
 
 static void SaveUserLevelInfo();
@@ -1260,14 +1334,17 @@ void sortTreeInfo(TreeInfo **node_first)
 #define MODE_X_ALL             (S_IXUSR | S_IXGRP | S_IXOTH)
 
 #define MODE_W_PRIVATE         (S_IWUSR)
-#define MODE_W_PUBLIC          (S_IWUSR | S_IWGRP)
+#define MODE_W_PUBLIC_FILE     (S_IWUSR | S_IWGRP)
 #define MODE_W_PUBLIC_DIR      (S_IWUSR | S_IWGRP | S_ISGID)
 
 #define DIR_PERMS_PRIVATE      (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
 #define DIR_PERMS_PUBLIC       (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
+#define DIR_PERMS_PUBLIC_ALL   (MODE_R_ALL | MODE_X_ALL | MODE_W_ALL)
 
 #define FILE_PERMS_PRIVATE     (MODE_R_ALL | MODE_W_PRIVATE)
-#define FILE_PERMS_PUBLIC      (MODE_R_ALL | MODE_W_PUBLIC)
+#define FILE_PERMS_PUBLIC      (MODE_R_ALL | MODE_W_PUBLIC_FILE)
+#define FILE_PERMS_PUBLIC_ALL  (MODE_R_ALL | MODE_W_ALL)
+
 
 char *getHomeDir()
 {
@@ -1345,7 +1422,10 @@ char *getUserGameDataDir(void)
 
 #if defined(PLATFORM_ANDROID)
   if (user_game_data_dir == NULL)
-    user_game_data_dir = (char *)SDL_AndroidGetInternalStoragePath();
+    user_game_data_dir = (char *)(SDL_AndroidGetExternalStorageState() &
+                                 SDL_ANDROID_EXTERNAL_STORAGE_WRITE ?
+                                 SDL_AndroidGetExternalStoragePath() :
+                                 SDL_AndroidGetInternalStoragePath());
 #else
   if (user_game_data_dir == NULL)
     user_game_data_dir = getPath2(getPersonalDataDir(),
@@ -1355,30 +1435,6 @@ char *getUserGameDataDir(void)
   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 (directoryExists(userdata_dir_old) && !directoryExists(userdata_dir_new))
-  {
-    if (rename(userdata_dir_old, userdata_dir_new) != 0)
-    {
-      Error(ERR_WARN, "cannot move game data directory '%s' to '%s'",
-           userdata_dir_old, userdata_dir_new);
-
-      /* continue using Unix style data directory -- this should not happen */
-      program.userdata_path = getPath2(getPersonalDataDir(),
-                                      program.userdata_subdir_unix);
-    }
-  }
-
-  free(userdata_dir_old);
-#endif
-}
-
 char *getSetupDir()
 {
   return getUserGameDataDir();
@@ -1413,6 +1469,9 @@ static boolean posix_process_running_setgid()
 
 void createDirectory(char *dir, char *text, int permission_class)
 {
+  if (directoryExists(dir))
+    return;
+
   /* leave "other" permissions in umask untouched, but ensure group parts
      of USERDATA_DIR_MODE are not masked */
   mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
@@ -1421,18 +1480,20 @@ void createDirectory(char *dir, char *text, int permission_class)
   mode_t group_umask = ~(dir_mode & S_IRWXG);
   int running_setgid = posix_process_running_setgid();
 
-  /* if we're setgid, protect files against "other" */
-  /* else keep umask(0) to make the dir world-writable */
+  if (permission_class == PERMS_PUBLIC)
+  {
+    /* if we're setgid, protect files against "other" */
+    /* else keep umask(0) to make the dir world-writable */
 
-  if (running_setgid)
-    posix_umask(last_umask & group_umask);
-  else
-    dir_mode |= MODE_W_ALL;
+    if (running_setgid)
+      posix_umask(last_umask & group_umask);
+    else
+      dir_mode = DIR_PERMS_PUBLIC_ALL;
+  }
 
-  if (!directoryExists(dir))
-    if (posix_mkdir(dir, dir_mode) != 0)
-      Error(ERR_WARN, "cannot create %s directory '%s': %s",
-           text, dir, strerror(errno));
+  if (posix_mkdir(dir, dir_mode) != 0)
+    Error(ERR_WARN, "cannot create %s directory '%s': %s",
+         text, dir, strerror(errno));
 
   if (permission_class == PERMS_PUBLIC && !running_setgid)
     chmod(dir, dir_mode);
@@ -1452,7 +1513,7 @@ void SetFilePermissions(char *filename, int permission_class)
               FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
 
   if (permission_class == PERMS_PUBLIC && !running_setgid)
-    perms |= MODE_W_ALL;
+    perms = FILE_PERMS_PUBLIC_ALL;
 
   chmod(filename, perms);
 }
@@ -1472,6 +1533,17 @@ char *getCookie(char *file_type)
   return cookie;
 }
 
+void fprintFileHeader(FILE *file, char *basename)
+{
+  char *prefix = "# ";
+  char *sep1 = "=";
+
+  fprintf_line_with_prefix(file, prefix, sep1, 77);
+  fprintf(file, "%s%s\n", prefix, basename);
+  fprintf_line_with_prefix(file, prefix, sep1, 77);
+  fprintf(file, "\n");
+}
+
 int getFileVersionFromCookieString(const char *cookie)
 {
   const char *ptr_cookie1, *ptr_cookie2;
@@ -1519,6 +1591,7 @@ boolean checkCookieString(const char *cookie, const char *template)
   return TRUE;
 }
 
+
 /* ------------------------------------------------------------------------- */
 /* setup file list and hash handling functions                               */
 /* ------------------------------------------------------------------------- */
@@ -1904,7 +1977,7 @@ static boolean loadSetupFileData(void *setup_file_data, char *filename,
 
   if (!(file = openFile(filename, MODE_READ)))
   {
-    Error(ERR_WARN, "cannot open configuration file '%s'", filename);
+    Error(ERR_DEBUG, "cannot open configuration file '%s'", filename);
 
     return FALSE;
   }
@@ -2101,19 +2174,6 @@ SetupFileHash *loadSetupFileHash(char *filename)
   return setup_file_hash;
 }
 
-void checkSetupFileHashIdentifier(SetupFileHash *setup_file_hash,
-                                 char *filename, char *identifier)
-{
-#if USE_FILE_IDENTIFIERS
-  char *value = getHashEntry(setup_file_hash, TOKEN_STR_FILE_IDENTIFIER);
-
-  if (value == NULL)
-    Error(ERR_WARN, "config file '%s' has no file identifier", filename);
-  else if (!checkCookieString(value, identifier))
-    Error(ERR_WARN, "config file '%s' has wrong file identifier", filename);
-#endif
-}
-
 
 /* ========================================================================= */
 /* setup file stuff                                                          */
@@ -2315,28 +2375,28 @@ static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
     ti->imported_by = getStringCopy(parent->imported_by);
     ti->tested_by = getStringCopy(parent->tested_by);
 
-    ti->graphics_set_ecs = NULL;
-    ti->graphics_set_aga = NULL;
-    ti->graphics_set = NULL;
-    ti->sounds_set = NULL;
-    ti->music_set = NULL;
+    ti->graphics_set_ecs = getStringCopy(parent->graphics_set_ecs);
+    ti->graphics_set_aga = getStringCopy(parent->graphics_set_aga);
+    ti->graphics_set = getStringCopy(parent->graphics_set);
+    ti->sounds_set = getStringCopy(parent->sounds_set);
+    ti->music_set = getStringCopy(parent->music_set);
     ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
     ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
     ti->music_path = getStringCopy(UNDEFINED_FILENAME);
 
-    ti->level_filename = NULL;
-    ti->level_filetype = NULL;
+    ti->level_filename = getStringCopy(parent->level_filename);
+    ti->level_filetype = getStringCopy(parent->level_filetype);
 
     ti->special_flags = getStringCopy(parent->special_flags);
 
-    ti->levels = 0;
-    ti->first_level = 0;
-    ti->last_level = 0;
+    ti->levels = parent->levels;
+    ti->first_level = parent->first_level;
+    ti->last_level = parent->last_level;
     ti->level_group = FALSE;
-    ti->handicap_level = 0;
+    ti->handicap_level = parent->handicap_level;
     ti->readonly = parent->readonly;
-    ti->handicap = TRUE;
-    ti->skip_levels = FALSE;
+    ti->handicap = parent->handicap;
+    ti->skip_levels = parent->skip_levels;
   }
 }
 
@@ -2541,12 +2601,12 @@ static int compareTreeInfoEntries(const void *object1, const void *object2)
   return compare_result;
 }
 
-static void createParentTreeInfoNode(TreeInfo *node_parent)
+static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
 {
   TreeInfo *ti_new;
 
   if (node_parent == NULL)
-    return;
+    return NULL;
 
   ti_new = newTreeInfo();
   setTreeInfoToDefaults(ti_new, node_parent->type);
@@ -2558,7 +2618,7 @@ static void createParentTreeInfoNode(TreeInfo *node_parent)
   setString(&ti_new->name, ".. (parent directory)");
   setString(&ti_new->name_sorting, ti_new->name);
 
-  setString(&ti_new->subdir, "..");
+  setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
   setString(&ti_new->fullpath, node_parent->fullpath);
 
   ti_new->sort_priority = node_parent->sort_priority;
@@ -2567,6 +2627,44 @@ static void createParentTreeInfoNode(TreeInfo *node_parent)
   setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
 
   pushTreeInfo(&node_parent->node_group, ti_new);
+
+  return ti_new;
+}
+
+static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
+{
+  TreeInfo *ti_new, *ti_new2;
+
+  if (node_first == NULL)
+    return NULL;
+
+  ti_new = newTreeInfo();
+  setTreeInfoToDefaults(ti_new, TREE_TYPE_LEVEL_DIR);
+
+  ti_new->node_parent = NULL;
+  ti_new->parent_link = FALSE;
+
+  setString(&ti_new->identifier, node_first->identifier);
+  setString(&ti_new->name, "level sets");
+  setString(&ti_new->name_sorting, ti_new->name);
+
+  setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
+  setString(&ti_new->fullpath, ".");
+
+  ti_new->sort_priority = node_first->sort_priority;;
+  ti_new->latest_engine = node_first->latest_engine;
+
+  setString(&ti_new->class_desc, "level sets");
+
+  ti_new->node_group = node_first;
+  ti_new->level_group = TRUE;
+
+  ti_new2 = createParentTreeInfoNode(ti_new);
+
+  setString(&ti_new2->name, ".. (main menu)");
+  setString(&ti_new2->name_sorting, ti_new2->name);
+
+  return ti_new;
 }
 
 
@@ -2817,9 +2915,6 @@ static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
 
   leveldir_new->subdir = getStringCopy(directory_name);
 
-  checkSetupFileHashIdentifier(setup_file_hash, filename,
-                              getCookie("LEVELINFO"));
-
   /* set all structure fields according to the token/value pairs */
   ldi = *leveldir_new;
   for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
@@ -2980,6 +3075,8 @@ void LoadLevelInfo()
   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
 
+  leveldir_first = createTopTreeInfoNode(leveldir_first);
+
   /* after loading all level set information, clone the level directory tree
      and remove all level sets without levels (these may still contain artwork
      to be offered in the setup menu as "custom artwork", and are therefore
@@ -3026,9 +3123,7 @@ static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
     {
       while ((dir_entry = readDirectory(dir)) != NULL)
       {
-       char *entry_name = dir_entry->basename;
-
-       if (FileIsArtworkType(entry_name, type))
+       if (FileIsArtworkType(dir_entry->filename, type))
        {
          valid_file_found = TRUE;
 
@@ -3421,20 +3516,132 @@ void LoadLevelArtworkInfo()
   print_timestamp_done("LoadLevelArtworkInfo");
 }
 
-static void SaveUserLevelInfo()
+static boolean AddUserLevelSetToLevelInfoExt(char *level_subdir_new)
+{
+  // get level info tree node of first (original) user level set
+  char *level_subdir_old = getLoginName();
+  LevelDirTree *leveldir_old = getTreeInfoFromIdentifier(leveldir_first,
+                                                        level_subdir_old);
+  if (leveldir_old == NULL)            // should not happen
+    return FALSE;
+
+  int draw_deactivation_mask = GetDrawDeactivationMask();
+
+  // override draw deactivation mask (temporarily disable drawing)
+  SetDrawDeactivationMask(REDRAW_ALL);
+
+  // load new level set config and add it next to first user level set
+  LoadLevelInfoFromLevelConf(&leveldir_old->next, NULL,
+                            leveldir_old->basepath, level_subdir_new);
+
+  // set draw deactivation mask to previous value
+  SetDrawDeactivationMask(draw_deactivation_mask);
+
+  // get level info tree node of newly added user level set
+  LevelDirTree *leveldir_new = getTreeInfoFromIdentifier(leveldir_first,
+                                                        level_subdir_new);
+  if (leveldir_new == NULL)            // should not happen
+    return FALSE;
+
+  // correct top link and parent node link of newly created tree node
+  leveldir_new->node_top    = leveldir_old->node_top;
+  leveldir_new->node_parent = leveldir_old->node_parent;
+
+  // sort level info tree to adjust position of newly added level set
+  sortTreeInfo(&leveldir_first);
+
+  return TRUE;
+}
+
+void AddUserLevelSetToLevelInfo(char *level_subdir_new)
+{
+  if (!AddUserLevelSetToLevelInfoExt(level_subdir_new))
+    Error(ERR_EXIT, "internal level set structure corrupted -- aborting");
+}
+
+boolean UpdateUserLevelSet(char *level_subdir, char *level_name,
+                          char *level_author, int num_levels)
+{
+  char *filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
+  char *filename_tmp = getStringCat2(filename, ".tmp");
+  FILE *file = NULL;
+  FILE *file_tmp = NULL;
+  char line[MAX_LINE_LEN];
+  boolean success = FALSE;
+  LevelDirTree *leveldir = getTreeInfoFromIdentifier(leveldir_first,
+                                                    level_subdir);
+  // update values in level directory tree
+
+  if (level_name != NULL)
+    setString(&leveldir->name, level_name);
+
+  if (level_author != NULL)
+    setString(&leveldir->author, level_author);
+
+  if (num_levels != -1)
+    leveldir->levels = num_levels;
+
+  // update values that depend on other values
+
+  setString(&leveldir->name_sorting, leveldir->name);
+
+  leveldir->last_level = leveldir->first_level + leveldir->levels - 1;
+
+  // sort order of level sets may have changed
+  sortTreeInfo(&leveldir_first);
+
+  if ((file     = fopen(filename,     MODE_READ)) &&
+      (file_tmp = fopen(filename_tmp, MODE_WRITE)))
+  {
+    while (fgets(line, MAX_LINE_LEN, file))
+    {
+      if (strPrefix(line, "name:") && level_name != NULL)
+       fprintf(file_tmp, "%-32s%s\n", "name:", level_name);
+      else if (strPrefix(line, "author:") && level_author != NULL)
+       fprintf(file_tmp, "%-32s%s\n", "author:", level_author);
+      else if (strPrefix(line, "levels:") && num_levels != -1)
+       fprintf(file_tmp, "%-32s%d\n", "levels:", num_levels);
+      else
+       fputs(line, file_tmp);
+    }
+
+    success = TRUE;
+  }
+
+  if (file)
+    fclose(file);
+
+  if (file_tmp)
+    fclose(file_tmp);
+
+  if (success)
+    success = (rename(filename_tmp, filename) == 0);
+
+  free(filename);
+  free(filename_tmp);
+
+  return success;
+}
+
+boolean CreateUserLevelSet(char *level_subdir, char *level_name,
+                          char *level_author, int num_levels)
 {
   LevelDirTree *level_info;
   char *filename;
   FILE *file;
   int i;
 
-  filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
+  // create user level sub-directory, if needed
+  createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
+
+  filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
 
   if (!(file = fopen(filename, MODE_WRITE)))
   {
     Error(ERR_WARN, "cannot write level info file '%s'", filename);
     free(filename);
-    return;
+
+    return FALSE;
   }
 
   level_info = newTreeInfo();
@@ -3442,15 +3649,16 @@ static void SaveUserLevelInfo()
   /* always start with reliable default values */
   setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
 
-  setString(&level_info->name, getLoginName());
-  setString(&level_info->author, getRealName());
-  level_info->levels = 100;
+  setString(&level_info->name, level_name);
+  setString(&level_info->author, level_author);
+  level_info->levels = num_levels;
   level_info->first_level = 1;
+  level_info->sort_priority = LEVELCLASS_PRIVATE_START;
+  level_info->readonly = FALSE;
 
   token_value_position = TOKEN_VALUE_POSITION_SHORT;
 
-  fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
-                                                getCookie("LEVELINFO")));
+  fprintFileHeader(file, LEVELINFO_FILENAME);
 
   ldi = *level_info;
   for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
@@ -3458,11 +3666,14 @@ static void SaveUserLevelInfo()
     if (i == LEVELINFO_TOKEN_NAME ||
        i == LEVELINFO_TOKEN_AUTHOR ||
        i == LEVELINFO_TOKEN_LEVELS ||
-       i == LEVELINFO_TOKEN_FIRST_LEVEL)
+       i == LEVELINFO_TOKEN_FIRST_LEVEL ||
+       i == LEVELINFO_TOKEN_SORT_PRIORITY ||
+       i == LEVELINFO_TOKEN_READONLY)
       fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
 
     /* just to make things nicer :) */
-    if (i == LEVELINFO_TOKEN_AUTHOR)
+    if (i == LEVELINFO_TOKEN_AUTHOR ||
+       i == LEVELINFO_TOKEN_FIRST_LEVEL)
       fprintf(file, "\n");     
   }
 
@@ -3474,6 +3685,13 @@ static void SaveUserLevelInfo()
 
   freeTreeInfo(level_info);
   free(filename);
+
+  return TRUE;
+}
+
+static void SaveUserLevelInfo()
+{
+  CreateUserLevelSet(getLoginName(), getLoginName(), getRealName(), 100);
 }
 
 char *getSetupValue(int type, void *value)
@@ -3591,12 +3809,13 @@ void LoadLevelSetup_LastSeries()
   /* always start with reliable default values */
   leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
 
-#if defined(CREATE_SPECIAL_EDITION_RND_JUE)
-  leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
-                                              "jue_start");
-  if (leveldir_current == NULL)
-    leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
-#endif
+  if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
+  {
+    leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
+                                                DEFAULT_LEVELSET);
+    if (leveldir_current == NULL)
+      leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
+  }
 
   if ((level_setup_hash = loadSetupFileHash(filename)))
   {
@@ -3608,13 +3827,12 @@ void LoadLevelSetup_LastSeries()
     if (leveldir_current == NULL)
       leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
 
-    checkSetupFileHashIdentifier(level_setup_hash, filename,
-                                getCookie("LEVELSETUP"));
-
     freeSetupFileHash(level_setup_hash);
   }
   else
-    Error(ERR_WARN, "using default setup values");
+  {
+    Error(ERR_DEBUG, "using default setup values");
+  }
 
   free(filename);
 }
@@ -3644,8 +3862,7 @@ static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
     return;
   }
 
-  fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
-                                                getCookie("LEVELSETUP")));
+  fprintFileHeader(file, LEVELSETUP_FILENAME);
 
   if (deactivate_last_level_series)
     fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
@@ -3708,7 +3925,7 @@ void LoadLevelSetup_SeriesInfo()
     LevelStats_setSolved(i, 0);
   }
 
-  checkSeriesInfo(leveldir_current);
+  checkSeriesInfo();
 
   /* ----------------------------------------------------------------------- */
   /* ~/.<program>/levelsetup/<level series>/levelsetup.conf                  */
@@ -3780,13 +3997,12 @@ void LoadLevelSetup_SeriesInfo()
     }
     END_HASH_ITERATION(hash, itr)
 
-    checkSetupFileHashIdentifier(level_setup_hash, filename,
-                                getCookie("LEVELSETUP"));
-
     freeSetupFileHash(level_setup_hash);
   }
   else
-    Error(ERR_WARN, "using default setup values");
+  {
+    Error(ERR_DEBUG, "using default setup values");
+  }
 
   free(filename);
 }
@@ -3815,8 +4031,8 @@ void SaveLevelSetup_SeriesInfo()
     return;
   }
 
-  fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
-                                                getCookie("LEVELSETUP")));
+  fprintFileHeader(file, LEVELSETUP_FILENAME);
+
   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
                                               level_nr_str));
   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,