added optional button to restart game (door, panel and touch variants)
[rocksndiamonds.git] / src / files.c
index 49813391c63e0e7a733a23447cb5e09862ee8eb2..097deead25dda3078dc12860702c540e87cfd2ff 100644 (file)
@@ -22,6 +22,7 @@
 #include "tools.h"
 #include "tape.h"
 #include "config.h"
+#include "api.h"
 
 #define ENABLE_UNUSED_CODE     0       // currently unused functions
 #define ENABLE_HISTORIC_CHUNKS 0       // only for historic reference
@@ -51,6 +52,7 @@
 
 // (element number only)
 #define LEVEL_CHUNK_GRPX_UNCHANGED     2
+#define LEVEL_CHUNK_EMPX_UNCHANGED     2
 #define LEVEL_CHUNK_NOTE_UNCHANGED     2
 
 // (nothing at all if unchanged)
@@ -58,7 +60,9 @@
 
 #define TAPE_CHUNK_VERS_SIZE   8       // size of file version chunk
 #define TAPE_CHUNK_HEAD_SIZE   20      // size of tape file header
-#define TAPE_CHUNK_HEAD_UNUSED 1       // unused tape header bytes
+#define TAPE_CHUNK_SCRN_SIZE   2       // size of screen size chunk
+
+#define SCORE_CHUNK_VERS_SIZE  8       // size of file version chunk
 
 #define LEVEL_CHUNK_CNT3_SIZE(x)        (LEVEL_CHUNK_CNT3_HEADER + (x))
 #define LEVEL_CHUNK_CUS3_SIZE(x)        (2 + (x) * LEVEL_CPART_CUS3_SIZE)
@@ -67,7 +71,7 @@
 // file identifier strings
 #define LEVEL_COOKIE_TMPL              "ROCKSNDIAMONDS_LEVEL_FILE_VERSION_x.x"
 #define TAPE_COOKIE_TMPL               "ROCKSNDIAMONDS_TAPE_FILE_VERSION_x.x"
-#define SCORE_COOKIE                   "ROCKSNDIAMONDS_SCORE_FILE_VERSION_1.2"
+#define SCORE_COOKIE_TMPL              "ROCKSNDIAMONDS_SCORE_FILE_VERSION_x.x"
 
 // values for deciding when (not) to save configuration data
 #define SAVE_CONF_NEVER                        0
                                         CONF_CONTENT_NUM_BYTES : 1)
 
 #define CONF_ELEMENT_BYTE_POS(i)       ((i) * CONF_ELEMENT_NUM_BYTES)
-#define CONF_ELEMENTS_ELEMENT(b,i)     ((b[CONF_ELEMENT_BYTE_POS(i)] << 8) |  \
+#define CONF_ELEMENTS_ELEMENT(b, i)    ((b[CONF_ELEMENT_BYTE_POS(i)] << 8) | \
                                        (b[CONF_ELEMENT_BYTE_POS(i) + 1]))
 
 #define CONF_CONTENT_ELEMENT_POS(c,x,y)        ((c) * CONF_CONTENT_NUM_ELEMENTS +    \
@@ -257,6 +261,18 @@ static struct LevelFileConfigInfo chunk_config_INFO[] =
     &li.solved_by_one_player,          FALSE
   },
 
+  {
+    -1,                                        -1,
+    TYPE_INTEGER,                      CONF_VALUE_8_BIT(12),
+    &li.time_score_base,               1
+  },
+
+  {
+    -1,                                        -1,
+    TYPE_BOOLEAN,                      CONF_VALUE_8_BIT(13),
+    &li.rate_time_over_score,          FALSE
+  },
+
   {
     -1,                                        -1,
     -1,                                        -1,
@@ -307,6 +323,16 @@ static struct LevelFileConfigInfo chunk_config_ELEM[] =
     TYPE_BOOLEAN,                      CONF_VALUE_8_BIT(15),
     &li.lazy_relocation,               FALSE
   },
+  {
+    EL_PLAYER_1,                       -1,
+    TYPE_BOOLEAN,                      CONF_VALUE_8_BIT(16),
+    &li.finish_dig_collect,            TRUE
+  },
+  {
+    EL_PLAYER_1,                       -1,
+    TYPE_BOOLEAN,                      CONF_VALUE_8_BIT(17),
+    &li.keep_walkable_ce,              FALSE
+  },
 
   // (these values are different for each player)
   {
@@ -878,11 +904,34 @@ static struct LevelFileConfigInfo chunk_config_ELEM[] =
     TYPE_INTEGER,                      CONF_VALUE_16_BIT(1),
     &li.mm_time_bomb,                  75
   },
+
   {
     EL_MM_GRAY_BALL,                   -1,
     TYPE_INTEGER,                      CONF_VALUE_16_BIT(1),
     &li.mm_time_ball,                  75
   },
+  {
+    EL_MM_GRAY_BALL,                   -1,
+    TYPE_INTEGER,                      CONF_VALUE_8_BIT(1),
+    &li.mm_ball_choice_mode,           ANIM_RANDOM
+  },
+  {
+    EL_MM_GRAY_BALL,                   -1,
+    TYPE_ELEMENT_LIST,                 CONF_VALUE_BYTES(1),
+    &li.mm_ball_content,               EL_EMPTY, NULL,
+    &li.num_mm_ball_contents,          8, MAX_MM_BALL_CONTENTS
+  },
+  {
+    EL_MM_GRAY_BALL,                   -1,
+    TYPE_BOOLEAN,                      CONF_VALUE_8_BIT(3),
+    &li.rotate_mm_ball_content,                TRUE
+  },
+  {
+    EL_MM_GRAY_BALL,                   -1,
+    TYPE_BOOLEAN,                      CONF_VALUE_8_BIT(2),
+    &li.explode_mm_ball,               FALSE
+  },
+
   {
     EL_MM_STEEL_BLOCK,                 -1,
     TYPE_INTEGER,                      CONF_VALUE_16_BIT(1),
@@ -1063,6 +1112,18 @@ static struct LevelFileConfigInfo chunk_config_CUSX_base[] =
     &xx_ei.move_delay_random,          0,
     &yy_ei.move_delay_random
   },
+  {
+    -1,                                        -1,
+    TYPE_INTEGER,                      CONF_VALUE_16_BIT(16),
+    &xx_ei.step_delay_fixed,           0,
+    &yy_ei.step_delay_fixed
+  },
+  {
+    -1,                                        -1,
+    TYPE_INTEGER,                      CONF_VALUE_16_BIT(17),
+    &xx_ei.step_delay_random,          0,
+    &yy_ei.step_delay_random
+  },
 
   {
     -1,                                        -1,
@@ -1339,6 +1400,26 @@ static struct LevelFileConfigInfo chunk_config_GRPX[] =
   }
 };
 
+static struct LevelFileConfigInfo chunk_config_EMPX[] =
+{
+  {
+    -1,                                        -1,
+    TYPE_BOOLEAN,                      CONF_VALUE_8_BIT(1),
+    &xx_ei.use_gfx_element,            FALSE
+  },
+  {
+    -1,                                        -1,
+    TYPE_ELEMENT,                      CONF_VALUE_16_BIT(1),
+    &xx_ei.gfx_element_initial,                EL_EMPTY_SPACE
+  },
+
+  {
+    -1,                                        -1,
+    -1,                                        -1,
+    NULL,                              -1
+  }
+};
+
 static struct LevelFileConfigInfo chunk_config_CONF[] =                // (OBSOLETE)
 {
   {
@@ -1783,6 +1864,16 @@ static void setLevelInfoToDefaults_Elements(struct LevelInfo *level)
     int element = i;
     struct ElementInfo *ei = &element_info[element];
 
+    if (element == EL_MM_GRAY_BALL)
+    {
+      struct LevelInfo_MM *level_mm = level->native_mm_level;
+      int j;
+
+      for (j = 0; j < level->num_mm_ball_contents; j++)
+       level->mm_ball_content[j] =
+         map_element_MM_to_RND(level_mm->ball_content[j]);
+    }
+
     // never initialize clipboard elements after the very first time
     // (to be able to use clipboard elements between several levels)
     if (IS_CLIPBOARD_ELEMENT(element) && clipboard_elements_initialized)
@@ -1812,8 +1903,7 @@ static void setLevelInfoToDefaults_Elements(struct LevelInfo *level)
     setElementChangeInfoToDefaults(ei->change);
 
     if (IS_CUSTOM_ELEMENT(element) ||
-       IS_GROUP_ELEMENT(element) ||
-       IS_INTERNAL_ELEMENT(element))
+       IS_GROUP_ELEMENT(element))
     {
       setElementDescriptionToDefault(ei);
 
@@ -1856,6 +1946,16 @@ static void setLevelInfoToDefaults_Elements(struct LevelInfo *level)
 
       *group = xx_group;
     }
+
+    if (IS_EMPTY_ELEMENT(element) ||
+       IS_INTERNAL_ELEMENT(element))
+    {
+      xx_ei = *ei;             // copy element data into temporary buffer
+
+      setConfigToDefaultsFromConfigList(chunk_config_EMPX);
+
+      *ei = xx_ei;
+    }
   }
 
   clipboard_elements_initialized = TRUE;
@@ -2070,7 +2170,7 @@ static char *getPackedLevelBasename(int type)
 
   if ((dir = openDirectory(directory)) == NULL)
   {
-    Error(ERR_WARN, "cannot read current level directory '%s'", directory);
+    Warn("cannot read current level directory '%s'", directory);
 
     return basename;
   }
@@ -2334,7 +2434,7 @@ static void copyLevelFileInfo(struct LevelFileInfo *lfi_from,
 // functions for loading R'n'D level
 // ----------------------------------------------------------------------------
 
-static int getMappedElement(int element)
+int getMappedElement(int element)
 {
   // remap some (historic, now obsolete) elements
 
@@ -2375,7 +2475,7 @@ static int getMappedElement(int element)
     default:
       if (element >= NUM_FILE_ELEMENTS)
       {
-       Error(ERR_WARN, "invalid level element %d", element);
+       Warn("invalid level element %d", element);
 
        element = EL_UNKNOWN;
       }
@@ -2623,7 +2723,7 @@ static int LoadLevel_CNT2(File *file, int chunk_size, struct LevelInfo *level)
   }
   else
   {
-    Error(ERR_WARN, "cannot load content for element '%d'", element);
+    Warn("cannot load content for element '%d'", element);
   }
 
   return chunk_size;
@@ -2683,7 +2783,7 @@ static int LoadLevel_CUS1(File *file, int chunk_size, struct LevelInfo *level)
     if (IS_CUSTOM_ELEMENT(element))
       element_info[element].properties[EP_BITFIELD_BASE_NR] = properties;
     else
-      Error(ERR_WARN, "invalid custom element number %d", element);
+      Warn("invalid custom element number %d", element);
 
     // older game versions that wrote level files with CUS1 chunks used
     // different default push delay values (not yet stored in level file)
@@ -2716,7 +2816,7 @@ static int LoadLevel_CUS2(File *file, int chunk_size, struct LevelInfo *level)
     if (IS_CUSTOM_ELEMENT(element))
       element_info[element].change->target_element = custom_target_element;
     else
-      Error(ERR_WARN, "invalid custom element number %d", element);
+      Warn("invalid custom element number %d", element);
   }
 
   level->file_has_custom_elements = TRUE;
@@ -2744,7 +2844,7 @@ static int LoadLevel_CUS3(File *file, int chunk_size, struct LevelInfo *level)
 
     if (!IS_CUSTOM_ELEMENT(element))
     {
-      Error(ERR_WARN, "invalid custom element number %d", element);
+      Warn("invalid custom element number %d", element);
 
       element = EL_INTERNAL_DUMMY;
     }
@@ -2777,9 +2877,10 @@ static int LoadLevel_CUS3(File *file, int chunk_size, struct LevelInfo *level)
       for (x = 0; x < 3; x++)
        ei->content.e[x][y] = getMappedElement(getFile16BitBE(file));
 
+    // bits 0 - 31 of "has_event[]"
     event_bits = getFile32BitBE(file);
-    for (j = 0; j < NUM_CHANGE_EVENTS; j++)
-      if (event_bits & (1 << j))
+    for (j = 0; j < MIN(NUM_CHANGE_EVENTS, 32); j++)
+      if (event_bits & (1u << j))
        ei->change->has_event[j] = TRUE;
 
     ei->change->target_element = getMappedElement(getFile16BitBE(file));
@@ -2830,9 +2931,10 @@ static int LoadLevel_CUS4(File *file, int chunk_size, struct LevelInfo *level)
 
   if (!IS_CUSTOM_ELEMENT(element))
   {
-    Error(ERR_WARN, "invalid custom element number %d", element);
+    Warn("invalid custom element number %d", element);
 
     ReadUnusedBytesFromFile(file, chunk_size - 2);
+
     return chunk_size;
   }
 
@@ -2914,7 +3016,7 @@ static int LoadLevel_CUS4(File *file, int chunk_size, struct LevelInfo *level)
     // bits 0 - 31 of "has_event[]" ...
     event_bits = getFile32BitBE(file);
     for (j = 0; j < MIN(NUM_CHANGE_EVENTS, 32); j++)
-      if (event_bits & (1 << j))
+      if (event_bits & (1u << j))
        change->has_event[j] = TRUE;
 
     change->target_element = getMappedElement(getFile16BitBE(file));
@@ -2955,7 +3057,7 @@ static int LoadLevel_CUS4(File *file, int chunk_size, struct LevelInfo *level)
     // ... bits 32 - 39 of "has_event[]" (not nice, but downward compatible)
     event_bits = getFile8Bit(file);
     for (j = 32; j < NUM_CHANGE_EVENTS; j++)
-      if (event_bits & (1 << (j - 32)))
+      if (event_bits & (1u << (j - 32)))
        change->has_event[j] = TRUE;
   }
 
@@ -2978,9 +3080,10 @@ static int LoadLevel_GRP1(File *file, int chunk_size, struct LevelInfo *level)
 
   if (!IS_GROUP_ELEMENT(element))
   {
-    Error(ERR_WARN, "invalid group element number %d", element);
+    Warn("invalid group element number %d", element);
 
     ReadUnusedBytesFromFile(file, chunk_size - 2);
+
     return chunk_size;
   }
 
