added function to get next valid tree node
[rocksndiamonds.git] / src / libgame / setup.c
index 90fb933af54d225dfc670d131af85d5fe7595bf7..60ec59f307cc83e9aaf332a86c5b85f16b13e73d 100644 (file)
@@ -43,46 +43,22 @@ static char *levelclass_desc[NUM_LEVELCLASS_DESC] =
   "DX Boulderdash"
 };
 
-#define LEVELCOLOR(n)  (IS_LEVELCLASS_TUTORIAL(n) ?            FC_BLUE :    \
-                        IS_LEVELCLASS_CLASSICS(n) ?            FC_RED :     \
-                        IS_LEVELCLASS_BD(n) ?                  FC_GREEN :   \
-                        IS_LEVELCLASS_EM(n) ?                  FC_GREEN :   \
-                        IS_LEVELCLASS_SP(n) ?                  FC_GREEN :   \
-                        IS_LEVELCLASS_DX(n) ?                  FC_GREEN :   \
-                        IS_LEVELCLASS_SB(n) ?                  FC_GREEN :   \
-                        IS_LEVELCLASS_CONTRIB(n) ?             FC_GREEN :   \
-                        IS_LEVELCLASS_PRIVATE(n) ?             FC_RED :     \
-                        FC_BLUE)
-
-#define LEVELSORTING(n)        (IS_LEVELCLASS_TUTORIAL(n) ?            0 :     \
-                        IS_LEVELCLASS_CLASSICS(n) ?            1 :     \
-                        IS_LEVELCLASS_BD(n) ?                  2 :     \
-                        IS_LEVELCLASS_EM(n) ?                  3 :     \
-                        IS_LEVELCLASS_SP(n) ?                  4 :     \
-                        IS_LEVELCLASS_DX(n) ?                  5 :     \
-                        IS_LEVELCLASS_SB(n) ?                  6 :     \
-                        IS_LEVELCLASS_CONTRIB(n) ?             7 :     \
-                        IS_LEVELCLASS_PRIVATE(n) ?             8 :     \
-                        9)
-
-#define ARTWORKCOLOR(n)        (IS_ARTWORKCLASS_CLASSICS(n) ?          FC_RED :     \
-                        IS_ARTWORKCLASS_CONTRIB(n) ?           FC_GREEN :   \
-                        IS_ARTWORKCLASS_PRIVATE(n) ?           FC_RED :     \
-                        IS_ARTWORKCLASS_LEVEL(n) ?             FC_GREEN :   \
-                        FC_BLUE)
-
-#define ARTWORKSORTING(n) (IS_ARTWORKCLASS_CLASSICS(n) ?       0 :     \
-                          IS_ARTWORKCLASS_LEVEL(n) ?           1 :     \
-                          IS_ARTWORKCLASS_CONTRIB(n) ?         2 :     \
-                          IS_ARTWORKCLASS_PRIVATE(n) ?         3 :     \
-                          9)
-
 #define TOKEN_VALUE_POSITION_SHORT             32
 #define TOKEN_VALUE_POSITION_DEFAULT           40
 #define TOKEN_COMMENT_POSITION_DEFAULT         60
 
 #define MAX_COOKIE_LEN                         256
 
+#define TREE_NODE_TYPE_DEFAULT                 0
+#define TREE_NODE_TYPE_PARENT                  1
+#define TREE_NODE_TYPE_GROUP                   2
+#define TREE_NODE_TYPE_COPY                    3
+
+#define TREE_NODE_TYPE(ti)     (ti->node_group  ? TREE_NODE_TYPE_GROUP  : \
+                                ti->parent_link ? TREE_NODE_TYPE_PARENT : \
+                                ti->is_copy     ? TREE_NODE_TYPE_COPY   : \
+                                TREE_NODE_TYPE_DEFAULT)
+
 
 static void setTreeInfoToDefaults(TreeInfo *, int);
 static TreeInfo *getTreeInfoCopy(TreeInfo *ti);