@@ -3042,9 +3145,8 @@ static int LoadLevel_MicroChunk(File *file, struct LevelFileConfigInfo *conf,
 
        if (num_entities > max_num_entities)
        {
-         Error(ERR_WARN,
-               "truncating number of entities for element %d from %d to %d",
-               element, num_entities, max_num_entities);
+         Warn("truncating number of entities for element %d from %d to %d",
+              element, num_entities, max_num_entities);
 
          num_entities = max_num_entities;
        }
@@ -3053,8 +3155,7 @@ static int LoadLevel_MicroChunk(File *file, struct LevelFileConfigInfo *conf,
                                  data_type == TYPE_CONTENT_LIST))
        {
          // for element and content lists, zero entities are not allowed
-         Error(ERR_WARN, "found empty list of entities for element %d",
-               element);
+         Warn("found empty list of entities for element %d", element);
 
          // do not set "num_entities" here to prevent reading behind buffer
 
@@ -3123,7 +3224,7 @@ static int LoadLevel_MicroChunk(File *file, struct LevelFileConfigInfo *conf,
          value = getMappedElement(value);
 
        if (data_type == TYPE_BOOLEAN)
-         *(boolean *)(conf[i].value) = value;
+         *(boolean *)(conf[i].value) = (value ? TRUE : FALSE);
        else
          *(int *)    (conf[i].value) = value;
 
@@ -3145,9 +3246,9 @@ static int LoadLevel_MicroChunk(File *file, struct LevelFileConfigInfo *conf,
     int error_conf_chunk_token = conf_type & CONF_MASK_TOKEN;
     int error_element = real_element;
 
-    Error(ERR_WARN, "cannot load micro chunk '%s(%d)' value for element %d ['%s']",
-         error_conf_chunk_bytes, error_conf_chunk_token,
-         error_element, EL_NAME(error_element));
+    Warn("cannot load micro chunk '%s(%d)' value for element %d ['%s']",
+        error_conf_chunk_bytes, error_conf_chunk_token,
+        error_element, EL_NAME(error_element));
   }
 
   return micro_chunk_size;
@@ -3264,8 +3365,8 @@ static int LoadLevel_CUSX(File *file, int chunk_size, struct LevelInfo *level)
 
   if (ei->num_change_pages == -1)
   {
-    Error(ERR_WARN, "LoadLevel_CUSX(): missing 'num_change_pages' for '%s'",
-         EL_NAME(element));
+    Warn("LoadLevel_CUSX(): missing 'num_change_pages' for '%s'",
+        EL_NAME(element));
 
     ei->num_change_pages = 1;
 
@@ -3285,6 +3386,10 @@ static int LoadLevel_CUSX(File *file, int chunk_size, struct LevelInfo *level)
 
   while (!checkEndOfFile(file))
   {
+    // level file might contain invalid change page number
+    if (xx_current_change_page >= ei->num_change_pages)
+      break;
+
     struct ElementChangeInfo *change = &ei->change_page[xx_current_change_page];
 
     xx_change = *change;       // copy change data into temporary buffer
@@ -3314,6 +3419,9 @@ static int LoadLevel_GRPX(File *file, int chunk_size, struct LevelInfo *level)
   struct ElementInfo *ei = &element_info[element];
   struct ElementGroupInfo *group = ei->group;
 
+  if (group == NULL)
+    return -1;
+
   xx_ei = *ei;         // copy element data into temporary buffer
   xx_group = *group;   // copy group data into temporary buffer
 
@@ -3334,6 +3442,30 @@ static int LoadLevel_GRPX(File *file, int chunk_size, struct LevelInfo *level)
   return real_chunk_size;
 }
 
+static int LoadLevel_EMPX(File *file, int chunk_size, struct LevelInfo *level)
+{
+  int element = getMappedElement(getFile16BitBE(file));
+  int real_chunk_size = 2;
+  struct ElementInfo *ei = &element_info[element];
+
+  xx_ei = *ei;         // copy element data into temporary buffer
+
+  while (!checkEndOfFile(file))
+  {
+    real_chunk_size += LoadLevel_MicroChunk(file, chunk_config_EMPX,
+                                           -1, element);
+
+    if (real_chunk_size >= chunk_size)
+      break;
+  }
+
+  *ei = xx_ei;
+
+  level->file_has_custom_elements = TRUE;
+
+  return real_chunk_size;
+}
+
 static void LoadLevelFromFileInfo_RND(struct LevelInfo *level,
                                      struct LevelFileInfo *level_file_info,
                                      boolean level_info_only)
@@ -3352,7 +3484,7 @@ static void LoadLevelFromFileInfo_RND(struct LevelInfo *level,
     if (level_info_only)
       return;
 
-    Error(ERR_WARN, "cannot read level '%s' -- using empty level", filename);
+    Warn("cannot read level '%s' -- using empty level", filename);
 
     if (!setup.editor.use_template_for_new_levels)
       return;
@@ -3379,7 +3511,7 @@ static void LoadLevelFromFileInfo_RND(struct LevelInfo *level,
     {
       level->no_valid_file = TRUE;
 
-      Error(ERR_WARN, "unknown format of level file '%s'", filename);
+      Warn("unknown format of level file '%s'", filename);
 
       closeFile(file);
 
@@ -3398,7 +3530,7 @@ static void LoadLevelFromFileInfo_RND(struct LevelInfo *level,
     {
       level->no_valid_file = TRUE;
 
-      Error(ERR_WARN, "unknown format of level file '%s'", filename);
+      Warn("unknown format of level file '%s'", filename);
 
       closeFile(file);
 
@@ -3409,7 +3541,7 @@ static void LoadLevelFromFileInfo_RND(struct LevelInfo *level,
     {
       level->no_valid_file = TRUE;
 
-      Error(ERR_WARN, "unsupported version of level file '%s'", filename);
+      Warn("unsupported version of level file '%s'", filename);
 
       closeFile(file);
 
@@ -3456,6 +3588,7 @@ static void LoadLevelFromFileInfo_RND(struct LevelInfo *level,
       { "NOTE", -1,                    LoadLevel_NOTE },
       { "CUSX", -1,                    LoadLevel_CUSX },
       { "GRPX", -1,                    LoadLevel_GRPX },
+      { "EMPX", -1,                    LoadLevel_EMPX },
 
       {  NULL,  0,                     NULL }
     };
@@ -3470,15 +3603,17 @@ static void LoadLevelFromFileInfo_RND(struct LevelInfo *level,
 
       if (chunk_info[i].name == NULL)
       {
-       Error(ERR_WARN, "unknown chunk '%s' in level file '%s'",
-             chunk_name, filename);
+       Warn("unknown chunk '%s' in level file '%s'",
+            chunk_name, filename);
+
        ReadUnusedBytesFromFile(file, chunk_size);
       }
       else if (chunk_info[i].size != -1 &&
               chunk_info[i].size != chunk_size)
       {
-       Error(ERR_WARN, "wrong size (%d) of chunk '%s' in level file '%s'",
-             chunk_size, chunk_name, filename);
+       Warn("wrong size (%d) of chunk '%s' in level file '%s'",
+            chunk_size, chunk_name, filename);
+
        ReadUnusedBytesFromFile(file, chunk_size);
       }
       else
@@ -3487,13 +3622,23 @@ static void LoadLevelFromFileInfo_RND(struct LevelInfo *level,
        int chunk_size_expected =
          (chunk_info[i].loader)(file, chunk_size, level);
 
+       if (chunk_size_expected < 0)
+       {
+         Warn("error reading chunk '%s' in level file '%s'",
+              chunk_name, filename);
+
+         break;
+       }
+
        // the size of some chunks cannot be checked before reading other
        // chunks first (like "HEAD" and "BODY") that contain some header
        // information, so check them here
        if (chunk_size_expected != chunk_size)
        {
-         Error(ERR_WARN, "wrong size (%d) of chunk '%s' in level file '%s'",
-               chunk_size, chunk_name, filename);
+         Warn("wrong size (%d) of chunk '%s' in level file '%s'",
+              chunk_size, chunk_name, filename);
+
+         break;
        }
       }
     }
@@ -3567,6 +3712,7 @@ static void CopyNativeLevel_RND_to_EM(struct LevelInfo *level)
   cav->lenses_time             = level->lenses_time;
   cav->magnify_time            = level->magnify_time;
 
+  cav->wind_time = 9999;
   cav->wind_direction =
     map_direction_RND_to_EM(level->wind_direction_initial);
 
@@ -3603,7 +3749,7 @@ static void CopyNativeLevel_RND_to_EM(struct LevelInfo *level)
   // initialize player positions and delete players from the playfield
   for (y = 0; y < cav->height; y++) for (x = 0; x < cav->width; x++)
   {
-    if (ELEM_IS_PLAYER(level->field[x][y]))
+    if (IS_PLAYER_ELEMENT(level->field[x][y]))
     {
       int player_nr = GET_PLAYER_NR(level->field[x][y]);
 
@@ -3708,6 +3854,9 @@ static void CopyNativeLevel_EM_to_RND(struct LevelInfo *level)
     if (jx != -1 && jy != -1)
       level->field[jx][jy] = EL_PLAYER_1 + nr;
   }
+
+  // time score is counted for each 10 seconds left in Emerald Mine levels
+  level->time_score_base = 10;
 }
 
 
@@ -3820,7 +3969,7 @@ static void CopyNativeLevel_SP_to_RND(struct LevelInfo *level)
       {
        num_invalid_elements++;
 
-       Error(ERR_DEBUG, "invalid element %d at position %d, %d",
+       Debug("level:native:SP", "invalid element %d at position %d, %d",
              element_old, x, y);
       }
 
@@ -3829,8 +3978,8 @@ static void CopyNativeLevel_SP_to_RND(struct LevelInfo *level)
   }
 
   if (num_invalid_elements > 0)
-    Error(ERR_WARN, "found %d invalid elements%s", num_invalid_elements,
-         (!options.debug ? " (use '--debug' for more details)" : ""));
+    Warn("found %d invalid elements%s", num_invalid_elements,
+        (!options.debug ? " (use '--debug' for more details)" : ""));
 
   for (i = 0; i < MAX_PLAYERS; i++)
     level->initial_player_gravity[i] =
@@ -3866,8 +4015,7 @@ static void CopyNativeLevel_SP_to_RND(struct LevelInfo *level)
     if (port_x < 0 || port_x >= level->fieldx ||
        port_y < 0 || port_y >= level->fieldy)
     {
-      Error(ERR_WARN, "special port position (%d, %d) out of bounds",
-           port_x, port_y);
+      Warn("special port position (%d, %d) out of bounds", port_x, port_y);
 
       continue;
     }
@@ -3877,7 +4025,7 @@ static void CopyNativeLevel_SP_to_RND(struct LevelInfo *level)
     if (port_element < EL_SP_GRAVITY_PORT_RIGHT ||
        port_element > EL_SP_GRAVITY_PORT_UP)
     {
-      Error(ERR_WARN, "no special port at position (%d, %d)", port_x, port_y);
+      Warn("no special port at position (%d, %d)", port_x, port_y);
 
       continue;
     }
@@ -3902,12 +4050,11 @@ static void CopyNativeLevel_SP_to_RND(struct LevelInfo *level)
   level->time_wheel = 0;
   level->amoeba_content = EL_EMPTY;
 
-#if 1
-  // original Supaplex does not use score values -- use default values
-#else
+  // original Supaplex does not use score values -- rate by playing time
   for (i = 0; i < LEVEL_SCORE_ELEMENTS; i++)
     level->score[i] = 0;
-#endif
+
+  level->rate_time_over_score = TRUE;
 
   // there are no yamyams in supaplex levels
   for (i = 0; i < level->num_yamyam_contents; i++)
@@ -3943,8 +4090,8 @@ static void CopyNativeTape_RND_to_SP(struct LevelInfo *level)
 
     if (demo->length + demo_entries >= SP_MAX_TAPE_LEN)
     {
-      Error(ERR_WARN, "tape truncated: size exceeds maximum SP demo size %d",
-           SP_MAX_TAPE_LEN);
+      Warn("tape truncated: size exceeds maximum SP demo size %d",
+          SP_MAX_TAPE_LEN);
 
       break;
     }
@@ -3997,8 +4144,8 @@ static void CopyNativeTape_SP_to_RND(struct LevelInfo *level)
 
     if (!success)
     {
-      Error(ERR_WARN, "SP demo truncated: size exceeds maximum tape size %d",
-           MAX_TAPE_LEN);
+      Warn("SP demo truncated: size exceeds maximum tape size %d",
+          MAX_TAPE_LEN);
 
       break;
     }
@@ -4015,7 +4162,7 @@ static void CopyNativeTape_SP_to_RND(struct LevelInfo *level)
 static void CopyNativeLevel_RND_to_MM(struct LevelInfo *level)
 {
   struct LevelInfo_MM *level_mm = level->native_mm_level;
-  int x, y;
+  int i, x, y;
 
   level_mm->fieldx = MIN(level->fieldx, MM_MAX_PLAYFIELD_WIDTH);
   level_mm->fieldy = MIN(level->fieldy, MM_MAX_PLAYFIELD_HEIGHT);
@@ -4024,9 +4171,13 @@ static void CopyNativeLevel_RND_to_MM(struct LevelInfo *level)
   level_mm->kettles_needed = level->gems_needed;
   level_mm->auto_count_kettles = level->auto_count_gems;
 
-  level_mm->laser_red = level->mm_laser_red;
-  level_mm->laser_green = level->mm_laser_green;
-  level_mm->laser_blue = level->mm_laser_blue;
+  level_mm->mm_laser_red   = level->mm_laser_red;
+  level_mm->mm_laser_green = level->mm_laser_green;
+  level_mm->mm_laser_blue  = level->mm_laser_blue;
+
+  level_mm->df_laser_red   = level->df_laser_red;
+  level_mm->df_laser_green = level->df_laser_green;
+  level_mm->df_laser_blue  = level->df_laser_blue;
 
   strcpy(level_mm->name, level->name);
   strcpy(level_mm->author, level->author);
@@ -4043,6 +4194,15 @@ static void CopyNativeLevel_RND_to_MM(struct LevelInfo *level)
   level_mm->time_ball    = level->mm_time_ball;
   level_mm->time_block   = level->mm_time_block;
 
+  level_mm->num_ball_contents = level->num_mm_ball_contents;
+  level_mm->ball_choice_mode = level->mm_ball_choice_mode;
+  level_mm->rotate_ball_content = level->rotate_mm_ball_content;
+  level_mm->explode_ball = level->explode_mm_ball;
+
+  for (i = 0; i < level->num_mm_ball_contents; i++)
+    level_mm->ball_content[i] =
+      map_element_RND_to_MM(level->mm_ball_content[i]);
+
   for (x = 0; x < level->fieldx; x++)
     for (y = 0; y < level->fieldy; y++)
       Ur[x][y] =
@@ -4052,7 +4212,7 @@ static void CopyNativeLevel_RND_to_MM(struct LevelInfo *level)
 static void CopyNativeLevel_MM_to_RND(struct LevelInfo *level)
 {
   struct LevelInfo_MM *level_mm = level->native_mm_level;
-  int x, y;
+  int i, x, y;
 
   level->fieldx = MIN(level_mm->fieldx, MAX_LEV_FIELDX);
   level->fieldy = MIN(level_mm->fieldy, MAX_LEV_FIELDY);
@@ -4061,9 +4221,13 @@ static void CopyNativeLevel_MM_to_RND(struct LevelInfo *level)
   level->gems_needed = level_mm->kettles_needed;
   level->auto_count_gems = level_mm->auto_count_kettles;
 
-  level->mm_laser_red = level_mm->laser_red;
-  level->mm_laser_green = level_mm->laser_green;
-  level->mm_laser_blue = level_mm->laser_blue;
+  level->mm_laser_red   = level_mm->mm_laser_red;
+  level->mm_laser_green = level_mm->mm_laser_green;
+  level->mm_laser_blue  = level_mm->mm_laser_blue;
+
+  level->df_laser_red   = level_mm->df_laser_red;
+  level->df_laser_green = level_mm->df_laser_green;
+  level->df_laser_blue  = level_mm->df_laser_blue;
 
   strcpy(level->name, level_mm->name);
 
@@ -4083,6 +4247,15 @@ static void CopyNativeLevel_MM_to_RND(struct LevelInfo *level)
   level->mm_time_ball  = level_mm->time_ball;
   level->mm_time_block = level_mm->time_block;
 
+  level->num_mm_ball_contents = level_mm->num_ball_contents;
+  level->mm_ball_choice_mode = level_mm->ball_choice_mode;
+  level->rotate_mm_ball_content = level_mm->rotate_ball_content;
+  level->explode_mm_ball = level_mm->explode_ball;
+
+  for (i = 0; i < level->num_mm_ball_contents; i++)
+    level->mm_ball_content[i] =
+      map_element_MM_to_RND(level_mm->ball_content[i]);
+
   for (x = 0; x < level->fieldx; x++)
     for (y = 0; y < level->fieldy; y++)
       level->field[x][y] = map_element_MM_to_RND(level_mm->field[x][y]);
@@ -4273,7 +4446,7 @@ static int getMappedElement_DC(int element)
       break;
 
     case 0x13f5:
-      element = EL_YAMYAM;
+      element = EL_YAMYAM_UP;
       break;
 
     case 0x1425:
@@ -5299,7 +5472,7 @@ static int getMappedElement_DC(int element)
       break;
 
     case 0x1682:       // secret gate (red)
-      element = EL_GATE_1_GRAY;
+      element = EL_EM_GATE_1_GRAY;
       break;
 
     case 0x1683:       // gate (yellow)
@@ -5307,7 +5480,7 @@ static int getMappedElement_DC(int element)
       break;
 
     case 0x1684:       // secret gate (yellow)
-      element = EL_GATE_2_GRAY;
+      element = EL_EM_GATE_2_GRAY;
       break;
 
     case 0x1685:       // gate (blue)
@@ -5315,7 +5488,7 @@ static int getMappedElement_DC(int element)
       break;
 
     case 0x1686:       // secret gate (blue)
-      element = EL_GATE_4_GRAY;
+      element = EL_EM_GATE_4_GRAY;
       break;
 
     case 0x1687:       // gate (green)
@@ -5323,7 +5496,7 @@ static int getMappedElement_DC(int element)
       break;
 
     case 0x1688:       // secret gate (green)
-      element = EL_GATE_3_GRAY;
+      element = EL_EM_GATE_3_GRAY;
       break;
 
     case 0x1689:       // gate (white)
@@ -5554,7 +5727,8 @@ static int getMappedElement_DC(int element)
        element = EL_INVISIBLE_SAND;
       else
       {
-       Error(ERR_WARN, "unknown Diamond Caves element 0x%04x", element);
+       Warn("unknown Diamond Caves element 0x%04x", element);
+
        element = EL_UNKNOWN;
       }
       break;
@@ -5563,8 +5737,7 @@ static int getMappedElement_DC(int element)
   return getMappedElement(element);
 }
 
-static void LoadLevelFromFileStream_DC(File *file, struct LevelInfo *level,
-                                      int nr)
+static void LoadLevelFromFileStream_DC(File *file, struct LevelInfo *level)
 {
   byte header[DC_LEVEL_HEADER_SIZE];
   int envelope_size;
@@ -5602,7 +5775,7 @@ static void LoadLevelFromFileStream_DC(File *file, struct LevelInfo *level,
   {
     level->no_valid_file = TRUE;
 
-    Error(ERR_WARN, "cannot decode level from stream -- using empty level");
+    Warn("cannot decode level from stream -- using empty level");
 
     return;
   }
@@ -5715,9 +5888,19 @@ static void LoadLevelFromFileStream_DC(File *file, struct LevelInfo *level,
   level->extra_time            = header[56] | (header[57] << 8);
   level->shield_normal_time    = header[58] | (header[59] << 8);
 
+  // shield and extra time elements do not have a score
+  level->score[SC_SHIELD]      = 0;
+  level->extra_time_score      = 0;
+
+  // set time for normal and deadly shields to the same value
+  level->shield_deadly_time    = level->shield_normal_time;
+
   // Diamond Caves has the same (strange) behaviour as Emerald Mine that gems
   // can slip down from flat walls, like normal walls and steel walls
   level->em_slippery_gems = TRUE;
+
+  // time score is counted for each 10 seconds left in Diamond Caves levels
+  level->time_score_base = 10;
 }
 
 static void LoadLevelFromFileInfo_DC(struct LevelInfo *level,
@@ -5735,7 +5918,7 @@ static void LoadLevelFromFileInfo_DC(struct LevelInfo *level,
     level->no_valid_file = TRUE;
 
     if (!level_info_only)
-      Error(ERR_WARN, "cannot read level '%s' -- using empty level", filename);
+      Warn("cannot read level '%s' -- using empty level", filename);
 
     return;
   }
@@ -5753,8 +5936,7 @@ static void LoadLevelFromFileInfo_DC(struct LevelInfo *level,
     {
       level->no_valid_file = TRUE;
 
-      Error(ERR_WARN, "unknown DC level file '%s' -- using empty level",
-           filename);
+      Warn("unknown DC level file '%s' -- using empty level", filename);
 
       return;
     }
@@ -5780,8 +5962,7 @@ static void LoadLevelFromFileInfo_DC(struct LevelInfo *level,
        {
          level->no_valid_file = TRUE;
 
-         Error(ERR_WARN, "cannot fseek in file '%s' -- using empty level",
-               filename);
+         Warn("cannot fseek in file '%s' -- using empty level", filename);
 
          return;
        }
@@ -5799,14 +5980,13 @@ static void LoadLevelFromFileInfo_DC(struct LevelInfo *level,
     {
       level->no_valid_file = TRUE;
 
-      Error(ERR_WARN, "unknown DC2 level file '%s' -- using empty level",
-           filename);
+      Warn("unknown DC2 level file '%s' -- using empty level", filename);
 
       return;
     }
   }
 
-  LoadLevelFromFileStream_DC(file, level, level_file_info->nr);
+  LoadLevelFromFileStream_DC(file, level);
 
   closeFile(file);
 }
@@ -5847,6 +6027,21 @@ int getMappedElement_SB(int element_ascii, boolean use_ces)
   return EL_UNDEFINED;
 }
 
+static void SetLevelSettings_SB(struct LevelInfo *level)
+{
+  // time settings
+  level->time = 0;
+  level->use_step_counter = TRUE;
+
+  // score settings
+  level->score[SC_TIME_BONUS] = 0;
+  level->time_score_base = 1;
+  level->rate_time_over_score = TRUE;
+
+  // game settings
+  level->auto_exit_sokoban = TRUE;
+}
+
 static void LoadLevelFromFileInfo_SB(struct LevelInfo *level,
                                     struct LevelFileInfo *level_file_info,
                                     boolean level_info_only)
@@ -5875,7 +6070,7 @@ static void LoadLevelFromFileInfo_SB(struct LevelInfo *level,
     level->no_valid_file = TRUE;
 
     if (!level_info_only)
-      Error(ERR_WARN, "cannot read level '%s' -- using empty level", filename);
+      Warn("cannot read level '%s' -- using empty level", filename);
 
     return;
   }
@@ -6049,7 +6244,7 @@ static void LoadLevelFromFileInfo_SB(struct LevelInfo *level,
   {
     level->no_valid_file = TRUE;
 
-    Error(ERR_WARN, "cannot read level '%s' -- using empty level", filename);
+    Warn("cannot read level '%s' -- using empty level", filename);
 
     return;
   }
@@ -6080,14 +6275,11 @@ static void LoadLevelFromFileInfo_SB(struct LevelInfo *level,
   }
 
   // set special level settings for Sokoban levels
-
-  level->time = 0;
-  level->use_step_counter = TRUE;
+  SetLevelSettings_SB(level);
 
   if (load_xsb_to_ces)
   {
     // special global settings can now be set in level template
-
     level->use_custom_template = TRUE;
   }
 }
@@ -6244,7 +6436,7 @@ static void LoadLevel_InitVersion(struct LevelInfo *level)
   if (level->game_version < VERSION_IDENT(3,2,0,5))
   {
     // time bonus score was given for 10 s instead of 1 s before 3.2.0-5
-    level->score[SC_TIME_BONUS] /= 10;
+    level->time_score_base = 10;
   }
 
   if (leveldir_current->latest_engine)
@@ -6416,6 +6608,45 @@ static void LoadLevel_InitVersion(struct LevelInfo *level)
   // only Sokoban fields (but not objects) had to be solved before 4.1.1.1
   if (level->game_version < VERSION_IDENT(4,1,1,1))
     level->sb_objects_needed = FALSE;
+
+  // CE actions were triggered by unfinished digging/collecting up to 4.2.2.0
+  if (level->game_version <= VERSION_IDENT(4,2,2,0))
+    level->finish_dig_collect = FALSE;
+
+  // CE changing to player was kept under the player if walkable up to 4.2.3.1
+  if (level->game_version <= VERSION_IDENT(4,2,3,1))
+    level->keep_walkable_ce = TRUE;
+}
+
+static void LoadLevel_InitSettings_SB(struct LevelInfo *level)
+{
+  boolean is_sokoban_level = TRUE;    // unless non-Sokoban elements found
+  int x, y;
+
+  // check if this level is (not) a Sokoban level
+  for (y = 0; y < level->fieldy; y++)
+    for (x = 0; x < level->fieldx; x++)
+      if (!IS_SB_ELEMENT(Tile[x][y]))
+       is_sokoban_level = FALSE;
+
+  if (is_sokoban_level)
+  {
+    // set special level settings for Sokoban levels
+    SetLevelSettings_SB(level);
+  }
+}
+
+static void LoadLevel_InitSettings(struct LevelInfo *level)
+{
+  // adjust level settings for (non-native) Sokoban-style levels
+  LoadLevel_InitSettings_SB(level);
+
+  // rename levels with title "nameless level" or if renaming is forced
+  if (leveldir_current->empty_level_name != NULL &&
+      (strEqual(level->name, NAMELESS_LEVEL_NAME) ||
+       leveldir_current->force_level_name))
+    snprintf(level->name, MAX_LEVEL_NAME_LEN + 1,
+            leveldir_current->empty_level_name, level_nr);
 }
 
 static void LoadLevel_InitStandardElements(struct LevelInfo *level)
@@ -6563,6 +6794,27 @@ static void LoadLevel_InitCustomElements(struct LevelInfo *level)
       element_info[element].ignition_delay = 8;
     }
   }
+
+  // set mouse click change events to work for left/middle/right mouse button
+  if (level->game_version < VERSION_IDENT(4,2,3,0))
+  {
+    for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
+    {
+      int element = EL_CUSTOM_START + i;
+      struct ElementInfo *ei = &element_info[element];
+
+      for (j = 0; j < ei->num_change_pages; j++)
+      {
+       struct ElementChangeInfo *change = &ei->change_page[j];
+
+       if (change->has_event[CE_CLICKED_BY_MOUSE] ||
+           change->has_event[CE_PRESSED_BY_MOUSE] ||
+           change->has_event[CE_MOUSE_CLICKED_ON_X] ||
+           change->has_event[CE_MOUSE_PRESSED_ON_X])
+         change->trigger_side = CH_SIDE_ANY;
+      }
+    }
+  }
 }
 
 static void LoadLevel_InitElements(struct LevelInfo *level)
@@ -6623,6 +6875,7 @@ static void LoadLevelTemplate_LoadAndInit(void)
 
   LoadLevel_InitVersion(&level_template);
   LoadLevel_InitElements(&level_template);
+  LoadLevel_InitSettings(&level_template);
 
   ActivateLevelTemplate();
 }
@@ -6631,7 +6884,7 @@ void LoadLevelTemplate(int nr)
 {
   if (!fileExists(getGlobalLevelTemplateFilename()))
   {
-    Error(ERR_WARN, "no level template found for this level");
+    Warn("no level template found for this level");
 
     return;
   }
@@ -6663,6 +6916,7 @@ static void LoadLevel_LoadAndInit(struct NetworkLevelInfo *network_level)
   LoadLevel_InitVersion(&level);
   LoadLevel_InitElements(&level);
   LoadLevel_InitPlayfield(&level);
+  LoadLevel_InitSettings(&level);
 
   LoadLevel_InitNativeEngines(&level);
 }
@@ -6872,7 +7126,8 @@ static void SaveLevel_CNT2(FILE *file, struct LevelInfo *level, int element)
     // chunk header already written -- write empty chunk data
     WriteUnusedBytesToFile(file, LEVEL_CHUNK_CNT2_SIZE);
 
-    Error(ERR_WARN, "cannot save content for element '%d'", element);
+    Warn("cannot save content for element '%d'", element);
+
     return;
   }
 
@@ -6940,7 +7195,7 @@ static void SaveLevel_CUS1(FILE *file, struct LevelInfo *level,
   }
 
   if (check != num_changed_custom_elements)    // should not happen
-    Error(ERR_WARN, "inconsistent number of custom element properties");
+    Warn("inconsistent number of custom element properties");
 }
 #endif
 
@@ -6969,7 +7224,7 @@ static void SaveLevel_CUS2(FILE *file, struct LevelInfo *level,
   }
 
   if (check != num_changed_custom_elements)    // should not happen
-    Error(ERR_WARN, "inconsistent number of custom target elements");
+    Warn("inconsistent number of custom target elements");
 }
 #endif
 
@@ -7052,7 +7307,7 @@ static void SaveLevel_CUS3(FILE *file, struct LevelInfo *level,
   }
 
   if (check != num_changed_custom_elements)    // should not happen
-    Error(ERR_WARN, "inconsistent number of custom element properties");
+    Warn("inconsistent number of custom element properties");
 }
 #endif
 
@@ -7130,7 +7385,7 @@ static void SaveLevel_CUS4(FILE *file, struct LevelInfo *level, int element)
     event_bits = 0;
     for (j = 0; j < MIN(NUM_CHANGE_EVENTS, 32); j++)
       if (change->has_event[j])
-       event_bits |= (1 << j);
+       event_bits |= (1u << j);
     putFile32BitBE(file, event_bits);
 
     putFile16BitBE(file, change->target_element);
@@ -7170,7 +7425,7 @@ static void SaveLevel_CUS4(FILE *file, struct LevelInfo *level, int element)
     event_bits = 0;
     for (j = 32; j < NUM_CHANGE_EVENTS; j++)
       if (change->has_event[j])
-       event_bits |= (1 << (j - 32));
+       event_bits |= (1u << (j - 32));
     putFile8Bit(file, event_bits);
   }
 }
@@ -7421,6 +7676,22 @@ static int SaveLevel_GRPX(FILE *file, struct LevelInfo *level, int element)
   return chunk_size;
 }
 
+static int SaveLevel_EMPX(FILE *file, struct LevelInfo *level, int element)
+{
+  struct ElementInfo *ei = &element_info[element];
+  int chunk_size = 0;
+  int i;
+
+  chunk_size += putFile16BitBE(file, element);
+
+  xx_ei = *ei;         // copy element data into temporary buffer
+
+  for (i = 0; chunk_config_EMPX[i].data_type != -1; i++)
+    chunk_size += SaveLevel_MicroChunk(file, &chunk_config_EMPX[i], FALSE);
+
+  return chunk_size;
+}
+
 static void SaveLevelFromFilename(struct LevelInfo *level, char *filename,
                                  boolean save_as_template)
 {
@@ -7430,7 +7701,8 @@ static void SaveLevelFromFilename(struct LevelInfo *level, char *filename,
 
   if (!(file = fopen(filename, MODE_WRITE)))
   {
-    Error(ERR_WARN, "cannot save level file '%s'", filename);
+    Warn("cannot save level file '%s'", filename);
+
     return;
   }
 
@@ -7511,6 +7783,18 @@ static void SaveLevelFromFilename(struct LevelInfo *level, char *filename,
        SaveLevel_GRPX(file, level, element);
       }
     }
+
+    for (i = 0; i < NUM_EMPTY_ELEMENTS_ALL; i++)
+    {
+      int element = GET_EMPTY_ELEMENT(i);
+
+      chunk_size = SaveLevel_EMPX(NULL, level, element);
+      if (chunk_size > LEVEL_CHUNK_EMPX_UNCHANGED)     // save if changed
+      {
+       putFileChunkBE(file, "EMPX", chunk_size);
+       SaveLevel_EMPX(file, level, element);
+      }
+    }
   }
 
   fclose(file);
@@ -7555,7 +7839,7 @@ void DumpLevel(struct LevelInfo *level)
 {
   if (level->no_level_file || level->no_valid_file)
   {
-    Error(ERR_WARN, "cannot dump -- no valid level file found");
+    Warn("cannot dump -- no valid level file found");
 
     return;
   }
@@ -7586,10 +7870,55 @@ void DumpLevel(struct LevelInfo *level)
   Print("SP player blocks last field: %s\n", (level->sp_block_last_field ? "yes" : "no"));
   Print("use spring bug: %s\n", (level->use_spring_bug ? "yes" : "no"));
   Print("use step counter: %s\n", (level->use_step_counter ? "yes" : "no"));
+  Print("rate time over score: %s\n", (level->rate_time_over_score ? "yes" : "no"));
+
+  if (options.debug)
+  {
+    int i, j;
+
+    for (i = 0; i < NUM_ENVELOPES; i++)
+    {
+      char *text = level->envelope[i].text;
+      int text_len = strlen(text);
+      boolean has_text = FALSE;
+
+      for (j = 0; j < text_len; j++)
+       if (text[j] != ' ' && text[j] != '\n')
+         has_text = TRUE;
+
+      if (has_text)
+      {
+       Print("\n");
+       Print("Envelope %d:\n'%s'\n", i + 1, text);
+      }
+    }
+  }
 
   PrintLine("-", 79);
 }
 
+void DumpLevels(void)
+{
+  static LevelDirTree *dumplevel_leveldir = NULL;
+
+  dumplevel_leveldir = getTreeInfoFromIdentifier(leveldir_first,
+                                                global.dumplevel_leveldir);
+
+  if (dumplevel_leveldir == NULL)
+    Fail("no such level identifier: '%s'", global.dumplevel_leveldir);
+
+  if (global.dumplevel_level_nr < dumplevel_leveldir->first_level ||
+      global.dumplevel_level_nr > dumplevel_leveldir->last_level)
+    Fail("no such level number: %d", global.dumplevel_level_nr);
+
+  leveldir_current = dumplevel_leveldir;
+
+  LoadLevel(global.dumplevel_level_nr);
+  DumpLevel(&level);
+
+  CloseAllAndExit(0);
+}
+
 
 // ============================================================================
 // tape file functions
@@ -7615,11 +7944,16 @@ static void setTapeInfoToDefaults(void)
   tape.level_nr = level_nr;
   tape.counter = 0;
   tape.changed = FALSE;
+  tape.solved = FALSE;
 
   tape.recording = FALSE;
   tape.playing = FALSE;
   tape.pausing = FALSE;
 
+  tape.scr_fieldx = SCR_FIELDX_DEFAULT;
+  tape.scr_fieldy = SCR_FIELDY_DEFAULT;
+
+  tape.no_info_chunk = TRUE;
   tape.no_valid_file = FALSE;
 }
 
@@ -7697,8 +8031,7 @@ static int LoadTape_HEAD(File *file, int chunk_size, struct TapeInfo *tape)
     setTapeActionFlags(tape, getFile8Bit(file));
 
     tape->property_bits = getFile8Bit(file);
-
-    ReadUnusedBytesFromFile(file, TAPE_CHUNK_HEAD_UNUSED);
+    tape->solved = getFile8Bit(file);
 
     engine_version = getFileVersion(file);
     if (engine_version > 0)
@@ -7710,18 +8043,33 @@ static int LoadTape_HEAD(File *file, int chunk_size, struct TapeInfo *tape)
   return chunk_size;
 }
 