@@ -112,6 +88,16 @@ static char *getLevelClassDescription(TreeInfo *ti)
     return "Unknown Level Class";
 }
 
+static char *getCacheDir(void)
+{
+  static char *cache_dir = NULL;
+
+  if (cache_dir == NULL)
+    cache_dir = getPath2(getMainUserGameDataDir(), CACHE_DIRECTORY);
+
+  return cache_dir;
+}
+
 static char *getScoreDir(char *level_subdir)
 {
   static char *score_dir = NULL;
@@ -119,13 +105,29 @@ static char *getScoreDir(char *level_subdir)
   char *score_subdir = SCORES_DIRECTORY;
 
   if (score_dir == NULL)
+    score_dir = getPath2(getMainUserGameDataDir(), score_subdir);
+
+  if (level_subdir != NULL)
   {
-    if (program.global_scores)
-      score_dir = getPath2(getCommonDataDir(),       score_subdir);
-    else
-      score_dir = getPath2(getMainUserGameDataDir(), score_subdir);
+    checked_free(score_level_dir);
+
+    score_level_dir = getPath2(score_dir, level_subdir);
+
+    return score_level_dir;
   }
 
+  return score_dir;
+}
+
+static char *getScoreCacheDir(char *level_subdir)
+{
+  static char *score_dir = NULL;
+  static char *score_level_dir = NULL;
+  char *score_subdir = SCORES_DIRECTORY;
+
+  if (score_dir == NULL)
+    score_dir = getPath2(getCacheDir(), score_subdir);
+
   if (level_subdir != NULL)
   {
     checked_free(score_level_dir);
@@ -138,6 +140,19 @@ static char *getScoreDir(char *level_subdir)
   return score_dir;
 }
 
+static char *getScoreTapeDir(char *level_subdir, int nr)
+{
+  static char *score_tape_dir = NULL;
+  char tape_subdir[MAX_FILENAME_LEN];
+
+  checked_free(score_tape_dir);
+
+  sprintf(tape_subdir, "%03d", nr);
+  score_tape_dir = getPath2(getScoreDir(level_subdir), tape_subdir);
+
+  return score_tape_dir;
+}
+
 static char *getUserSubdir(int nr)
 {
   static char user_subdir[16] = { 0 };
@@ -180,16 +195,6 @@ static char *getLevelSetupDir(char *level_subdir)
   return levelsetup_dir;
 }
 
-static char *getCacheDir(void)
-{
-  static char *cache_dir = NULL;
-
-  if (cache_dir == NULL)
-    cache_dir = getPath2(getMainUserGameDataDir(), CACHE_DIRECTORY);
-
-  return cache_dir;
-}
-
 static char *getNetworkDir(void)
 {
   static char *network_dir = NULL;
@@ -529,8 +534,8 @@ char *getProgramConfigFilename(char *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 *base_path = getProgramMainDataPath(command_filename, BASE_PATH);
+    char *conf_directory = getPath2(base_path, CONF_DIRECTORY);
 
     char *command_basepath = getBasePath(command_filename);
     char *command_basename = getBaseNameNoSuffix(command_filename);
@@ -540,7 +545,7 @@ char *getProgramConfigFilename(char *command_filename)
     config_filename_2 = getStringCat2(command_filename_2, ".conf");
     config_filename_3 = getPath2(conf_directory, SETUP_FILENAME);
 
-    checked_free(ro_base_path);
+    checked_free(base_path);
     checked_free(conf_directory);
 
     checked_free(command_basepath);
@@ -577,7 +582,20 @@ char *getTapeFilename(int nr)
   return filename;
 }
 
-char *getSolutionTapeFilename(int nr)
+char *getTemporaryTapeFilename(void)
+{
+  static char *filename = NULL;
+  char basename[MAX_FILENAME_LEN];
+
+  checked_free(filename);
+
+  sprintf(basename, "tmp.%s", TAPEFILE_EXTENSION);
+  filename = getPath2(getTapeDir(NULL), basename);
+
+  return filename;
+}
+
+char *getDefaultSolutionTapeFilename(int nr)
 {
   static char *filename = NULL;
   char basename[MAX_FILENAME_LEN];
@@ -587,17 +605,32 @@ char *getSolutionTapeFilename(int nr)
   sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
   filename = getPath2(getSolutionTapeDir(), basename);
 
-  if (!fileExists(filename))
-  {
-    static char *filename_sln = NULL;
+  return filename;
+}
+
+char *getSokobanSolutionTapeFilename(int nr)
+{
+  static char *filename = NULL;
+  char basename[MAX_FILENAME_LEN];
 
-    checked_free(filename_sln);
+  checked_free(filename);
+
+  sprintf(basename, "%03d.sln", nr);
+  filename = getPath2(getSolutionTapeDir(), basename);
 
-    sprintf(basename, "%03d.sln", nr);
-    filename_sln = getPath2(getSolutionTapeDir(), basename);
+  return filename;
+}
 
-    if (fileExists(filename_sln))
-      return filename_sln;
+char *getSolutionTapeFilename(int nr)
+{
+  char *filename = getDefaultSolutionTapeFilename(nr);
+
+  if (!fileExists(filename))
+  {
+    char *filename2 = getSokobanSolutionTapeFilename(nr);
+
+    if (fileExists(filename2))
+      return filename2;
   }
 
   return filename;
@@ -618,6 +651,49 @@ char *getScoreFilename(int nr)
   return filename;
 }
 
+char *getScoreCacheFilename(int nr)
+{
+  static char *filename = NULL;
+  char basename[MAX_FILENAME_LEN];
+
+  checked_free(filename);
+
+  sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
+
+  // used instead of "leveldir_current->subdir" (for network games)
+  filename = getPath2(getScoreCacheDir(levelset.identifier), basename);
+
+  return filename;
+}
+
+char *getScoreTapeBasename(char *name)
+{
+  static char basename[MAX_FILENAME_LEN];
+  char basename_raw[MAX_FILENAME_LEN];
+  char timestamp[20];
+
+  sprintf(timestamp, "%s", getCurrentTimestamp());
+  sprintf(basename_raw, "%s-%s", timestamp, name);
+  sprintf(basename, "%s-%08x", timestamp, get_hash_from_key(basename_raw));
+
+  return basename;
+}
+
+char *getScoreTapeFilename(char *basename_no_ext, int nr)
+{
+  static char *filename = NULL;
+  char basename[MAX_FILENAME_LEN];
+
+  checked_free(filename);
+
+  sprintf(basename, "%s.%s", basename_no_ext, TAPEFILE_EXTENSION);
+
+  // used instead of "leveldir_current->subdir" (for network games)
+  filename = getPath2(getScoreTapeDir(levelset.identifier, nr), basename);
+
+  return filename;
+}
+
 char *getSetupFilename(void)
 {
   static char *filename = NULL;
@@ -1088,15 +1164,24 @@ void InitTapeDirectory(char *level_subdir)
 
 void InitScoreDirectory(char *level_subdir)
 {
-  int permissions = (program.global_scores ? PERMS_PUBLIC : PERMS_PRIVATE);
+  createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
+  createDirectory(getScoreDir(NULL), "main score", PERMS_PRIVATE);
+  createDirectory(getScoreDir(level_subdir), "level score", PERMS_PRIVATE);
+}
 
-  if (program.global_scores)
-    createDirectory(getCommonDataDir(), "common data", permissions);
-  else
-    createDirectory(getMainUserGameDataDir(), "main user data", permissions);
+void InitScoreCacheDirectory(char *level_subdir)
+{
+  createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
+  createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
+  createDirectory(getScoreCacheDir(NULL), "main score", PERMS_PRIVATE);
+  createDirectory(getScoreCacheDir(level_subdir), "level score", PERMS_PRIVATE);
+}
+
+void InitScoreTapeDirectory(char *level_subdir, int nr)
+{
+  InitScoreDirectory(level_subdir);
 
-  createDirectory(getScoreDir(NULL), "main score", permissions);
-  createDirectory(getScoreDir(level_subdir), "level score", permissions);
+  createDirectory(getScoreTapeDir(level_subdir, nr), "score tape", PERMS_PRIVATE);
 }
 
 static void SaveUserLevelInfo(void);
@@ -1188,25 +1273,55 @@ int numTreeInfo(TreeInfo *node)
 
 boolean validLevelSeries(TreeInfo *node)
 {
-  return (node != NULL && !node->node_group && !node->parent_link);
+  // in a number of cases, tree node is no valid level set
+  if (node == NULL || node->node_group || node->parent_link || node->is_copy)
+    return FALSE;
+
+  return TRUE;
 }
 
-TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
+TreeInfo *getValidLevelSeries(TreeInfo *node, TreeInfo *default_node)
+{
+  if (validLevelSeries(node))
+    return node;
+  else if (node->is_copy)
+    return getTreeInfoFromIdentifier(leveldir_first, node->identifier);
+  else
+    return getFirstValidTreeInfoEntry(default_node);
+}
+
+static TreeInfo *getValidTreeInfoEntryExt(TreeInfo *node, boolean get_next_node)
 {
   if (node == NULL)
     return NULL;
 
-  if (node->node_group)                // enter level group (step down into tree)
+  if (node->node_group)                // enter node group (step down into tree)
     return getFirstValidTreeInfoEntry(node->node_group);
-  else if (node->parent_link)  // skip start entry of level group
+
+  if (node->parent_link)       // skip first node (back link) of node group
+    get_next_node = TRUE;
+
+  if (get_next_node)
   {
-    if (node->next)            // get first real level series entry
-      return getFirstValidTreeInfoEntry(node->next);
-    else                       // leave empty level group and go on
-      return getFirstValidTreeInfoEntry(node->node_parent->next);
+    // get next regular tree node, or step up until one is found
+    while (node->next == NULL && node->node_parent != NULL)
+      node = node->node_parent;
+
+    return getFirstValidTreeInfoEntry(node->next);
   }
-  else                         // this seems to be a regular level series
-    return node;
+
+  // this is a regular tree node
+  return node;
+}
+
+TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
+{
+  return getValidTreeInfoEntryExt(node, FALSE);
+}
+
+TreeInfo *getNextValidTreeInfoEntry(TreeInfo *node)
+{
+  return getValidTreeInfoEntryExt(node, TRUE);
 }
 
 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
@@ -1260,29 +1375,25 @@ TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
 }
 
 static TreeInfo *getTreeInfoFromIdentifierExt(TreeInfo *node, char *identifier,
-                                             boolean include_node_groups)
+                                             int node_type_wanted)
 {
   if (identifier == NULL)
     return NULL;
 
   while (node)
   {
+    if (TREE_NODE_TYPE(node) == node_type_wanted &&
+       strEqual(identifier, node->identifier))
+      return node;
+
     if (node->node_group)
     {
-      if (include_node_groups && strEqual(identifier, node->identifier))
-       return node;
-
       TreeInfo *node_group = getTreeInfoFromIdentifierExt(node->node_group,
                                                          identifier,
-                                                         include_node_groups);
+                                                         node_type_wanted);
       if (node_group)
        return node_group;
     }
-    else if (!node->parent_link)
-    {
-      if (strEqual(identifier, node->identifier))
-       return node;
-    }
 
     node = node->next;
   }
@@ -1292,7 +1403,7 @@ static TreeInfo *getTreeInfoFromIdentifierExt(TreeInfo *node, char *identifier,
 
 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
 {
-  return getTreeInfoFromIdentifierExt(node, identifier, FALSE);
+  return getTreeInfoFromIdentifierExt(node, identifier, TREE_NODE_TYPE_DEFAULT);
 }
 
 static TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
@@ -1396,9 +1507,10 @@ static boolean adjustTreeSoundsForEMC(TreeInfo *node)
   return settings_changed;
 }
 
-void dumpTreeInfo(TreeInfo *node, int depth)
+int dumpTreeInfo(TreeInfo *node, int depth)
 {
   char bullet_list[] = { '-', '*', 'o' };
+  int num_leaf_nodes = 0;
   int i;
 
   if (depth == 0)
@@ -1416,6 +1528,9 @@ void dumpTreeInfo(TreeInfo *node, int depth)
                   (node->node_parent ? node->node_parent->identifier : "-"),
                   (node->node_group ? "[GROUP]" : ""));
 
+    if (!node->node_group && !node->parent_link)
+      num_leaf_nodes++;
+
     /*
     // use for dumping artwork info tree
     Debug("tree", "subdir == '%s' ['%s', '%s'] [%d])",
@@ -1423,10 +1538,15 @@ void dumpTreeInfo(TreeInfo *node, int depth)
     */
 
     if (node->node_group != NULL)
-      dumpTreeInfo(node->node_group, depth + 1);
+      num_leaf_nodes += dumpTreeInfo(node->node_group, depth + 1);
 
     node = node->next;
   }
+
+  if (depth == 0)
+    Debug("tree", "Summary: %d leaf nodes found", num_leaf_nodes);
+
+  return num_leaf_nodes;
 }
 
 void sortTreeInfoBySortFunction(TreeInfo **node_first,
@@ -1545,6 +1665,8 @@ char *getHomeDir(void)
     if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
       strcpy(dir, ".");
   }