+static int LoadTape_SCRN(File *file, int chunk_size, struct TapeInfo *tape)
+{
+  tape->scr_fieldx = getFile8Bit(file);
+  tape->scr_fieldy = getFile8Bit(file);
+
+  return chunk_size;
+}
+
 static int LoadTape_INFO(File *file, int chunk_size, struct TapeInfo *tape)
 {
+  char *level_identifier = NULL;
   int level_identifier_size;
   int i;
 
+  tape->no_info_chunk = FALSE;
+
   level_identifier_size = getFile16BitBE(file);
 
-  tape->level_identifier =
-    checked_realloc(tape->level_identifier, level_identifier_size);
+  level_identifier = checked_malloc(level_identifier_size);
 
   for (i = 0; i < level_identifier_size; i++)
-    tape->level_identifier[i] = getFile8Bit(file);
+    level_identifier[i] = getFile8Bit(file);
+
+  strncpy(tape->level_identifier, level_identifier, MAX_FILENAME_LEN);
+  tape->level_identifier[MAX_FILENAME_LEN] = '\0';
+
+  checked_free(level_identifier);
 
   tape->level_nr = getFile16BitBE(file);
 
@@ -7746,7 +8094,7 @@ static int LoadTape_BODY(File *file, int chunk_size, struct TapeInfo *tape)
   {
     if (i >= MAX_TAPE_LEN)
     {
-      Error(ERR_WARN, "tape truncated -- size exceeds maximum tape size %d",
+      Warn("tape truncated -- size exceeds maximum tape size %d",
            MAX_TAPE_LEN);
 
       // tape too large; read and ignore remaining tape data from this chunk
@@ -7892,7 +8240,7 @@ static void LoadTape_SokobanSolution(char *filename)
       default:
        tape.no_valid_file = TRUE;
 
-       Error(ERR_WARN, "unsupported Sokoban solution file '%s' ['%d']", filename, c);
+       Warn("unsupported Sokoban solution file '%s' ['%d']", filename, c);
 
        break;
     }
@@ -7941,7 +8289,7 @@ void LoadTapeFromFilename(char *filename)
     {
       tape.no_valid_file = TRUE;
 
-      Error(ERR_WARN, "unknown format of tape file '%s'", filename);
+      Warn("unknown format of tape file '%s'", filename);
 
       closeFile(file);
 
@@ -7960,7 +8308,7 @@ void LoadTapeFromFilename(char *filename)
     {
       tape.no_valid_file = TRUE;
 
-      Error(ERR_WARN, "unknown format of tape file '%s'", filename);
+      Warn("unknown format of tape file '%s'", filename);
 
       closeFile(file);
 
@@ -7971,7 +8319,7 @@ void LoadTapeFromFilename(char *filename)
     {
       tape.no_valid_file = TRUE;
 
-      Error(ERR_WARN, "unsupported version of tape file '%s'", filename);
+      Warn("unsupported version of tape file '%s'", filename);
 
       closeFile(file);
 
@@ -8000,6 +8348,7 @@ void LoadTapeFromFilename(char *filename)
     {
       { "VERS", TAPE_CHUNK_VERS_SIZE,  LoadTape_VERS },
       { "HEAD", TAPE_CHUNK_HEAD_SIZE,  LoadTape_HEAD },
+      { "SCRN", TAPE_CHUNK_SCRN_SIZE,  LoadTape_SCRN },
       { "INFO", -1,                    LoadTape_INFO },
       { "BODY", -1,                    LoadTape_BODY },
       {  NULL,  0,                     NULL }
@@ -8015,15 +8364,17 @@ void LoadTapeFromFilename(char *filename)
 
       if (chunk_info[i].name == NULL)
       {
-       Error(ERR_WARN, "unknown chunk '%s' in tape file '%s'",
+       Warn("unknown chunk '%s' in tape file '%s'",
              chunk_name, filename);
+
        ReadUnusedBytesFromFile(file, chunk_size);
       }
       else if (chunk_info[i].size != -1 &&
               chunk_info[i].size != chunk_size)
       {
-       Error(ERR_WARN, "wrong size (%d) of chunk '%s' in tape file '%s'",
+       Warn("wrong size (%d) of chunk '%s' in tape file '%s'",
              chunk_size, chunk_name, filename);
+
        ReadUnusedBytesFromFile(file, chunk_size);
       }
       else
@@ -8037,7 +8388,7 @@ void LoadTapeFromFilename(char *filename)
        // information, so check them here
        if (chunk_size_expected != chunk_size)
        {
-         Error(ERR_WARN, "wrong size (%d) of chunk '%s' in tape file '%s'",
+         Warn("wrong size (%d) of chunk '%s' in tape file '%s'",
                chunk_size, chunk_name, filename);
        }
       }
@@ -8050,9 +8401,12 @@ void LoadTapeFromFilename(char *filename)
   tape.length_seconds = GetTapeLengthSeconds();
 
 #if 0
-  printf("::: tape file version: %d\n",   tape.file_version);
-  printf("::: tape game version: %d\n",   tape.game_version);
-  printf("::: tape engine version: %d\n", tape.engine_version);
+  Debug("files:LoadTapeFromFilename", "tape file version: %d",
+       tape.file_version);
+  Debug("files:LoadTapeFromFilename", "tape game version: %d",
+       tape.game_version);
+  Debug("files:LoadTapeFromFilename", "tape engine version: %d",
+       tape.engine_version);
 #endif
 }
 
@@ -8075,6 +8429,28 @@ void LoadSolutionTape(int nr)
     CopyNativeTape_SP_to_RND(&level);
 }
 
+void LoadScoreTape(char *score_tape_basename, int nr)
+{
+  char *filename = getScoreTapeFilename(score_tape_basename, nr);
+
+  LoadTapeFromFilename(filename);
+}
+
+void LoadScoreCacheTape(char *score_tape_basename, int nr)
+{
+  char *filename = getScoreCacheTapeFilename(score_tape_basename, nr);
+
+  LoadTapeFromFilename(filename);
+}
+
+static boolean checkSaveTape_SCRN(struct TapeInfo *tape)
+{
+  // chunk required for team mode tapes with non-default screen size
+  return (tape->num_participating_players > 1 &&
+         (tape->scr_fieldx != SCR_FIELDX_DEFAULT ||
+          tape->scr_fieldy != SCR_FIELDY_DEFAULT));
+}
+
 static void SaveTape_VERS(FILE *file, struct TapeInfo *tape)
 {
   putFileVersion(file, tape->file_version);
@@ -8100,13 +8476,17 @@ static void SaveTape_HEAD(FILE *file, struct TapeInfo *tape)
   putFile8Bit(file, getTapeActionValue(tape));
 
   putFile8Bit(file, tape->property_bits);
-
-  // unused bytes not at the end here for 4-byte alignment of engine_version
-  WriteUnusedBytesToFile(file, TAPE_CHUNK_HEAD_UNUSED);
+  putFile8Bit(file, tape->solved);
 
   putFileVersion(file, tape->engine_version);
 }
 
+static void SaveTape_SCRN(FILE *file, struct TapeInfo *tape)
+{
+  putFile8Bit(file, tape->scr_fieldx);
+  putFile8Bit(file, tape->scr_fieldy);
+}
+
 static void SaveTape_INFO(FILE *file, struct TapeInfo *tape)
 {
   int level_identifier_size = strlen(tape->level_identifier) + 1;
@@ -8153,7 +8533,8 @@ void SaveTapeToFilename(char *filename)
 
   if (!(file = fopen(filename, MODE_WRITE)))
   {
-    Error(ERR_WARN, "cannot save level recording file '%s'", filename);
+    Warn("cannot save level recording file '%s'", filename);
+
     return;
   }
 
@@ -8171,6 +8552,12 @@ void SaveTapeToFilename(char *filename)
   putFileChunkBE(file, "HEAD", TAPE_CHUNK_HEAD_SIZE);
   SaveTape_HEAD(file, &tape);
 
+  if (checkSaveTape_SCRN(&tape))
+  {
+    putFileChunkBE(file, "SCRN", TAPE_CHUNK_SCRN_SIZE);
+    SaveTape_SCRN(file, &tape);
+  }
+
   putFileChunkBE(file, "INFO", info_chunk_size);
   SaveTape_INFO(file, &tape);
 
@@ -8182,13 +8569,10 @@ void SaveTapeToFilename(char *filename)
   SetFilePermissions(filename, PERMS_PRIVATE);
 }
 
-void SaveTape(int nr)
+static void SaveTapeExt(char *filename)
 {
-  char *filename = getTapeFilename(nr);
   int i;
 
-  InitTapeDirectory(leveldir_current->subdir);
-
   tape.file_version = FILE_VERSION_ACTUAL;
   tape.game_version = GAME_VERSION_ACTUAL;
 
@@ -8204,12 +8588,31 @@ void SaveTape(int nr)
   tape.changed = FALSE;
 }
 
-static boolean SaveTapeCheckedExt(int nr, char *msg_replace, char *msg_saved,
-                                 unsigned int req_state_added)
+void SaveTape(int nr)
 {
   char *filename = getTapeFilename(nr);
-  boolean new_tape = !fileExists(filename);
-  boolean tape_saved = FALSE;
+
+  InitTapeDirectory(leveldir_current->subdir);
+
+  SaveTapeExt(filename);
+}
+
+void SaveScoreTape(int nr)
+{
+  char *filename = getScoreTapeFilename(tape.score_tape_basename, nr);
+
+  // used instead of "leveldir_current->subdir" (for network games)
+  InitScoreTapeDirectory(levelset.identifier, nr);
+
+  SaveTapeExt(filename);
+}
+
+static boolean SaveTapeCheckedExt(int nr, char *msg_replace, char *msg_saved,
+                                 unsigned int req_state_added)
+{
+  char *filename = getTapeFilename(nr);
+  boolean new_tape = !fileExists(filename);
+  boolean tape_saved = FALSE;
 
   if (new_tape || Request(msg_replace, REQ_ASK | req_state_added))
   {
@@ -8242,17 +8645,53 @@ void DumpTape(struct TapeInfo *tape)
 
   if (tape->no_valid_file)
   {
-    Error(ERR_WARN, "cannot dump -- no valid tape file found");
+    Warn("cannot dump -- no valid tape file found");
 
     return;
   }
 
   PrintLine("-", 79);
+
   Print("Tape of Level %03d (file version %08d, game version %08d)\n",
        tape->level_nr, tape->file_version, tape->game_version);
   Print("                  (effective engine version %08d)\n",
        tape->engine_version);
   Print("Level series identifier: '%s'\n", tape->level_identifier);
+
+  Print("Solution tape: %s\n",
+       tape->solved ? "yes" :
+       tape->game_version < VERSION_IDENT(4,3,2,3) ? "unknown" : "no");
+
+  Print("Special tape properties: ");
+  if (tape->property_bits == TAPE_PROPERTY_NONE)
+    Print("[none]");
+  if (tape->property_bits & TAPE_PROPERTY_EM_RANDOM_BUG)
+    Print("[em_random_bug]");
+  if (tape->property_bits & TAPE_PROPERTY_GAME_SPEED)
+    Print("[game_speed]");
+  if (tape->property_bits & TAPE_PROPERTY_PAUSE_MODE)
+    Print("[pause]");
+  if (tape->property_bits & TAPE_PROPERTY_SINGLE_STEP)
+    Print("[single_step]");
+  if (tape->property_bits & TAPE_PROPERTY_SNAPSHOT)
+    Print("[snapshot]");
+  if (tape->property_bits & TAPE_PROPERTY_REPLAYED)
+    Print("[replayed]");
+  if (tape->property_bits & TAPE_PROPERTY_TAS_KEYS)
+    Print("[tas_keys]");
+  if (tape->property_bits & TAPE_PROPERTY_SMALL_GRAPHICS)
+    Print("[small_graphics]");
+  Print("\n");
+
+  int year2 = tape->date / 10000;
+  int year4 = (year2 < 70 ? 2000 + year2 : 1900 + year2);
+  int month_index_raw = (tape->date / 100) % 100;
+  int month_index = month_index_raw % 12;      // prevent invalid index
+  int month = month_index + 1;
+  int day = tape->date % 100;
+
+  Print("Tape date: %04d-%02d-%02d\n", year4, month, day);
+
   PrintLine("-", 79);
 
   tape_frame_counter = 0;
@@ -8290,12 +8729,84 @@ void DumpTape(struct TapeInfo *tape)
   PrintLine("-", 79);
 }
 
+void DumpTapes(void)
+{
+  static LevelDirTree *dumptape_leveldir = NULL;
+
+  dumptape_leveldir = getTreeInfoFromIdentifier(leveldir_first,
+                                               global.dumptape_leveldir);
+
+  if (dumptape_leveldir == NULL)
+    Fail("no such level identifier: '%s'", global.dumptape_leveldir);
+
+  if (global.dumptape_level_nr < dumptape_leveldir->first_level ||
+      global.dumptape_level_nr > dumptape_leveldir->last_level)
+    Fail("no such level number: %d", global.dumptape_level_nr);
+
+  leveldir_current = dumptape_leveldir;
+
+  if (options.mytapes)
+    LoadTape(global.dumptape_level_nr);
+  else
+    LoadSolutionTape(global.dumptape_level_nr);
+
+  DumpTape(&tape);
+
+  CloseAllAndExit(0);
+}
+
 
 // ============================================================================
 // score file functions
 // ============================================================================
 
-void LoadScore(int nr)
+static void setScoreInfoToDefaultsExt(struct ScoreInfo *scores)
+{
+  int i;
+
+  for (i = 0; i < MAX_SCORE_ENTRIES; i++)
+  {
+    strcpy(scores->entry[i].tape_basename, UNDEFINED_FILENAME);
+    strcpy(scores->entry[i].name, EMPTY_PLAYER_NAME);
+    scores->entry[i].score = 0;
+    scores->entry[i].time = 0;
+
+    scores->entry[i].id = -1;
+    strcpy(scores->entry[i].tape_date,    UNKNOWN_NAME);
+    strcpy(scores->entry[i].platform,     UNKNOWN_NAME);
+    strcpy(scores->entry[i].version,      UNKNOWN_NAME);
+    strcpy(scores->entry[i].country_name, UNKNOWN_NAME);
+    strcpy(scores->entry[i].country_code, "??");
+  }
+
+  scores->num_entries = 0;
+  scores->last_added = -1;
+  scores->last_added_local = -1;
+
+  scores->updated = FALSE;
+  scores->uploaded = FALSE;
+  scores->tape_downloaded = FALSE;
+  scores->force_last_added = FALSE;
+
+  // The following values are intentionally not reset here:
+  // - last_level_nr
+  // - last_entry_nr
+  // - next_level_nr
+  // - continue_playing
+  // - continue_on_return
+}
+
+static void setScoreInfoToDefaults(void)
+{
+  setScoreInfoToDefaultsExt(&scores);
+}
+
+static void setServerScoreInfoToDefaults(void)
+{
+  setScoreInfoToDefaultsExt(&server_scores);
+}
+
+static void LoadScore_OLD(int nr)
 {
   int i;
   char *filename = getScoreFilename(nr);
@@ -8304,13 +8815,6 @@ void LoadScore(int nr)
   char *line_ptr;
   FILE *file;
 
-  // always start with reliable default values
-  for (i = 0; i < MAX_SCORE_ENTRIES; i++)
-  {
-    strcpy(highscore[i].Name, EMPTY_PLAYER_NAME);
-    highscore[i].Score = 0;
-  }
-
   if (!(file = fopen(filename, MODE_READ)))
     return;
 
@@ -8320,17 +8824,20 @@ void LoadScore(int nr)
   if (strlen(cookie) > 0 && cookie[strlen(cookie) - 1] == '\n')
     cookie[strlen(cookie) - 1] = '\0';
 
-  if (!checkCookieString(cookie, SCORE_COOKIE))
+  if (!checkCookieString(cookie, SCORE_COOKIE_TMPL))
   {
-    Error(ERR_WARN, "unknown format of score file '%s'", filename);
+    Warn("unknown format of score file '%s'", filename);
+
     fclose(file);
+
     return;
   }
 
   for (i = 0; i < MAX_SCORE_ENTRIES; i++)
   {
-    if (fscanf(file, "%d", &highscore[i].Score) == EOF)
-      Error(ERR_WARN, "fscanf() failed; %s", strerror(errno));
+    if (fscanf(file, "%d", &scores.entry[i].score) == EOF)
+      Warn("fscanf() failed; %s", strerror(errno));
+
     if (fgets(line, MAX_LINE_LEN, file) == NULL)
       line[0] = '\0';
 
@@ -8341,8 +8848,8 @@ void LoadScore(int nr)
     {
       if (*line_ptr != ' ' && *line_ptr != '\t' && *line_ptr != '\0')
       {
-       strncpy(highscore[i].Name, line_ptr, MAX_PLAYER_NAME_LEN);
-       highscore[i].Name[MAX_PLAYER_NAME_LEN] = '\0';
+       strncpy(scores.entry[i].name, line_ptr, MAX_PLAYER_NAME_LEN);
+       scores.entry[i].name[MAX_PLAYER_NAME_LEN] = '\0';
        break;
       }
     }
@@ -8351,30 +8858,597 @@ void LoadScore(int nr)
   fclose(file);
 }
 
-void SaveScore(int nr)
+static void ConvertScore_OLD(void)
+{
+  // only convert score to time for levels that rate playing time over score
+  if (!level.rate_time_over_score)
+    return;
+
+  // convert old score to playing time for score-less levels (like Supaplex)
+  int time_final_max = 999;
+  int i;
+
+  for (i = 0; i < MAX_SCORE_ENTRIES; i++)
+  {
+    int score = scores.entry[i].score;
+
+    if (score > 0 && score < time_final_max)
+      scores.entry[i].time = (time_final_max - score - 1) * FRAMES_PER_SECOND;
+  }
+}
+
+static int LoadScore_VERS(File *file, int chunk_size, struct ScoreInfo *scores)
+{
+  scores->file_version = getFileVersion(file);
+  scores->game_version = getFileVersion(file);
+
+  return chunk_size;
+}
+
+static int LoadScore_INFO(File *file, int chunk_size, struct ScoreInfo *scores)
+{
+  char *level_identifier = NULL;
+  int level_identifier_size;
+  int i;
+
+  level_identifier_size = getFile16BitBE(file);
+
+  level_identifier = checked_malloc(level_identifier_size);
+
+  for (i = 0; i < level_identifier_size; i++)
+    level_identifier[i] = getFile8Bit(file);
+
+  strncpy(scores->level_identifier, level_identifier, MAX_FILENAME_LEN);
+  scores->level_identifier[MAX_FILENAME_LEN] = '\0';
+
+  checked_free(level_identifier);
+
+  scores->level_nr = getFile16BitBE(file);
+  scores->num_entries = getFile16BitBE(file);
+
+  chunk_size = 2 + level_identifier_size + 2 + 2;
+
+  return chunk_size;
+}
+
+static int LoadScore_NAME(File *file, int chunk_size, struct ScoreInfo *scores)
+{
+  int i, j;
+
+  for (i = 0; i < scores->num_entries; i++)
+  {
+    for (j = 0; j < MAX_PLAYER_NAME_LEN; j++)
+      scores->entry[i].name[j] = getFile8Bit(file);
+
+    scores->entry[i].name[MAX_PLAYER_NAME_LEN] = '\0';
+  }
+
+  chunk_size = scores->num_entries * MAX_PLAYER_NAME_LEN;
+
+  return chunk_size;
+}
+
+static int LoadScore_SCOR(File *file, int chunk_size, struct ScoreInfo *scores)
+{
+  int i;
+
+  for (i = 0; i < scores->num_entries; i++)
+    scores->entry[i].score = getFile16BitBE(file);
+
+  chunk_size = scores->num_entries * 2;
+
+  return chunk_size;
+}
+
+static int LoadScore_SC4R(File *file, int chunk_size, struct ScoreInfo *scores)
+{
+  int i;
+
+  for (i = 0; i < scores->num_entries; i++)
+    scores->entry[i].score = getFile32BitBE(file);
+
+  chunk_size = scores->num_entries * 4;
+
+  return chunk_size;
+}
+
+static int LoadScore_TIME(File *file, int chunk_size, struct ScoreInfo *scores)
+{
+  int i;
+
+  for (i = 0; i < scores->num_entries; i++)
+    scores->entry[i].time = getFile32BitBE(file);
+
+  chunk_size = scores->num_entries * 4;
+
+  return chunk_size;
+}
+
+static int LoadScore_TAPE(File *file, int chunk_size, struct ScoreInfo *scores)
+{
+  int i, j;
+
+  for (i = 0; i < scores->num_entries; i++)
+  {
+    for (j = 0; j < MAX_SCORE_TAPE_BASENAME_LEN; j++)
+      scores->entry[i].tape_basename[j] = getFile8Bit(file);
+
+    scores->entry[i].tape_basename[MAX_SCORE_TAPE_BASENAME_LEN] = '\0';
+  }
+
+  chunk_size = scores->num_entries * MAX_SCORE_TAPE_BASENAME_LEN;
+
+  return chunk_size;
+}
+
+void LoadScore(int nr)
+{
+  char *filename = getScoreFilename(nr);
+  char cookie[MAX_LINE_LEN];
+  char chunk_name[CHUNK_ID_LEN + 1];
+  int chunk_size;
+  boolean old_score_file_format = FALSE;
+  File *file;
+
+  // always start with reliable default values
+  setScoreInfoToDefaults();
+
+  if (!(file = openFile(filename, MODE_READ)))
+    return;
+
+  getFileChunkBE(file, chunk_name, NULL);
+  if (strEqual(chunk_name, "RND1"))
+  {
+    getFile32BitBE(file);              // not used
+
+    getFileChunkBE(file, chunk_name, NULL);
+    if (!strEqual(chunk_name, "SCOR"))
+    {
+      Warn("unknown format of score file '%s'", filename);
+
+      closeFile(file);
+
+      return;
+    }
+  }
+  else // check for old file format with cookie string
+  {
+    strcpy(cookie, chunk_name);
+    if (getStringFromFile(file, &cookie[4], MAX_LINE_LEN - 4) == NULL)
+      cookie[4] = '\0';
+    if (strlen(cookie) > 0 && cookie[strlen(cookie) - 1] == '\n')
+      cookie[strlen(cookie) - 1] = '\0';
+
+    if (!checkCookieString(cookie, SCORE_COOKIE_TMPL))
+    {
+      Warn("unknown format of score file '%s'", filename);
+
+      closeFile(file);
+
+      return;
+    }
+
+    old_score_file_format = TRUE;
+  }
+
+  if (old_score_file_format)
+  {
+    // score files from versions before 4.2.4.0 without chunk structure
+    LoadScore_OLD(nr);
+
+    // convert score to time, if possible (mainly for Supaplex levels)
+    ConvertScore_OLD();
+  }
+  else
+  {
+    static struct
+    {
+      char *name;
+      int size;
+      int (*loader)(File *, int, struct ScoreInfo *);
+    }
+    chunk_info[] =
+    {
+      { "VERS", SCORE_CHUNK_VERS_SIZE, LoadScore_VERS },
+      { "INFO", -1,                    LoadScore_INFO },
+      { "NAME", -1,                    LoadScore_NAME },
+      { "SCOR", -1,                    LoadScore_SCOR },
+      { "SC4R", -1,                    LoadScore_SC4R },
+      { "TIME", -1,                    LoadScore_TIME },
+      { "TAPE", -1,                    LoadScore_TAPE },
+
+      {  NULL,  0,                     NULL }
+    };
+
+    while (getFileChunkBE(file, chunk_name, &chunk_size))
+    {
+      int i = 0;
+
+      while (chunk_info[i].name != NULL &&
+            !strEqual(chunk_name, chunk_info[i].name))
+       i++;
+
+      if (chunk_info[i].name == NULL)
+      {
+       Warn("unknown chunk '%s' in score file '%s'",
+             chunk_name, filename);
+
+       ReadUnusedBytesFromFile(file, chunk_size);
+      }
+      else if (chunk_info[i].size != -1 &&
+              chunk_info[i].size != chunk_size)
+      {
+       Warn("wrong size (%d) of chunk '%s' in score file '%s'",
+             chunk_size, chunk_name, filename);
+
+       ReadUnusedBytesFromFile(file, chunk_size);
+      }
+      else
+      {
+       // call function to load this score chunk
+       int chunk_size_expected =
+         (chunk_info[i].loader)(file, chunk_size, &scores);
+
+       // the size of some chunks cannot be checked before reading other
+       // chunks first (like "HEAD" and "BODY") that contain some header
+       // information, so check them here
+       if (chunk_size_expected != chunk_size)
+       {
+         Warn("wrong size (%d) of chunk '%s' in score file '%s'",
+               chunk_size, chunk_name, filename);
+       }
+      }
+    }
+  }
+
+  closeFile(file);
+}
+
+#if ENABLE_HISTORIC_CHUNKS
+void SaveScore_OLD(int nr)
 {
   int i;
-  int permissions = (program.global_scores ? PERMS_PUBLIC : PERMS_PRIVATE);
   char *filename = getScoreFilename(nr);
   FILE *file;
 
   // used instead of "leveldir_current->subdir" (for network games)
   InitScoreDirectory(levelset.identifier);
 
-  if (!(file = fopen(filename, MODE_WRITE)))
-  {
-    Error(ERR_WARN, "cannot save score for level %d", nr);
-    return;
-  }
+  if (!(file = fopen(filename, MODE_WRITE)))
+  {
+    Warn("cannot save score for level %d", nr);
+
+    return;
+  }
+
+  fprintf(file, "%s\n\n", SCORE_COOKIE);
+
+  for (i = 0; i < MAX_SCORE_ENTRIES; i++)
+    fprintf(file, "%d %s\n", scores.entry[i].score, scores.entry[i].name);
+
+  fclose(file);
+
+  SetFilePermissions(filename, PERMS_PRIVATE);
+}
+#endif
+
+static void SaveScore_VERS(FILE *file, struct ScoreInfo *scores)
+{
+  putFileVersion(file, scores->file_version);
+  putFileVersion(file, scores->game_version);
+}
+
+static void SaveScore_INFO(FILE *file, struct ScoreInfo *scores)
+{
+  int level_identifier_size = strlen(scores->level_identifier) + 1;
+  int i;
+
+  putFile16BitBE(file, level_identifier_size);
+
+  for (i = 0; i < level_identifier_size; i++)
+    putFile8Bit(file, scores->level_identifier[i]);
+
+  putFile16BitBE(file, scores->level_nr);
+  putFile16BitBE(file, scores->num_entries);
+}
+
+static void SaveScore_NAME(FILE *file, struct ScoreInfo *scores)
+{
+  int i, j;
+
+  for (i = 0; i < scores->num_entries; i++)
+  {
+    int name_size = strlen(scores->entry[i].name);
+
+    for (j = 0; j < MAX_PLAYER_NAME_LEN; j++)
+      putFile8Bit(file, (j < name_size ? scores->entry[i].name[j] : 0));
+  }
+}
+
+static void SaveScore_SCOR(FILE *file, struct ScoreInfo *scores)
+{
+  int i;
+
+  for (i = 0; i < scores->num_entries; i++)
+    putFile16BitBE(file, scores->entry[i].score);
+}
+
+static void SaveScore_SC4R(FILE *file, struct ScoreInfo *scores)
+{
+  int i;
+
+  for (i = 0; i < scores->num_entries; i++)
+    putFile32BitBE(file, scores->entry[i].score);
+}
+
+static void SaveScore_TIME(FILE *file, struct ScoreInfo *scores)
+{
+  int i;
+
+  for (i = 0; i < scores->num_entries; i++)
+    putFile32BitBE(file, scores->entry[i].time);
+}
+
+static void SaveScore_TAPE(FILE *file, struct ScoreInfo *scores)
+{
+  int i, j;
+
+  for (i = 0; i < scores->num_entries; i++)
+  {
+    int size = strlen(scores->entry[i].tape_basename);
+
+    for (j = 0; j < MAX_SCORE_TAPE_BASENAME_LEN; j++)
+      putFile8Bit(file, (j < size ? scores->entry[i].tape_basename[j] : 0));
+  }
+}
+
+static void SaveScoreToFilename(char *filename)
+{
+  FILE *file;
+  int info_chunk_size;
+  int name_chunk_size;
+  int scor_chunk_size;
+  int sc4r_chunk_size;
+  int time_chunk_size;
+  int tape_chunk_size;
+  boolean has_large_score_values;
+  int i;
+
+  if (!(file = fopen(filename, MODE_WRITE)))
+  {
+    Warn("cannot save score file '%s'", filename);
+
+    return;
+  }
+
+  info_chunk_size = 2 + (strlen(scores.level_identifier) + 1) + 2 + 2;
+  name_chunk_size = scores.num_entries * MAX_PLAYER_NAME_LEN;
+  scor_chunk_size = scores.num_entries * 2;
+  sc4r_chunk_size = scores.num_entries * 4;
+  time_chunk_size = scores.num_entries * 4;
+  tape_chunk_size = scores.num_entries * MAX_SCORE_TAPE_BASENAME_LEN;
+
+  has_large_score_values = FALSE;
+  for (i = 0; i < scores.num_entries; i++)
+    if (scores.entry[i].score > 0xffff)
+      has_large_score_values = TRUE;
+
+  putFileChunkBE(file, "RND1", CHUNK_SIZE_UNDEFINED);
+  putFileChunkBE(file, "SCOR", CHUNK_SIZE_NONE);
+
+  putFileChunkBE(file, "VERS", SCORE_CHUNK_VERS_SIZE);
+  SaveScore_VERS(file, &scores);
+
+  putFileChunkBE(file, "INFO", info_chunk_size);
+  SaveScore_INFO(file, &scores);
+
+  putFileChunkBE(file, "NAME", name_chunk_size);
+  SaveScore_NAME(file, &scores);
+
+  if (has_large_score_values)
+  {
+    putFileChunkBE(file, "SC4R", sc4r_chunk_size);
+    SaveScore_SC4R(file, &scores);
+  }
+  else
+  {
+    putFileChunkBE(file, "SCOR", scor_chunk_size);
+    SaveScore_SCOR(file, &scores);
+  }
+
+  putFileChunkBE(file, "TIME", time_chunk_size);
+  SaveScore_TIME(file, &scores);
+
+  putFileChunkBE(file, "TAPE", tape_chunk_size);
+  SaveScore_TAPE(file, &scores);
+
+  fclose(file);
+
+  SetFilePermissions(filename, PERMS_PRIVATE);
+}
+
+void SaveScore(int nr)
+{
+  char *filename = getScoreFilename(nr);
+  int i;
+
+  // used instead of "leveldir_current->subdir" (for network games)
+  InitScoreDirectory(levelset.identifier);
+
+  scores.file_version = FILE_VERSION_ACTUAL;
+  scores.game_version = GAME_VERSION_ACTUAL;
+
+  strncpy(scores.level_identifier, levelset.identifier, MAX_FILENAME_LEN);
+  scores.level_identifier[MAX_FILENAME_LEN] = '\0';
+  scores.level_nr = level_nr;
+
+  for (i = 0; i < MAX_SCORE_ENTRIES; i++)
+    if (scores.entry[i].score == 0 &&
+        scores.entry[i].time == 0 &&
+        strEqual(scores.entry[i].name, EMPTY_PLAYER_NAME))
+      break;
+
+  scores.num_entries = i;
+
+  if (scores.num_entries == 0)
+    return;
+
+  SaveScoreToFilename(filename);
+}
+
+static void LoadServerScoreFromCache(int nr)
+{
+  struct ScoreEntry score_entry;
+  struct
+  {
+    void *value;
+    boolean is_string;
+    int string_size;
+  }
+  score_mapping[] =
+  {
+    { &score_entry.score,              FALSE,  0                       },
+    { &score_entry.time,               FALSE,  0                       },
+    { score_entry.name,                        TRUE,   MAX_PLAYER_NAME_LEN     },
+    { score_entry.tape_basename,       TRUE,   MAX_FILENAME_LEN        },
+    { score_entry.tape_date,           TRUE,   MAX_ISO_DATE_LEN        },
+    { &score_entry.id,                 FALSE,  0                       },
+    { score_entry.platform,            TRUE,   MAX_PLATFORM_TEXT_LEN   },
+    { score_entry.version,             TRUE,   MAX_VERSION_TEXT_LEN    },
+    { score_entry.country_code,                TRUE,   MAX_COUNTRY_CODE_LEN    },
+    { score_entry.country_name,                TRUE,   MAX_COUNTRY_NAME_LEN    },
+
+    { NULL,                            FALSE,  0                       }
+  };
+  char *filename = getScoreCacheFilename(nr);
+  SetupFileHash *score_hash = loadSetupFileHash(filename);
+  int i, j;
+
+  server_scores.num_entries = 0;
+
+  if (score_hash == NULL)
+    return;
+
+  for (i = 0; i < MAX_SCORE_ENTRIES; i++)
+  {
+    score_entry = server_scores.entry[i];
+
+    for (j = 0; score_mapping[j].value != NULL; j++)
+    {
+      char token[10];
+
+      sprintf(token, "%02d.%d", i, j);
+
+      char *value = getHashEntry(score_hash, token);
+
+      if (value == NULL)
+       continue;
+
+      if (score_mapping[j].is_string)
+      {
+       char *score_value = (char *)score_mapping[j].value;
+       int value_size = score_mapping[j].string_size;
+
+       strncpy(score_value, value, value_size);
+       score_value[value_size] = '\0';
+      }
+      else
+      {
+       int *score_value = (int *)score_mapping[j].value;
+
+       *score_value = atoi(value);
+      }
+
+      server_scores.num_entries = i + 1;
+    }
+
+    server_scores.entry[i] = score_entry;
+  }
+
+  freeSetupFileHash(score_hash);
+}
+
+void LoadServerScore(int nr, boolean download_score)
+{
+  if (!setup.use_api_server)
+    return;
+
+  // always start with reliable default values
+  setServerScoreInfoToDefaults();
+
+  // 1st step: load server scores from cache file (which may not exist)
+  // (this should prevent reading it while the thread is writing to it)
+  LoadServerScoreFromCache(nr);
+
+  if (download_score && runtime.use_api_server)
+  {
+    // 2nd step: download server scores from score server to cache file
+    // (as thread, as it might time out if the server is not reachable)
+    ApiGetScoreAsThread(nr);
+  }
+}
+
+void PrepareScoreTapesForUpload(char *leveldir_subdir)
+{
+  MarkTapeDirectoryUploadsAsIncomplete(leveldir_subdir);
+
+  // if score tape not uploaded, ask for uploading missing tapes later
+  if (!setup.has_remaining_tapes)
+    setup.ask_for_remaining_tapes = TRUE;
+
+  setup.provide_uploading_tapes = TRUE;
+  setup.has_remaining_tapes = TRUE;
+
+  SaveSetup_ServerSetup();
+}
+
+void SaveServerScore(int nr, boolean tape_saved)
+{
+  if (!runtime.use_api_server)
+  {
+    PrepareScoreTapesForUpload(leveldir_current->subdir);
+
+    return;
+  }
+
+  ApiAddScoreAsThread(nr, tape_saved, NULL);
+}
+
+void SaveServerScoreFromFile(int nr, boolean tape_saved,
+                            char *score_tape_filename)
+{
+  if (!runtime.use_api_server)
+    return;
+
+  ApiAddScoreAsThread(nr, tape_saved, score_tape_filename);
+}
+
+void LoadLocalAndServerScore(int nr, boolean download_score)
+{
+  int last_added_local = scores.last_added_local;
+  boolean force_last_added = scores.force_last_added;
+
+  // needed if only showing server scores
+  setScoreInfoToDefaults();
 
-  fprintf(file, "%s\n\n", SCORE_COOKIE);
+  if (!strEqual(setup.scores_in_highscore_list, STR_SCORES_TYPE_SERVER_ONLY))
+    LoadScore(nr);
 
-  for (i = 0; i < MAX_SCORE_ENTRIES; i++)
-    fprintf(file, "%d %s\n", highscore[i].Score, highscore[i].Name);
+  // restore last added local score entry (before merging server scores)
+  scores.last_added = scores.last_added_local = last_added_local;
 
-  fclose(file);
+  if (setup.use_api_server &&
+      !strEqual(setup.scores_in_highscore_list, STR_SCORES_TYPE_LOCAL_ONLY))
+  {
+    // load server scores from cache file and trigger update from server
+    LoadServerScore(nr, download_score);
 
-  SetFilePermissions(filename, permissions);
+    // merge local scores with scores from server
+    MergeServerScore();
+  }
+
+  if (force_last_added)
+    scores.force_last_added = force_last_added;
 }
 
 
@@ -8391,6 +9465,10 @@ static struct TokenInfo global_setup_tokens[] =
     TYPE_STRING,
     &setup.player_name,                                "player_name"
   },
+  {
+    TYPE_SWITCH,
+    &setup.multiple_users,                     "multiple_users"
+  },
   {
     TYPE_SWITCH,
     &setup.sound,                              "sound"
@@ -8411,6 +9489,10 @@ static struct TokenInfo global_setup_tokens[] =
     TYPE_SWITCH,
     &setup.toons,                              "toons"
   },
+  {
+    TYPE_SWITCH,
+    &setup.global_animations,                  "global_animations"
+  },
   {
     TYPE_SWITCH,
     &setup.scroll_delay,                       "scroll_delay"
@@ -8439,6 +9521,14 @@ static struct TokenInfo global_setup_tokens[] =
     TYPE_SWITCH,
     &setup.autorecord,                         "automatic_tape_recording"
   },
+  {
+    TYPE_SWITCH,
+    &setup.autorecord_after_replay,            "autorecord_after_replay"
+  },
+  {
+    TYPE_SWITCH,
+    &setup.auto_pause_on_start,                        "auto_pause_on_start"
+  },
   {
     TYPE_SWITCH,
     &setup.show_titlescreen,                   "show_titlescreen"
@@ -8469,7 +9559,11 @@ static struct TokenInfo global_setup_tokens[] =
   },
   {
     TYPE_SWITCH,
-    &setup.skip_scores_after_game,             "skip_scores_after_game"
+    &setup.count_score_after_game,             "count_score_after_game"
+  },
+  {
+    TYPE_SWITCH,
+    &setup.show_scores_after_game,             "show_scores_after_game"
   },
   {
     TYPE_SWITCH,
@@ -8507,6 +9601,14 @@ static struct TokenInfo global_setup_tokens[] =
     TYPE_SWITCH,
     &setup.ask_on_game_over,                   "ask_on_game_over"
   },
+  {
+    TYPE_SWITCH,
+    &setup.ask_on_quit_game,                   "ask_on_quit_game"
+  },
+  {
+    TYPE_SWITCH,
+    &setup.ask_on_quit_program,                        "ask_on_quit_program"
+  },
   {
     TYPE_SWITCH,
     &setup.quick_switch,                       "quick_player_switch"
@@ -8523,6 +9625,10 @@ static struct TokenInfo global_setup_tokens[] =
     TYPE_SWITCH,
     &setup.prefer_lowpass_sounds,              "prefer_lowpass_sounds"
   },
+  {
+    TYPE_SWITCH,
+    &setup.prefer_extra_panel_items,           "prefer_extra_panel_items"
+  },
   {
     TYPE_SWITCH,
     &setup.game_speed_extended,                        "game_speed_extended"
@@ -8541,7 +9647,15 @@ static struct TokenInfo global_setup_tokens[] =
   },
   {
     TYPE_SWITCH,
-    &setup.show_snapshot_buttons,              "show_snapshot_buttons"
+    &setup.show_load_save_buttons,             "show_load_save_buttons"
+  },
+  {
+    TYPE_SWITCH,
+    &setup.show_undo_redo_buttons,             "show_undo_redo_buttons"
+  },
+  {
+    TYPE_STRING,
+    &setup.scores_in_highscore_list,           "scores_in_highscore_list"
   },
   {
     TYPE_STRING,
@@ -8631,6 +9745,10 @@ static struct TokenInfo global_setup_tokens[] =
     TYPE_INTEGER,
     &setup.touch.grid_ysize[1],                        "touch.virtual_buttons.1.ysize"
   },
+  {
+    TYPE_SWITCH,
+    &setup.touch.overlay_buttons,              "touch.overlay_buttons"
+  },
 };
 
 static struct TokenInfo auto_setup_tokens[] =
@@ -8641,6 +9759,50 @@ static struct TokenInfo auto_setup_tokens[] =
   },
 };
 
+static struct TokenInfo server_setup_tokens[] =
+{
+  {
+    TYPE_STRING,
+    &setup.player_uuid,                                "player_uuid"
+  },
+  {
+    TYPE_INTEGER,
+    &setup.player_version,                     "player_version"
+  },
+  {
+    TYPE_SWITCH,
+    &setup.use_api_server,          TEST_PREFIX        "use_api_server"
+  },
+  {
+    TYPE_STRING,
+    &setup.api_server_hostname,     TEST_PREFIX        "api_server_hostname"
+  },
+  {
+    TYPE_STRING,
+    &setup.api_server_password,     TEST_PREFIX        "api_server_password"
+  },
+  {
+    TYPE_SWITCH,
+    &setup.ask_for_uploading_tapes, TEST_PREFIX        "ask_for_uploading_tapes"
+  },
+  {
+    TYPE_SWITCH,
+    &setup.ask_for_remaining_tapes, TEST_PREFIX        "ask_for_remaining_tapes"
+  },
+  {
+    TYPE_SWITCH,
+    &setup.provide_uploading_tapes, TEST_PREFIX        "provide_uploading_tapes"
+  },
+  {
+    TYPE_SWITCH,
+    &setup.ask_for_using_api_server,TEST_PREFIX        "ask_for_using_api_server"
+  },
+  {
+    TYPE_SWITCH,
+    &setup.has_remaining_tapes,     TEST_PREFIX        "has_remaining_tapes"
+  },
+};
+
 static struct TokenInfo editor_setup_tokens[] =
 {
   {
@@ -8667,6 +9829,10 @@ static struct TokenInfo editor_setup_tokens[] =
     TYPE_SWITCH,
     &setup.editor.show_element_token,          "editor.show_element_token"
   },
+  {
+    TYPE_SWITCH,
+    &setup.editor.show_read_only_warning,      "editor.show_read_only_warning"
+  },
 };
 
 static struct TokenInfo editor_cascade_setup_tokens[] =
@@ -8727,6 +9893,10 @@ static struct TokenInfo editor_cascade_setup_tokens[] =
     TYPE_SWITCH,
     &setup.editor_cascade.el_ge,               "editor.cascade.el_ge"
   },
+  {
+    TYPE_SWITCH,
+    &setup.editor_cascade.el_es,               "editor.cascade.el_es"
+  },
   {
     TYPE_SWITCH,
     &setup.editor_cascade.el_ref,              "editor.cascade.el_ref"
@@ -8751,6 +9921,14 @@ static struct TokenInfo shortcut_setup_tokens[] =
     TYPE_KEY_X11,
     &setup.shortcut.load_game,                 "shortcut.load_game"
   },
+  {
+    TYPE_KEY_X11,
+    &setup.shortcut.restart_game,              "shortcut.restart_game"
+  },
+  {
+    TYPE_KEY_X11,
+    &setup.shortcut.pause_before_end,          "shortcut.pause_before_end"
+  },
   {
     TYPE_KEY_X11,
     &setup.shortcut.toggle_pause,              "shortcut.toggle_pause"
@@ -9000,10 +10178,18 @@ static struct TokenInfo internal_setup_tokens[] =
     TYPE_BOOLEAN,
     &setup.internal.create_user_levelset,      "create_user_levelset"
   },
+  {
+    TYPE_BOOLEAN,
+    &setup.internal.info_screens_from_main,    "info_screens_from_main"
+  },
   {
     TYPE_BOOLEAN,
     &setup.internal.menu_game,                 "menu_game"
   },
+  {
+    TYPE_BOOLEAN,
+    &setup.internal.menu_engines,              "menu_engines"
+  },
   {
     TYPE_BOOLEAN,
     &setup.internal.menu_editor,               "menu_editor"
@@ -9040,6 +10226,58 @@ static struct TokenInfo internal_setup_tokens[] =
     TYPE_BOOLEAN,
     &setup.internal.menu_save_and_exit,                "menu_save_and_exit"
   },
+  {
+    TYPE_BOOLEAN,
+    &setup.internal.menu_shortcuts_various,    "menu_shortcuts_various"
+  },
+  {
+    TYPE_BOOLEAN,
+    &setup.internal.menu_shortcuts_focus,      "menu_shortcuts_focus"
+  },
+  {
+    TYPE_BOOLEAN,
+    &setup.internal.menu_shortcuts_tape,       "menu_shortcuts_tape"
+  },
+  {
+    TYPE_BOOLEAN,
+    &setup.internal.menu_shortcuts_sound,      "menu_shortcuts_sound"
+  },
+  {
+    TYPE_BOOLEAN,
+    &setup.internal.menu_shortcuts_snap,       "menu_shortcuts_snap"
+  },
+  {
+    TYPE_BOOLEAN,
+    &setup.internal.info_title,                        "info_title"
+  },
+  {
+    TYPE_BOOLEAN,
+    &setup.internal.info_elements,             "info_elements"
+  },
+  {
+    TYPE_BOOLEAN,
+    &setup.internal.info_music,                        "info_music"
+  },
+  {
+    TYPE_BOOLEAN,
+    &setup.internal.info_credits,              "info_credits"
+  },
+  {
+    TYPE_BOOLEAN,
+    &setup.internal.info_program,              "info_program"
+  },
+  {
+    TYPE_BOOLEAN,
+    &setup.internal.info_version,              "info_version"
+  },
+  {
+    TYPE_BOOLEAN,
+    &setup.internal.info_levelset,             "info_levelset"
+  },
+  {
+    TYPE_BOOLEAN,
+    &setup.internal.info_exit,                 "info_exit"
+  },
 };
 
 static struct TokenInfo debug_setup_tokens[] =
@@ -9135,6 +10373,14 @@ static struct TokenInfo debug_setup_tokens[] =
     TYPE_BOOLEAN,
     &setup.debug.show_frames_per_second,       "debug.show_frames_per_second"
   },
+  {
+    TYPE_SWITCH3,
+    &setup.debug.xsn_mode,                     "debug.xsn_mode"
+  },
+  {
+    TYPE_INTEGER,
+    &setup.debug.xsn_percent,                  "debug.xsn_percent"
+  },
 };
 
 static struct TokenInfo options_setup_tokens[] =
@@ -9143,34 +10389,30 @@ static struct TokenInfo options_setup_tokens[] =
     TYPE_BOOLEAN,
     &setup.options.verbose,                    "options.verbose"
   },
+  {
+    TYPE_BOOLEAN,
+    &setup.options.debug,                      "options.debug"
+  },
+  {
+    TYPE_STRING,
+    &setup.options.debug_mode,                 "options.debug_mode"
+  },
 };
 
-static char *get_corrected_login_name(char *login_name)
-{
-  // needed because player name must be a fixed length string
-  char *login_name_new = checked_malloc(MAX_PLAYER_NAME_LEN + 1);
-
-  strncpy(login_name_new, login_name, MAX_PLAYER_NAME_LEN);
-  login_name_new[MAX_PLAYER_NAME_LEN] = '\0';
-
-  if (strlen(login_name) > MAX_PLAYER_NAME_LEN)                // name has been cut
-    if (strchr(login_name_new, ' '))
-      *strchr(login_name_new, ' ') = '\0';
-
-  return login_name_new;
-}
-
 static void setSetupInfoToDefaults(struct SetupInfo *si)
 {
   int i;
 
-  si->player_name = get_corrected_login_name(getLoginName());
+  si->player_name = getStringCopy(getDefaultUserName(user.nr));
+
+  si->multiple_users = TRUE;
 
   si->sound = TRUE;
   si->sound_loops = TRUE;
   si->sound_music = TRUE;
   si->sound_simple = TRUE;
   si->toons = TRUE;
+  si->global_animations = TRUE;
   si->scroll_delay = TRUE;
   si->forced_scroll_delay = FALSE;
   si->scroll_delay_value = STD_SCROLL_DELAY;
@@ -9178,6 +10420,8 @@ static void setSetupInfoToDefaults(struct SetupInfo *si)
   si->engine_snapshot_memory = SNAPSHOT_MEMORY_DEFAULT;
   si->fade_screens = TRUE;
   si->autorecord = TRUE;
+  si->autorecord_after_replay = TRUE;
+  si->auto_pause_on_start = FALSE;
   si->show_titlescreen = TRUE;
   si->quick_doors = FALSE;
   si->team_mode = FALSE;
@@ -9185,7 +10429,8 @@ static void setSetupInfoToDefaults(struct SetupInfo *si)
   si->skip_levels = TRUE;
   si->increment_levels = TRUE;
   si->auto_play_next_level = TRUE;
-  si->skip_scores_after_game = FALSE;
+  si->count_score_after_game = TRUE;
+  si->show_scores_after_game = TRUE;
   si->time_limit = TRUE;
   si->fullscreen = FALSE;
   si->window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
@@ -9195,15 +10440,20 @@ static void setSetupInfoToDefaults(struct SetupInfo *si)
   si->ask_on_escape = TRUE;
   si->ask_on_escape_editor = TRUE;
   si->ask_on_game_over = TRUE;
+  si->ask_on_quit_game = TRUE;
+  si->ask_on_quit_program = TRUE;
   si->quick_switch = FALSE;
   si->input_on_focus = FALSE;
   si->prefer_aga_graphics = TRUE;
   si->prefer_lowpass_sounds = FALSE;
+  si->prefer_extra_panel_items = TRUE;
   si->game_speed_extended = FALSE;
   si->game_frame_delay = GAME_FRAME_DELAY;
   si->sp_show_border_elements = FALSE;
   si->small_game_graphics = FALSE;
-  si->show_snapshot_buttons = FALSE;
+  si->show_load_save_buttons = FALSE;
+  si->show_undo_redo_buttons = FALSE;
+  si->scores_in_highscore_list = getStringCopy(STR_SCORES_TYPE_DEFAULT);
 
   si->graphics_set = getStringCopy(GFX_CLASSIC_SUBDIR);
   si->sounds_set   = getStringCopy(SND_CLASSIC_SUBDIR);
@@ -9277,6 +10527,8 @@ static void setSetupInfoToDefaults(struct SetupInfo *si)
 
   si->touch.grid_initialized           = video.initialized;
 
+  si->touch.overlay_buttons            = FALSE;
+
   si->editor.el_boulderdash            = TRUE;
   si->editor.el_emerald_mine           = TRUE;
   si->editor.el_emerald_mine_club      = TRUE;
@@ -9302,10 +10554,14 @@ static void setSetupInfoToDefaults(struct SetupInfo *si)
 
   si->editor.show_element_token                = FALSE;
 
+  si->editor.show_read_only_warning    = TRUE;
+
   si->editor.use_template_for_new_levels = TRUE;
 
   si->shortcut.save_game       = DEFAULT_KEY_SAVE_GAME;
   si->shortcut.load_game       = DEFAULT_KEY_LOAD_GAME;
+  si->shortcut.restart_game    = DEFAULT_KEY_RESTART_GAME;
+  si->shortcut.pause_before_end        = DEFAULT_KEY_PAUSE_BEFORE_END;
   si->shortcut.toggle_pause    = DEFAULT_KEY_TOGGLE_PAUSE;
 
   si->shortcut.focus_player[0] = DEFAULT_KEY_FOCUS_PLAYER_1;
@@ -9377,6 +10633,7 @@ static void setSetupInfoToDefaults(struct SetupInfo *si)
   si->internal.choose_from_top_leveldir = FALSE;
   si->internal.show_scaling_in_title = TRUE;
   si->internal.create_user_levelset = TRUE;
+  si->internal.info_screens_from_main = FALSE;
 
   si->internal.default_window_width  = WIN_XSIZE_DEFAULT;
   si->internal.default_window_height = WIN_YSIZE_DEFAULT;
@@ -9408,11 +10665,19 @@ static void setSetupInfoToDefaults(struct SetupInfo *si)
 
   si->debug.show_frames_per_second = FALSE;
 
+  si->debug.xsn_mode = AUTO;
+  si->debug.xsn_percent = 0;
+
   si->options.verbose = FALSE;
+  si->options.debug = FALSE;
+  si->options.debug_mode = getStringCopy(ARG_UNDEFINED_STRING);
 
 #if defined(PLATFORM_ANDROID)
   si->fullscreen = TRUE;
+  si->touch.overlay_buttons = TRUE;
 #endif
+
+  setHideSetupEntry(&setup.debug.xsn_mode);
 }
 
 static void setSetupInfoToDefaults_AutoSetup(struct SetupInfo *si)