+#elif defined(PLATFORM_EMSCRIPTEN)
+  dir = "/persistent";
 #elif defined(PLATFORM_UNIX)
   if (dir == NULL)
   {
@@ -1565,29 +1687,6 @@ char *getHomeDir(void)
   return dir;
 }
 
-char *getCommonDataDir(void)
-{
-  static char *common_data_dir = NULL;
-
-#if defined(PLATFORM_WIN32)
-  if (common_data_dir == NULL)
-  {
-    char *dir = checked_malloc(MAX_PATH + 1);
-
-    if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
-       && !strEqual(dir, ""))          // empty for Windows 95/98
-      common_data_dir = getPath2(dir, program.userdata_subdir);
-    else
-      common_data_dir = options.rw_base_directory;
-  }
-#else
-  if (common_data_dir == NULL)
-    common_data_dir = options.rw_base_directory;
-#endif
-
-  return common_data_dir;
-}
-
 char *getPersonalDataDir(void)
 {
   static char *personal_data_dir = NULL;
@@ -2364,6 +2463,8 @@ static void saveSetupFileHash(SetupFileHash *hash, char *filename)
     return;
   }
 
+  fprintf(file, "%s\n\n", getFormattedSetupEntry("program.version",
+                                                program.version_string));
   for (i = 0; i < item_count; i++)
     fprintf(file, "%s\n", getFormattedSetupEntry(sort_array[i].token,
                                                 sort_array[i].value));
@@ -2503,7 +2604,6 @@ static struct TokenInfo artworkinfo_tokens[] =
   { 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                    },
@@ -2551,6 +2651,7 @@ static void setTreeInfoToDefaults(TreeInfo *ti, int type)
   ti->sort_priority = LEVELCLASS_UNDEFINED;    // default: least priority
   ti->latest_engine = FALSE;                   // default: get from level
   ti->parent_link = FALSE;
+  ti->is_copy = FALSE;
   ti->in_user_dir = FALSE;
   ti->user_defined = FALSE;
   ti->color = 0;
@@ -2632,6 +2733,7 @@ static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
   ti->sort_priority = parent->sort_priority;
   ti->latest_engine = parent->latest_engine;
   ti->parent_link = FALSE;
+  ti->is_copy = FALSE;
   ti->in_user_dir = parent->in_user_dir;
   ti->user_defined = parent->user_defined;
   ti->color = parent->color;
@@ -2732,6 +2834,7 @@ static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
 
   ti_copy->level_group         = ti->level_group;
   ti_copy->parent_link         = ti->parent_link;
+  ti_copy->is_copy             = ti->is_copy;
   ti_copy->in_user_dir         = ti->in_user_dir;
   ti_copy->user_defined                = ti->user_defined;
   ti_copy->readonly            = ti->readonly;