@@ -9420,6 +10685,21 @@ static void setSetupInfoToDefaults_AutoSetup(struct SetupInfo *si)
   si->auto_setup.editor_zoom_tilesize = MINI_TILESIZE;
 }
 
+static void setSetupInfoToDefaults_ServerSetup(struct SetupInfo *si)
+{
+  si->player_uuid = NULL;      // (will be set later)
+  si->player_version = 1;      // (will be set later)
+
+  si->use_api_server = TRUE;
+  si->api_server_hostname = getStringCopy(API_SERVER_HOSTNAME);
+  si->api_server_password = getStringCopy(UNDEFINED_PASSWORD);
+  si->ask_for_uploading_tapes = TRUE;
+  si->ask_for_remaining_tapes = FALSE;
+  si->provide_uploading_tapes = TRUE;
+  si->ask_for_using_api_server = TRUE;
+  si->has_remaining_tapes = FALSE;
+}
+
 static void setSetupInfoToDefaults_EditorCascade(struct SetupInfo *si)
 {
   si->editor_cascade.el_bd             = TRUE;
@@ -9438,6 +10718,7 @@ static void setSetupInfoToDefaults_EditorCascade(struct SetupInfo *si)
   si->editor_cascade.el_steel_chars    = FALSE;
   si->editor_cascade.el_ce             = FALSE;
   si->editor_cascade.el_ge             = FALSE;
+  si->editor_cascade.el_es             = FALSE;
   si->editor_cascade.el_ref            = FALSE;
   si->editor_cascade.el_user           = FALSE;
   si->editor_cascade.el_dynamic                = FALSE;
@@ -9459,10 +10740,21 @@ void setHideSetupEntry(void *setup_value)
 {
   char *hide_setup_token = getHideSetupToken(setup_value);
 
+  if (hide_setup_hash == NULL)
+    hide_setup_hash = newSetupFileHash();
+
   if (setup_value != NULL)
     setHashEntry(hide_setup_hash, hide_setup_token, "");
 }
 
+void removeHideSetupEntry(void *setup_value)
+{
+  char *hide_setup_token = getHideSetupToken(setup_value);
+
+  if (setup_value != NULL)
+    removeHashEntry(hide_setup_hash, hide_setup_token);
+}
+
 boolean hideSetupEntry(void *setup_value)
 {
   char *hide_setup_token = getHideSetupToken(setup_value);
@@ -9484,6 +10776,8 @@ static void setSetupInfoFromTokenText(SetupFileHash *setup_file_hash,
   // check if this setup option should be hidden in the setup menu
   if (token_hide_value != NULL && get_boolean_from_string(token_hide_value))
     setHideSetupEntry(token_info[token_nr].value);
+
+  free(token_hide_text);
 }
 
 static void setSetupInfoFromTokenInfo(SetupFileHash *setup_file_hash,
@@ -9494,16 +10788,13 @@ static void setSetupInfoFromTokenInfo(SetupFileHash *setup_file_hash,
                            token_info[token_nr].text);
 }
 
-static void decodeSetupFileHash(SetupFileHash *setup_file_hash)
+static void decodeSetupFileHash_Default(SetupFileHash *setup_file_hash)
 {
   int i, pnr;
 
   if (!setup_file_hash)
     return;
 
-  if (hide_setup_hash == NULL)
-    hide_setup_hash = newSetupFileHash();
-
   for (i = 0; i < ARRAY_SIZE(global_setup_tokens); i++)
     setSetupInfoFromTokenInfo(setup_file_hash, global_setup_tokens, i);
 
@@ -9597,6 +10888,19 @@ static void decodeSetupFileHash_AutoSetup(SetupFileHash *setup_file_hash)
                              auto_setup_tokens[i].text));
 }
 