@@ -2887,7 +2990,7 @@ static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
   setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
   setString(&ti_new->fullpath, node_parent->fullpath);
 
-  ti_new->sort_priority = node_parent->sort_priority;
+  ti_new->sort_priority = LEVELCLASS_PARENT;
   ti_new->latest_engine = node_parent->latest_engine;
 
   setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
@@ -2910,14 +3013,14 @@ static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
   ti_new->node_parent = NULL;
   ti_new->parent_link = FALSE;
 
-  setString(&ti_new->identifier, node_first->identifier);
+  setString(&ti_new->identifier, "top_tree_node");
   setString(&ti_new->name, TREE_INFOTEXT(type));
   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->sort_priority = LEVELCLASS_TOP;
   ti_new->latest_engine = node_first->latest_engine;
 
   setString(&ti_new->class_desc, TREE_INFOTEXT(type));
@@ -2962,6 +3065,17 @@ static void LoadArtworkInfoCache(void)
     // try to load artwork info hash from already existing cache file
     artworkinfo_cache_old = loadSetupFileHash(filename);
 
+    // try to get program version that artwork info cache was written with
+    char *version = getHashEntry(artworkinfo_cache_old, "program.version");
+
+    // check program version of artwork info cache against current version
+    if (!strEqual(version, program.version_string))
+    {
+      freeSetupFileHash(artworkinfo_cache_old);
+
+      artworkinfo_cache_old = NULL;
+    }
+
     // if no artwork info cache file was found, start with empty hash
     if (artworkinfo_cache_old == NULL)
       artworkinfo_cache_old = newSetupFileHash();