+static void decodeSetupFileHash_ServerSetup(SetupFileHash *setup_file_hash)
+{
+  int i;
+
+  if (!setup_file_hash)
+    return;
+
+  for (i = 0; i < ARRAY_SIZE(server_setup_tokens); i++)
+    setSetupInfo(server_setup_tokens, i,
+                getHashEntry(setup_file_hash,
+                             server_setup_tokens[i].text));
+}
+
 static void decodeSetupFileHash_EditorCascade(SetupFileHash *setup_file_hash)
 {
   int i;
@@ -9610,19 +10914,56 @@ static void decodeSetupFileHash_EditorCascade(SetupFileHash *setup_file_hash)
                              editor_cascade_setup_tokens[i].text));
 }
 
+void LoadUserNames(void)
+{
+  int last_user_nr = user.nr;
+  int i;
+
+  if (global.user_names != NULL)
+  {
+    for (i = 0; i < MAX_PLAYER_NAMES; i++)
+      checked_free(global.user_names[i]);
+
+    checked_free(global.user_names);
+  }
+
+  global.user_names = checked_calloc(MAX_PLAYER_NAMES * sizeof(char *));
+
+  for (i = 0; i < MAX_PLAYER_NAMES; i++)
+  {
+    user.nr = i;
+
+    SetupFileHash *setup_file_hash = loadSetupFileHash(getSetupFilename());
+
+    if (setup_file_hash)
+    {
+      char *player_name = getHashEntry(setup_file_hash, "player_name");
+
+      global.user_names[i] = getFixedUserName(player_name);
+
+      freeSetupFileHash(setup_file_hash);
+    }
+
+    if (global.user_names[i] == NULL)
+      global.user_names[i] = getStringCopy(getDefaultUserName(i));
+  }
+
+  user.nr = last_user_nr;
+}
+
 void LoadSetupFromFilename(char *filename)
 {
   SetupFileHash *setup_file_hash = loadSetupFileHash(filename);
 
   if (setup_file_hash)
   {
-    decodeSetupFileHash(setup_file_hash);
+    decodeSetupFileHash_Default(setup_file_hash);
 
     freeSetupFileHash(setup_file_hash);
   }
   else
   {
-    Error(ERR_DEBUG, "using default setup values");
+    Debug("setup", "using default setup values");
   }
 }
 
@@ -9631,7 +10972,7 @@ static void LoadSetup_SpecialPostProcessing(void)
   char *player_name_new;
 
   // needed to work around problems with fixed length strings
-  player_name_new = get_corrected_login_name(setup.player_name);
+  player_name_new = getFixedUserName(setup.player_name);
   free(setup.player_name);
   setup.player_name = player_name_new;
 
@@ -9647,7 +10988,7 @@ static void LoadSetup_SpecialPostProcessing(void)
     MIN(MAX(MIN_SCROLL_DELAY, setup.scroll_delay_value), MAX_SCROLL_DELAY);
 }
 
-void LoadSetup(void)
+void LoadSetup_Default(void)
 {
   char *filename;
 
@@ -9657,6 +10998,12 @@ void LoadSetup(void)
   // try to load setup values from default setup file
   filename = getDefaultSetupFilename();
 
+  if (fileExists(filename))
+    LoadSetupFromFilename(filename);
+
+  // try to load setup values from platform setup file
+  filename = getPlatformSetupFilename();
+
   if (fileExists(filename))
     LoadSetupFromFilename(filename);
 
@@ -9688,6 +11035,35 @@ void LoadSetup_AutoSetup(void)
   free(filename);
 }
 
+void LoadSetup_ServerSetup(void)
+{
+  char *filename = getPath2(getSetupDir(), SERVERSETUP_FILENAME);
+  SetupFileHash *setup_file_hash = NULL;
+
+  // always start with reliable default values
+  setSetupInfoToDefaults_ServerSetup(&setup);
+
+  setup_file_hash = loadSetupFileHash(filename);
+
+  if (setup_file_hash)
+  {
+    decodeSetupFileHash_ServerSetup(setup_file_hash);
+
+    freeSetupFileHash(setup_file_hash);
+  }
+
+  free(filename);
+
+  if (setup.player_uuid == NULL)
+  {
+    // player UUID does not yet exist in setup file
+    setup.player_uuid = getStringCopy(getUUID());
+    setup.player_version = 2;
+
+    SaveSetup_ServerSetup();
+  }
+}
+
 void LoadSetup_EditorCascade(void)
 {
   char *filename = getPath2(getSetupDir(), EDITORCASCADE_FILENAME);
@@ -9708,6 +11084,14 @@ void LoadSetup_EditorCascade(void)
   free(filename);
 }
 