@@ -3481,8 +3595,6 @@ static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
   leveldir_new->user_defined =
     (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
 
-  leveldir_new->color = LEVELCOLOR(leveldir_new);
-
   setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
 
   leveldir_new->handicap_level =       // set handicap to default value
@@ -3726,9 +3838,6 @@ static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
   artwork_new->in_user_dir =
     (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
 
-  // (may use ".sort_priority" from "setup_file_hash" above)
-  artwork_new->color = ARTWORKCOLOR(artwork_new);
-
   setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
 
   if (setup_file_hash == NULL) // (after determining ".user_defined")
@@ -3746,9 +3855,6 @@ static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
        artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
       }
 
-      // set to new values after changing ".sort_priority"
-      artwork_new->color = ARTWORKCOLOR(artwork_new);
-
       setString(&artwork_new->class_desc,
                getLevelClassDescription(artwork_new));
     }
@@ -4007,7 +4113,6 @@ static void LoadArtworkInfoFromLevelInfoExt(ArtworkDirTree **artwork_node,
 
          artwork_new->sort_priority = level_node->sort_priority;
          artwork_new->in_user_dir = level_node->in_user_dir;
-         artwork_new->color = LEVELCOLOR(artwork_new);
 
          update_artworkinfo_cache = TRUE;
        }
@@ -4537,9 +4642,11 @@ static void InitLastPlayedLevels_ParentNode(void)
   setTreeInfoToDefaultsFromParent(leveldir_new, leveldir_first);
 
   leveldir_new->level_group = TRUE;
+  leveldir_new->sort_priority = LEVELCLASS_LAST_PLAYED_LEVEL;
 
   setString(&leveldir_new->identifier, TOKEN_STR_LAST_LEVEL_SERIES);
   setString(&leveldir_new->name, "<< (last played level sets)");
+  setString(&leveldir_new->name_sorting, leveldir_new->name);
 
   pushTreeInfo(leveldir_top, leveldir_new);
 
@@ -4550,7 +4657,6 @@ static void InitLastPlayedLevels_ParentNode(void)
 void UpdateLastPlayedLevels_TreeInfo(void)
 {
   char **last_level_series = setup.level_setup.last_level_series;
-  boolean reset_leveldir_current = FALSE;
   LevelDirTree *leveldir_last;
   TreeInfo **node_new = NULL;
   int i;
@@ -4560,13 +4666,9 @@ void UpdateLastPlayedLevels_TreeInfo(void)
 
   InitLastPlayedLevels_ParentNode();
 
-  // check if current level set is from "last played" sub-tree to be rebuilt
-  reset_leveldir_current = strEqual(leveldir_current->node_parent->identifier,
-                                   TOKEN_STR_LAST_LEVEL_SERIES);
-
   leveldir_last = getTreeInfoFromIdentifierExt(leveldir_first,
                                               TOKEN_STR_LAST_LEVEL_SERIES,
-                                              TRUE);
+                                              TREE_NODE_TYPE_GROUP);
   if (leveldir_last == NULL)
     return;
 
@@ -4574,16 +4676,22 @@ void UpdateLastPlayedLevels_TreeInfo(void)
 
   freeTreeInfo(*node_new);
 
+  *node_new = NULL;
+
   for (i = 0; last_level_series[i] != NULL; i++)
   {
     LevelDirTree *node_last = getTreeInfoFromIdentifier(leveldir_first,
                                                        last_level_series[i]);
+    if (node_last == NULL)
+      continue;
 
     *node_new = getTreeInfoCopy(node_last);    // copy complete node
 
     (*node_new)->node_top = &leveldir_first;   // correct top node link
     (*node_new)->node_parent = leveldir_last;  // correct parent node link
 
+    (*node_new)->is_copy = TRUE;               // mark entry as node copy
+
     (*node_new)->node_group = NULL;
     (*node_new)->next = NULL;
 
@@ -4591,10 +4699,6 @@ void UpdateLastPlayedLevels_TreeInfo(void)
 
     node_new = &((*node_new)->next);
   }
-
-  if (reset_leveldir_current)
-    leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
-                                                 last_level_series[0]);
 }
 
 static void UpdateLastPlayedLevels_List(void)
@@ -4616,6 +4720,35 @@ static void UpdateLastPlayedLevels_List(void)
   setString(&last_level_series[0], leveldir_current->identifier);
 }
 
+static TreeInfo *StoreOrRestoreLastPlayedLevels(TreeInfo *node, boolean store)
+{
+  static char *identifier = NULL;
+
+  if (store)
+  {
+    setString(&identifier, (node && node->is_copy ? node->identifier : NULL));
+
+    return NULL;       // not used
+  }
+  else
+  {
+    TreeInfo *node_new = getTreeInfoFromIdentifierExt(leveldir_first,
+                                                     identifier,
+                                                     TREE_NODE_TYPE_COPY);
+    return (node_new != NULL ? node_new : node);
+  }
+}
+
+void StoreLastPlayedLevels(TreeInfo *node)
+{
+  StoreOrRestoreLastPlayedLevels(node, TRUE);
+}
+
+void RestoreLastPlayedLevels(TreeInfo **node)
+{
+  *node = StoreOrRestoreLastPlayedLevels(*node, FALSE);
+}
+
 void LoadLevelSetup_LastSeries(void)
 {
   // --------------------------------------------------------------------------