+void LoadSetup(void)
+{
+  LoadSetup_Default();
+  LoadSetup_AutoSetup();
+  LoadSetup_ServerSetup();
+  LoadSetup_EditorCascade();
+}
+
 static void addGameControllerMappingToHash(SetupFileHash *mappings_hash,
                                           char *mapping_line)
 {
@@ -9739,7 +11123,7 @@ static void LoadSetup_ReadGameControllerMappings(SetupFileHash *mappings_hash,
 
   if (!(file = fopen(filename, MODE_READ)))
   {
-    Error(ERR_WARN, "cannot read game controller mappings file '%s'", filename);
+    Warn("cannot read game controller mappings file '%s'", filename);
 
     return;
   }
@@ -9757,7 +11141,7 @@ static void LoadSetup_ReadGameControllerMappings(SetupFileHash *mappings_hash,
   fclose(file);
 }
 
-void SaveSetup(void)
+void SaveSetup_Default(void)
 {
   char *filename = getSetupFilename();
   FILE *file;
@@ -9767,7 +11151,8 @@ void SaveSetup(void)
 
   if (!(file = fopen(filename, MODE_WRITE)))
   {
-    Error(ERR_WARN, "cannot write setup file '%s'", filename);
+    Warn("cannot write setup file '%s'", filename);
+
     return;
   }
 
@@ -9776,7 +11161,8 @@ void SaveSetup(void)
   for (i = 0; i < ARRAY_SIZE(global_setup_tokens); i++)
   {
     // just to make things nicer :)
-    if (global_setup_tokens[i].value == &setup.sound                   ||
+    if (global_setup_tokens[i].value == &setup.multiple_users          ||
+       global_setup_tokens[i].value == &setup.sound                    ||
        global_setup_tokens[i].value == &setup.graphics_set             ||
        global_setup_tokens[i].value == &setup.volume_simple            ||
        global_setup_tokens[i].value == &setup.network_mode             ||
@@ -9844,7 +11230,9 @@ void SaveSetup(void)
 
   fprintf(file, "\n");
   for (i = 0; i < ARRAY_SIZE(debug_setup_tokens); i++)
-    fprintf(file, "%s\n", getSetupLine(debug_setup_tokens, "", i));
+    if (!strPrefix(debug_setup_tokens[i].text, "debug.xsn_") ||
+       setup.debug.xsn_mode != AUTO)
+      fprintf(file, "%s\n", getSetupLine(debug_setup_tokens, "", i));
 
   fprintf(file, "\n");
   for (i = 0; i < ARRAY_SIZE(options_setup_tokens); i++)
@@ -9855,9 +11243,38 @@ void SaveSetup(void)
   SetFilePermissions(filename, PERMS_PRIVATE);
 }
 
-void SaveSetup_AutoSetup(void)
+void SaveSetup_AutoSetup(void)
+{
+  char *filename = getPath2(getSetupDir(), AUTOSETUP_FILENAME);
+  FILE *file;
+  int i;
+
+  InitUserDataDirectory();
+
+  if (!(file = fopen(filename, MODE_WRITE)))
+  {
+    Warn("cannot write auto setup file '%s'", filename);
+
+    free(filename);
+
+    return;
+  }
+
+  fprintFileHeader(file, AUTOSETUP_FILENAME);
+
+  for (i = 0; i < ARRAY_SIZE(auto_setup_tokens); i++)
+    fprintf(file, "%s\n", getSetupLine(auto_setup_tokens, "", i));
+
+  fclose(file);
+
+  SetFilePermissions(filename, PERMS_PRIVATE);
+
+  free(filename);
+}
+
+void SaveSetup_ServerSetup(void)
 {
-  char *filename = getPath2(getSetupDir(), AUTOSETUP_FILENAME);
+  char *filename = getPath2(getSetupDir(), SERVERSETUP_FILENAME);
   FILE *file;
   int i;
 
@@ -9865,15 +11282,23 @@ void SaveSetup_AutoSetup(void)
 
   if (!(file = fopen(filename, MODE_WRITE)))
   {
-    Error(ERR_WARN, "cannot write auto setup file '%s'", filename);
+    Warn("cannot write server setup file '%s'", filename);
+
     free(filename);
+
     return;
   }
 
-  fprintFileHeader(file, AUTOSETUP_FILENAME);
+  fprintFileHeader(file, SERVERSETUP_FILENAME);
 
-  for (i = 0; i < ARRAY_SIZE(auto_setup_tokens); i++)
-    fprintf(file, "%s\n", getSetupLine(auto_setup_tokens, "", i));
+  for (i = 0; i < ARRAY_SIZE(server_setup_tokens); i++)
+  {
+    // just to make things nicer :)
+    if (server_setup_tokens[i].value == &setup.use_api_server)
+      fprintf(file, "\n");
+
+    fprintf(file, "%s\n", getSetupLine(server_setup_tokens, "", i));
+  }
 
   fclose(file);
 
@@ -9892,8 +11317,10 @@ void SaveSetup_EditorCascade(void)
 
   if (!(file = fopen(filename, MODE_WRITE)))
   {
-    Error(ERR_WARN, "cannot write editor cascade state file '%s'", filename);
+    Warn("cannot write editor cascade state file '%s'", filename);
+
     free(filename);
+
     return;
   }
 
@@ -9909,6 +11336,14 @@ void SaveSetup_EditorCascade(void)
   free(filename);
 }
 
+void SaveSetup(void)
+{
+  SaveSetup_Default();
+  SaveSetup_AutoSetup();
+  SaveSetup_ServerSetup();
+  SaveSetup_EditorCascade();
+}
+
 static void SaveSetup_WriteGameControllerMappings(SetupFileHash *mappings_hash,
                                                  char *filename)
 {
@@ -9916,7 +11351,7 @@ static void SaveSetup_WriteGameControllerMappings(SetupFileHash *mappings_hash,
 
   if (!(file = fopen(filename, MODE_WRITE)))
   {
-    Error(ERR_WARN, "cannot write game controller mappings file '%s'",filename);
+    Warn("cannot write game controller mappings file '%s'", filename);
 
     return;
   }
@@ -9989,7 +11424,7 @@ static int getElementFromToken(char *token)
   if (value != NULL)
     return atoi(value);
 
-  Error(ERR_WARN, "unknown element token '%s'", token);
+  Warn("unknown element token '%s'", token);
 
   return EL_UNDEFINED;
 }
@@ -10096,8 +11531,9 @@ static boolean string_has_parameter(char *s, char *s_contained)
     char next_char = s[strlen(s_contained)];
 
     // check if next character is delimiter or whitespace
-    return (next_char == ',' || next_char == '\0' ||
-           next_char == ' ' || next_char == '\t' ? TRUE : FALSE);
+    if (next_char == ',' || next_char == '\0' ||
+       next_char == ' ' || next_char == '\t')
+      return TRUE;
   }
 
   // check if string contains another parameter string after a comma
@@ -10115,6 +11551,85 @@ static boolean string_has_parameter(char *s, char *s_contained)
   return string_has_parameter(substring, s_contained);
 }
 
+static int get_anim_parameter_value_ce(char *s)
+{
+  char *s_ptr = s;
+  char *pattern_1 = "ce_change:custom_";
+  char *pattern_2 = ".page_";
+  int pattern_1_len = strlen(pattern_1);
+  char *matching_char = strstr(s_ptr, pattern_1);
+  int result = ANIM_EVENT_NONE;
+
+  if (matching_char == NULL)
+    return ANIM_EVENT_NONE;
+
+  result = ANIM_EVENT_CE_CHANGE;
+
+  s_ptr = matching_char + pattern_1_len;
+
+  // check for custom element number ("custom_X", "custom_XX" or "custom_XXX")
+  if (*s_ptr >= '0' && *s_ptr <= '9')
+  {
+    int gic_ce_nr = (*s_ptr++ - '0');
+
+    if (*s_ptr >= '0' && *s_ptr <= '9')
+    {
+      gic_ce_nr = 10 * gic_ce_nr + (*s_ptr++ - '0');
+
+      if (*s_ptr >= '0' && *s_ptr <= '9')
+       gic_ce_nr = 10 * gic_ce_nr + (*s_ptr++ - '0');
+    }
+
+    if (gic_ce_nr < 1 || gic_ce_nr > NUM_CUSTOM_ELEMENTS)
+      return ANIM_EVENT_NONE;
+
+    // custom element stored as 0 to 255
+    gic_ce_nr--;
+
+    result |= gic_ce_nr << ANIM_EVENT_CE_BIT;
+  }
+  else
+  {
+    // invalid custom element number specified
+
+    return ANIM_EVENT_NONE;
+  }
+
+  // check for change page number ("page_X" or "page_XX") (optional)
+  if (strPrefix(s_ptr, pattern_2))
+  {
+    s_ptr += strlen(pattern_2);
+
+    if (*s_ptr >= '0' && *s_ptr <= '9')
+    {
+      int gic_page_nr = (*s_ptr++ - '0');
+
+      if (*s_ptr >= '0' && *s_ptr <= '9')
+       gic_page_nr = 10 * gic_page_nr + (*s_ptr++ - '0');
+
+      if (gic_page_nr < 1 || gic_page_nr > MAX_CHANGE_PAGES)
+       return ANIM_EVENT_NONE;
+
+      // change page stored as 1 to 32 (0 means "all change pages")
+
+      result |= gic_page_nr << ANIM_EVENT_PAGE_BIT;
+    }
+    else
+    {
+      // invalid animation part number specified
+
+      return ANIM_EVENT_NONE;
+    }
+  }
+
+  // discard result if next character is neither delimiter nor whitespace
+  if (!(*s_ptr == ',' || *s_ptr == '\0' ||
+       *s_ptr == ' ' || *s_ptr == '\t'))
+    return ANIM_EVENT_NONE;
+
+  return result;
+}
+
 static int get_anim_parameter_value(char *s)
 {
   int event_value[] =
@@ -10140,6 +11655,11 @@ static int get_anim_parameter_value(char *s)
   int result = ANIM_EVENT_NONE;
   int i;
 
+  result = get_anim_parameter_value_ce(s);
+
+  if (result != ANIM_EVENT_NONE)
+    return result;
+
   for (i = 0; i < ARRAY_SIZE(event_value); i++)
   {
     matching_char = strstr(s_ptr, pattern_1[i]);
@@ -10269,6 +11789,18 @@ static int get_anim_action_parameter_value(char *token)
       result = -(int)key;
   }
 
+  if (result == -1)
+  {
+    if (isURL(token))
+    {
+      result = get_hash_from_key(token);       // unsigned int => int
+      result = ABS(result);                    // may be negative now
+      result += (result < MAX_IMAGE_FILES ? MAX_IMAGE_FILES : 0);
+
+      setHashEntry(anim_url_hash, int2str(result, 0), token);
+    }
+  }
+
   if (result == -1)
     result = ANIM_EVENT_ACTION_NONE;
 
@@ -10297,6 +11829,8 @@ int get_parameter_value(char *value_raw, char *suffix, int type)
              strEqual(value, "lower")  ? POS_LOWER :
              strEqual(value, "bottom") ? POS_BOTTOM :
              strEqual(value, "any")    ? POS_ANY :
+             strEqual(value, "ce")     ? POS_CE :
+             strEqual(value, "ce_trigger") ? POS_CE_TRIGGER :
              strEqual(value, "last")   ? POS_LAST : POS_UNDEFINED);
   }
   else if (strEqual(suffix, ".align"))
@@ -10321,6 +11855,7 @@ int get_parameter_value(char *value_raw, char *suffix, int type)
              string_has_parameter(value, "pingpong")   ? ANIM_PINGPONG :
              string_has_parameter(value, "pingpong2")  ? ANIM_PINGPONG2 :
              string_has_parameter(value, "random")     ? ANIM_RANDOM :
+             string_has_parameter(value, "random_static") ? ANIM_RANDOM_STATIC :
              string_has_parameter(value, "ce_value")   ? ANIM_CE_VALUE :
              string_has_parameter(value, "ce_score")   ? ANIM_CE_SCORE :
              string_has_parameter(value, "ce_delay")   ? ANIM_CE_DELAY :
@@ -10328,6 +11863,8 @@ int get_parameter_value(char *value_raw, char *suffix, int type)
              string_has_parameter(value, "vertical")   ? ANIM_VERTICAL :
              string_has_parameter(value, "centered")   ? ANIM_CENTERED :
              string_has_parameter(value, "all")        ? ANIM_ALL :
+             string_has_parameter(value, "tiled")      ? ANIM_TILED :
+             string_has_parameter(value, "level_nr")   ? ANIM_LEVEL_NR :
              ANIM_DEFAULT);
 
     if (string_has_parameter(value, "once"))
@@ -10373,6 +11910,9 @@ int get_parameter_value(char *value_raw, char *suffix, int type)
     if (string_has_parameter(value, "reverse"))
       result |= STYLE_REVERSE;
 
+    if (string_has_parameter(value, "leftmost_position"))
+      result |= STYLE_LEFTMOST_POSITION;
+
     if (string_has_parameter(value, "block_clicks"))
       result |= STYLE_BLOCK;
 
@@ -10381,11 +11921,16 @@ int get_parameter_value(char *value_raw, char *suffix, int type)
 
     if (string_has_parameter(value, "multiple_actions"))
       result |= STYLE_MULTIPLE_ACTIONS;
+
+    if (string_has_parameter(value, "consume_ce_event"))
+      result |= STYLE_CONSUME_CE_EVENT;
   }
   else if (strEqual(suffix, ".fade_mode"))
   {
     result = (string_has_parameter(value, "none")      ? FADE_MODE_NONE :
              string_has_parameter(value, "fade")       ? FADE_MODE_FADE :
+             string_has_parameter(value, "fade_in")    ? FADE_MODE_FADE_IN :
+             string_has_parameter(value, "fade_out")   ? FADE_MODE_FADE_OUT :
              string_has_parameter(value, "crossfade")  ? FADE_MODE_CROSSFADE :
              string_has_parameter(value, "melt")       ? FADE_MODE_MELT :
              string_has_parameter(value, "curtain")    ? FADE_MODE_CURTAIN :
@@ -10432,14 +11977,18 @@ static int get_token_parameter_value(char *token, char *value_raw)
   return get_parameter_value(value_raw, suffix, TYPE_INTEGER);
 }
 
-void InitMenuDesignSettings_Static(void)
+void InitMenuDesignSettings_FromHash(SetupFileHash *setup_file_hash,
+                                    boolean ignore_defaults)
 {
   int i;
 
-  // always start with reliable default values from static default config
   for (i = 0; image_config_vars[i].token != NULL; i++)
   {
-    char *value = getHashEntry(image_config_hash, image_config_vars[i].token);
+    char *value = getHashEntry(setup_file_hash, image_config_vars[i].token);
+
+    // (ignore definitions set to "[DEFAULT]" which are already initialized)
+    if (ignore_defaults && strEqual(value, ARG_DEFAULT))
+      continue;
 
     if (value != NULL)
       *image_config_vars[i].value =
@@ -10447,6 +11996,12 @@ void InitMenuDesignSettings_Static(void)
   }
 }
 
+void InitMenuDesignSettings_Static(void)
+{
+  // always start with reliable default values from static default config
+  InitMenuDesignSettings_FromHash(image_config_hash, FALSE);
+}
+
 static void InitMenuDesignSettings_SpecialPreProcessing(void)
 {
   int i;
@@ -10652,7 +12207,7 @@ static void InitMenuDesignSettings_SpecialPostProcessing(void)
       vp_playfield->width = MIN(vp_playfield->width, vp_playfield->max_width);
 
     if (vp_playfield->max_height != -1)
-      vp_playfield->height = MIN(vp_playfield->height,vp_playfield->max_height);
+      vp_playfield->height = MIN(vp_playfield->height, vp_playfield->max_height);
 
     // adjust playfield position according to specified alignment
 
@@ -10887,6 +12442,45 @@ static void InitMenuDesignSettings_SpecialPostProcessing_AfterGraphics(void)
   }
 }
 
+static void InitMenuDesignSettings_PreviewPlayers_Ext(SetupFileHash *hash,
+                                                      boolean initialize)
+{
+  // special case: check if network and preview player positions are redefined,
+  // to compare this later against the main menu level preview being redefined
+  struct TokenIntPtrInfo menu_config_players[] =
+  {
+    { "main.network_players.x",        &menu.main.network_players.redefined    },
+    { "main.network_players.y",        &menu.main.network_players.redefined    },
+    { "main.preview_players.x",        &menu.main.preview_players.redefined    },
+    { "main.preview_players.y",        &menu.main.preview_players.redefined    },
+    { "preview.x",             &preview.redefined                      },
+    { "preview.y",             &preview.redefined                      }
+  };
+  int i;
+
+  if (initialize)
+  {
+    for (i = 0; i < ARRAY_SIZE(menu_config_players); i++)
+      *menu_config_players[i].value = FALSE;
+  }
+  else
+  {
+    for (i = 0; i < ARRAY_SIZE(menu_config_players); i++)
+      if (getHashEntry(hash, menu_config_players[i].token) != NULL)
+        *menu_config_players[i].value = TRUE;
+  }
+}
+
+static void InitMenuDesignSettings_PreviewPlayers(void)
+{
+  InitMenuDesignSettings_PreviewPlayers_Ext(NULL, TRUE);
+}
+
+static void InitMenuDesignSettings_PreviewPlayers_FromHash(SetupFileHash *hash)
+{
+  InitMenuDesignSettings_PreviewPlayers_Ext(hash, FALSE);
+}
+
 static void LoadMenuDesignSettingsFromFilename(char *filename)
 {
   static struct TitleFadingInfo tfi;
@@ -11021,7 +12615,9 @@ static void LoadMenuDesignSettingsFromFilename(char *filename)
     {
       { "menu.draw_xoffset.INFO",      &menu.draw_xoffset_info[i]      },
       { "menu.draw_yoffset.INFO",      &menu.draw_yoffset_info[i]      },
-      { "menu.list_size.INFO",         &menu.list_size_info[i]         }
+      { "menu.list_size.INFO",         &menu.list_size_info[i]         },
+      { "menu.list_entry_size.INFO",   &menu.list_entry_size_info[i]   },
+      { "menu.tile_size.INFO",         &menu.tile_size_info[i]         }
     };
 
     for (j = 0; j < ARRAY_SIZE(menu_config); j++)
@@ -11061,6 +12657,7 @@ static void LoadMenuDesignSettingsFromFilename(char *filename)
     struct TokenIntPtrInfo menu_config[] =
     {
       { "menu.left_spacing.INFO",      &menu.left_spacing_info[i]      },
+      { "menu.middle_spacing.INFO",    &menu.middle_spacing_info[i]    },
       { "menu.right_spacing.INFO",     &menu.right_spacing_info[i]     },
       { "menu.top_spacing.INFO",       &menu.top_spacing_info[i]       },
       { "menu.bottom_spacing.INFO",    &menu.bottom_spacing_info[i]    },
@@ -11225,35 +12822,11 @@ static void LoadMenuDesignSettingsFromFilename(char *filename)
     }
   }
 
-  // special case: check if network and preview player positions are redefined,
-  // to compare this later against the main menu level preview being redefined
-  struct TokenIntPtrInfo menu_config_players[] =
-  {
-    { "main.network_players.x",        &menu.main.network_players.redefined    },
-    { "main.network_players.y",        &menu.main.network_players.redefined    },
-    { "main.preview_players.x",        &menu.main.preview_players.redefined    },
-    { "main.preview_players.y",        &menu.main.preview_players.redefined    },
-    { "preview.x",             &preview.redefined                      },
-    { "preview.y",             &preview.redefined                      }
-  };
-
-  for (i = 0; i < ARRAY_SIZE(menu_config_players); i++)
-    *menu_config_players[i].value = FALSE;
-
-  for (i = 0; i < ARRAY_SIZE(menu_config_players); i++)
-    if (getHashEntry(setup_file_hash, menu_config_players[i].token) != NULL)
-      *menu_config_players[i].value = TRUE;
-
   // read (and overwrite with) values that may be specified in config file
-  for (i = 0; image_config_vars[i].token != NULL; i++)
-  {
-    char *value = getHashEntry(setup_file_hash, image_config_vars[i].token);
+  InitMenuDesignSettings_FromHash(setup_file_hash, TRUE);
 
-    // (ignore definitions set to "[DEFAULT]" which are already initialized)
-    if (value != NULL && !strEqual(value, ARG_DEFAULT))
-      *image_config_vars[i].value =
-       get_token_parameter_value(image_config_vars[i].token, value);
-  }
+  // special case: check if network and preview player positions are redefined
+  InitMenuDesignSettings_PreviewPlayers_FromHash(setup_file_hash);
 
   freeSetupFileHash(setup_file_hash);
 }
@@ -11264,6 +12837,7 @@ void LoadMenuDesignSettings(void)
 
   InitMenuDesignSettings_Static();
   InitMenuDesignSettings_SpecialPreProcessing();
+  InitMenuDesignSettings_PreviewPlayers();
 
   if (!GFX_OVERRIDE_ARTWORK(ARTWORK_TYPE_GRAPHICS))
   {
@@ -11342,19 +12916,19 @@ void LoadUserDefinedEditorElementList(int **elements, int *num_elements)
     {
       if (num_unknown_tokens == 0)
       {
-       Error(ERR_INFO_LINE, "-");
-       Error(ERR_INFO, "warning: unknown token(s) found in config file:");
-       Error(ERR_INFO, "- config file: '%s'", filename);
+       Warn("---");
+       Warn("unknown token(s) found in config file:");
+       Warn("- config file: '%s'", filename);
 
        num_unknown_tokens++;
       }
 
-      Error(ERR_INFO, "- token: '%s'", list->token);
+      Warn("- token: '%s'", list->token);
     }
   }
 
   if (num_unknown_tokens > 0)
-    Error(ERR_INFO_LINE, "-");
+    Warn("---");
 
   while (*num_elements % 4)    // pad with empty elements, if needed
     (*elements)[(*num_elements)++] = EL_EMPTY;
@@ -11364,8 +12938,8 @@ void LoadUserDefinedEditorElementList(int **elements, int *num_elements)
 
 #if 0
   for (i = 0; i < *num_elements; i++)
-    printf("editor: element '%s' [%d]\n",
-          element_info[(*elements)[i]].token_name, (*elements)[i]);
+    Debug("editor", "element '%s' [%d]\n",
+         element_info[(*elements)[i]].token_name, (*elements)[i]);
 #endif
 }
 
@@ -11386,11 +12960,13 @@ static struct MusicFileInfo *get_music_file_info_ext(char *basename, int music,
     { "artist_header", &tmp_music_file_info.artist_header      },
     { "album_header",  &tmp_music_file_info.album_header       },
     { "year_header",   &tmp_music_file_info.year_header        },
+    { "played_header", &tmp_music_file_info.played_header      },
 
     { "title",         &tmp_music_file_info.title              },
     { "artist",                &tmp_music_file_info.artist             },
     { "album",         &tmp_music_file_info.album              },
     { "year",          &tmp_music_file_info.year               },
+    { "played",                &tmp_music_file_info.played             },
 
     { NULL,            NULL                                    },
   };
@@ -11486,14 +13062,12 @@ static boolean sound_info_listed(struct MusicFileInfo *list, char *basename)
 
 void LoadMusicInfo(void)
 {
-  char *music_directory = getCustomMusicDirectory();
+  int num_music_noconf = getMusicListSize_NoConf();
   int num_music = getMusicListSize();
-  int num_music_noconf = 0;
   int num_sounds = getSoundListSize();
-  Directory *dir;
-  DirectoryEntry *dir_entry;
   struct FileInfo *music, *sound;
   struct MusicFileInfo *next, **new;
+
   int i;
 
   while (music_file_info != NULL)
@@ -11506,11 +13080,13 @@ void LoadMusicInfo(void)
     checked_free(music_file_info->artist_header);
     checked_free(music_file_info->album_header);
     checked_free(music_file_info->year_header);
+    checked_free(music_file_info->played_header);
 
     checked_free(music_file_info->title);
     checked_free(music_file_info->artist);
     checked_free(music_file_info->album);
     checked_free(music_file_info->year);
+    checked_free(music_file_info->played);
 
     free(music_file_info);
 
@@ -11519,75 +13095,68 @@ void LoadMusicInfo(void)
 
   new = &music_file_info;
 
-  for (i = 0; i < num_music; i++)
+  // get (configured or unconfigured) music file info for all levels
+  for (i = leveldir_current->first_level;
+       i <= leveldir_current->last_level; i++)
   {
-    music = getMusicListEntry(i);
+    int music_nr;
 
-    if (music->filename == NULL)
-      continue;
+    if (levelset.music[i] != MUS_UNDEFINED)
+    {
+      // get music file info for configured level music
+      music_nr = levelset.music[i];
+    }
+    else if (num_music_noconf > 0)
+    {
+      // get music file info for unconfigured level music
+      int level_pos = i - leveldir_current->first_level;
 
-    if (strEqual(music->filename, UNDEFINED_FILENAME))
+      music_nr = MAP_NOCONF_MUSIC(level_pos % num_music_noconf);
+    }
+    else
+    {
       continue;
+    }
 
-    // a configured file may be not recognized as music
-    if (!FileIsMusic(music->filename))
+    char *basename = getMusicInfoEntryFilename(music_nr);
+
+    if (basename == NULL)
       continue;
 
-    if (!music_info_listed(music_file_info, music->filename))
+    if (!music_info_listed(music_file_info, basename))
     {
-      *new = get_music_file_info(music->filename, i);
+      *new = get_music_file_info(basename, music_nr);
 
       if (*new != NULL)
        new = &(*new)->next;
     }
   }
 
-  if ((dir = openDirectory(music_directory)) == NULL)
-  {
-    Error(ERR_WARN, "cannot read music directory '%s'", music_directory);
-    return;
-  }
-
-  while ((dir_entry = readDirectory(dir)) != NULL)     // loop all entries
+  // get music file info for all remaining configured music files
+  for (i = 0; i < num_music; i++)
   {
-    char *basename = dir_entry->basename;
-    boolean music_already_used = FALSE;
-    int i;
-
-    // skip all music files that are configured in music config file
-    for (i = 0; i < num_music; i++)
-    {
-      music = getMusicListEntry(i);
-
-      if (music->filename == NULL)
-       continue;
+    music = getMusicListEntry(i);
 
-      if (strEqual(basename, music->filename))
-      {
-       music_already_used = TRUE;
-       break;
-      }
-    }
+    if (music->filename == NULL)
+      continue;
 
-    if (music_already_used)
+    if (strEqual(music->filename, UNDEFINED_FILENAME))
       continue;
 
-    if (!FileIsMusic(dir_entry->filename))
+    // a configured file may be not recognized as music
+    if (!FileIsMusic(music->filename))
       continue;
 
-    if (!music_info_listed(music_file_info, basename))
+    if (!music_info_listed(music_file_info, music->filename))
     {
-      *new = get_music_file_info(basename, MAP_NOCONF_MUSIC(num_music_noconf));
+      *new = get_music_file_info(music->filename, i);
 
       if (*new != NULL)
        new = &(*new)->next;
     }
-
-    num_music_noconf++;
   }
 
-  closeDirectory(dir);
-
+  // get sound file info for all configured sound files
   for (i = 0; i < num_sounds; i++)
   {
     sound = getSoundListEntry(i);
@@ -11609,6 +13178,18 @@ void LoadMusicInfo(void)
        new = &(*new)->next;
     }
   }
+
+  // add pointers to previous list nodes
+
+  struct MusicFileInfo *node = music_file_info;
+
+  while (node != NULL)
+  {
+    if (node->next)
+      node->next->prev = node;
+
+    node = node->next;
+  }
 }
 
 static void add_helpanim_entry(int element, int action, int direction,
@@ -11632,18 +13213,18 @@ static void print_unknown_token(char *filename, char *token, int token_nr)
 {
   if (token_nr == 0)
   {
-    Error(ERR_INFO_LINE, "-");
-    Error(ERR_INFO, "warning: unknown token(s) found in config file:");
-    Error(ERR_INFO, "- config file: '%s'", filename);
+    Warn("---");
+    Warn("unknown token(s) found in config file:");
+    Warn("- config file: '%s'", filename);
   }
 
-  Error(ERR_INFO, "- token: '%s'", token);
+  Warn("- token: '%s'", token);
 }
 
 static void print_unknown_token_end(int token_nr)
 {
   if (token_nr > 0)
-    Error(ERR_INFO_LINE, "-");
+    Warn("---");
 }
 
 void LoadHelpAnimInfo(void)
@@ -11847,12 +13428,12 @@ void LoadHelpAnimInfo(void)
 
 #if 0
   for (i = 0; i < num_list_entries; i++)
-    printf("::: '%s': %d, %d, %d => %d\n",
-          EL_NAME(helpanim_info[i].element),
-          helpanim_info[i].element,
-          helpanim_info[i].action,
-          helpanim_info[i].direction,
-          helpanim_info[i].delay);
+    Debug("files:LoadHelpAnimInfo", "'%s': %d, %d, %d => %d",
+         EL_NAME(helpanim_info[i].element),
+         helpanim_info[i].element,
+         helpanim_info[i].action,
+         helpanim_info[i].direction,
+         helpanim_info[i].delay);
 #endif
 }
 
@@ -11884,8 +13465,8 @@ void LoadHelpTextInfo(void)
 #if 0
   BEGIN_HASH_ITERATION(helptext_info, itr)
   {
-    printf("::: '%s' => '%s'\n",
-          HASH_ITERATION_TOKEN(itr), HASH_ITERATION_VALUE(itr));
+    Debug("files:LoadHelpTextInfo", "'%s' => '%s'",
+         HASH_ITERATION_TOKEN(itr), HASH_ITERATION_VALUE(itr));
   }
   END_HASH_ITERATION(hash, itr)
 #endif
@@ -11911,8 +13492,7 @@ void ConvertLevels(void)
                                               global.convert_leveldir);
 
   if (convert_leveldir == NULL)
-    Error(ERR_EXIT, "no such level identifier: '%s'",
-         global.convert_leveldir);
+    Fail("no such level identifier: '%s'", global.convert_leveldir);
 
   leveldir_current = convert_leveldir;
 
@@ -11955,6 +13535,11 @@ void ConvertLevels(void)
 
     Print("converting level ... ");
 
+#if 0
+    // special case: conversion of some EMC levels as requested by ACME
+    level.game_engine_type = GAME_ENGINE_TYPE_RND;
+#endif
+
     level_filename = getDefaultLevelFilename(level_nr);
     new_level = !fileExists(level_filename);
 
@@ -12032,20 +13617,20 @@ void CreateLevelSketchImages(void)
     sprintf(basename1, "%04d.bmp", i);
     sprintf(basename2, "%04ds.bmp", i);
 
-    filename1 = getPath2(global.create_images_dir, basename1);
-    filename2 = getPath2(global.create_images_dir, basename2);
+    filename1 = getPath2(global.create_sketch_images_dir, basename1);
+    filename2 = getPath2(global.create_sketch_images_dir, basename2);
 
     DrawSizedElement(0, 0, element, TILESIZE);
     BlitBitmap(drawto, bitmap1, SX, SY, TILEX, TILEY, 0, 0);
 
     if (SDL_SaveBMP(bitmap1->surface, filename1) != 0)
-      Error(ERR_EXIT, "cannot save level sketch image file '%s'", filename1);
+      Fail("cannot save level sketch image file '%s'", filename1);
 
     DrawSizedElement(0, 0, element, MINI_TILESIZE);
     BlitBitmap(drawto, bitmap2, SX, SY, MINI_TILEX, MINI_TILEY, 0, 0);
 
     if (SDL_SaveBMP(bitmap2->surface, filename2) != 0)
-      Error(ERR_EXIT, "cannot save level sketch image file '%s'", filename2);
+      Fail("cannot save level sketch image file '%s'", filename2);
 
     free(filename1);
     free(filename2);
@@ -12071,7 +13656,136 @@ void CreateLevelSketchImages(void)
   if (options.debug)
     fprintf(stderr, "\n");
 
-  Error(ERR_INFO, "%d normal and small images created", NUM_FILE_ELEMENTS);
+  Info("%d normal and small images created", NUM_FILE_ELEMENTS);
+
+  CloseAllAndExit(0);
+}
+
+
+// ----------------------------------------------------------------------------
+// create and save images for element collecting animations (raw BMP format)
+// ----------------------------------------------------------------------------
+
+static boolean createCollectImage(int element)
+{
+  return (IS_COLLECTIBLE(element) && !IS_SP_ELEMENT(element));
+}
+
+void CreateCollectElementImages(void)
+{
+  int i, j;
+  int num_steps = 8;
+  int anim_frames = num_steps - 1;
+  int tile_size = TILESIZE;
+  int anim_width  = tile_size * anim_frames;
+  int anim_height = tile_size;
+  int num_collect_images = 0;
+  int pos_collect_images = 0;
+
+  for (i = 0; i < MAX_NUM_ELEMENTS; i++)
+    if (createCollectImage(i))
+      num_collect_images++;
+
+  Info("Creating %d element collecting animation images ...",
+       num_collect_images);
+
+  int dst_width  = anim_width * 2;
+  int dst_height = anim_height * num_collect_images / 2;
+  Bitmap *dst_bitmap = CreateBitmap(dst_width, dst_height, DEFAULT_DEPTH);
+  char *basename_bmp = "RocksCollect.bmp";
+  char *basename_png = "RocksCollect.png";
+  char *filename_bmp = getPath2(global.create_collect_images_dir, basename_bmp);
+  char *filename_png = getPath2(global.create_collect_images_dir, basename_png);
+  int len_filename_bmp = strlen(filename_bmp);
+  int len_filename_png = strlen(filename_png);
+  int max_command_len = MAX_FILENAME_LEN + len_filename_bmp + len_filename_png;
+  char cmd_convert[max_command_len];
+
+  snprintf(cmd_convert, max_command_len, "convert \"%s\" \"%s\"",
+          filename_bmp,
+          filename_png);
+
+  // force using RGBA surface for destination bitmap
+  SDL_SetColorKey(dst_bitmap->surface, SET_TRANSPARENT_PIXEL,
+                 SDL_MapRGB(dst_bitmap->surface->format, 0x00, 0x00, 0x00));
+
+  dst_bitmap->surface =
+    SDL_ConvertSurfaceFormat(dst_bitmap->surface, SDL_PIXELFORMAT_ARGB8888, 0);
+
+  for (i = 0; i < MAX_NUM_ELEMENTS; i++)
+  {
+    if (!createCollectImage(i))
+      continue;
+
+    int dst_x = (pos_collect_images / (num_collect_images / 2)) * anim_width;
+    int dst_y = (pos_collect_images % (num_collect_images / 2)) * anim_height;
+    int graphic = el2img(i);
+    char *token_name = element_info[i].token_name;
+    Bitmap *tmp_bitmap = CreateBitmap(tile_size, tile_size, DEFAULT_DEPTH);
+    Bitmap *src_bitmap;
+    int src_x, src_y;
+
+    Info("- creating collecting image for '%s' ...", token_name);
+
+    getGraphicSource(graphic, 0, &src_bitmap, &src_x, &src_y);
+
+    BlitBitmap(src_bitmap, tmp_bitmap, src_x, src_y,
+              tile_size, tile_size, 0, 0);
+
+    // force using RGBA surface for temporary bitmap (using transparent black)
+    SDL_SetColorKey(tmp_bitmap->surface, SET_TRANSPARENT_PIXEL,
+                   SDL_MapRGB(tmp_bitmap->surface->format, 0x00, 0x00, 0x00));
+
+    tmp_bitmap->surface =
+      SDL_ConvertSurfaceFormat(tmp_bitmap->surface, SDL_PIXELFORMAT_ARGB8888, 0);
+
+    tmp_bitmap->surface_masked = tmp_bitmap->surface;
+
+    for (j = 0; j < anim_frames; j++)
+    {
+      int frame_size_final = tile_size * (anim_frames - j) / num_steps;
+      int frame_size = frame_size_final * num_steps;
+      int offset = (tile_size - frame_size_final) / 2;
+      Bitmap *frame_bitmap = ZoomBitmap(tmp_bitmap, frame_size, frame_size);
+
+      while (frame_size > frame_size_final)
+      {
+       frame_size /= 2;
+
+       Bitmap *half_bitmap = ZoomBitmap(frame_bitmap, frame_size, frame_size);
+
+       FreeBitmap(frame_bitmap);
+
+       frame_bitmap = half_bitmap;
+      }
+
+      BlitBitmapMasked(frame_bitmap, dst_bitmap, 0, 0,
+                      frame_size_final, frame_size_final,
+                      dst_x + j * tile_size + offset, dst_y + offset);
+
+      FreeBitmap(frame_bitmap);
+    }
+
+    tmp_bitmap->surface_masked = NULL;
+
+    FreeBitmap(tmp_bitmap);
+
+    pos_collect_images++;
+  }
+
+  if (SDL_SaveBMP(dst_bitmap->surface, filename_bmp) != 0)
+    Fail("cannot save element collecting image file '%s'", filename_bmp);
+
+  FreeBitmap(dst_bitmap);
+
+  Info("Converting image file from BMP to PNG ...");
+
+  if (system(cmd_convert) != 0)
+    Fail("converting image file failed");
+
+  unlink(filename_bmp);
+
+  Info("Done.");
 
   CloseAllAndExit(0);
 }
@@ -12169,7 +13883,7 @@ void CreateCustomElementImages(char *directory)
   }
 
   if (SDL_SaveBMP(bitmap->surface, dst_filename) != 0)
-    Error(ERR_EXIT, "cannot save CE graphics file '%s'", dst_filename);
+    Fail("cannot save CE graphics file '%s'", dst_filename);
 
   FreeBitmap(bitmap);