fixed undefined signed integer overflow for expression "1 << 31"
[rocksndiamonds.git] / src / files.c
index 9f20487fb0e4f5aebb8bd2108b2f7e19040db114..5d8f6479387a22bd0ec5c25424e865265bc37418 100644 (file)
@@ -4,7 +4,7 @@
 // (c) 1995-2014 by Artsoft Entertainment
 //                         Holger Schemel
 //                 info@artsoft.org
-//                 http://www.artsoft.org/
+//                 https://www.artsoft.org/
 // ----------------------------------------------------------------------------
 // files.c
 // ============================================================================
@@ -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 2       // 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
@@ -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)
   {
@@ -768,9 +794,15 @@ static struct LevelFileConfigInfo chunk_config_ELEM[] =
     &li.android_clone_time,            10
   },
   {
-    EL_EMC_ANDROID,                    -1,
+    EL_EMC_ANDROID,                    SAVE_CONF_NEVER,
     TYPE_ELEMENT_LIST,                 CONF_VALUE_BYTES(1),
     &li.android_clone_element[0],      EL_EMPTY, NULL,
+    &li.num_android_clone_elements,    1, MAX_ANDROID_ELEMENTS_OLD
+  },
+  {
+    EL_EMC_ANDROID,                    -1,
+    TYPE_ELEMENT_LIST,                 CONF_VALUE_BYTES(2),
+    &li.android_clone_element[0],      EL_EMPTY, NULL,
     &li.num_android_clone_elements,    1, MAX_ANDROID_ELEMENTS
   },
 
@@ -809,7 +841,7 @@ static struct LevelFileConfigInfo chunk_config_ELEM[] =
   {
     EL_EMC_MAGIC_BALL,                 -1,
     TYPE_BOOLEAN,                      CONF_VALUE_8_BIT(2),
-    &li.ball_state_initial,            FALSE
+    &li.ball_active_initial,           FALSE
   },
   {
     EL_EMC_MAGIC_BALL,                 -1,
@@ -1057,6 +1089,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,
@@ -1333,6 +1377,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)
 {
   {
@@ -1850,6 +1914,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;
@@ -2064,7 +2138,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;
   }
@@ -2328,7 +2402,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
 
@@ -2369,7 +2443,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;
       }
@@ -2617,7 +2691,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;
@@ -2677,7 +2751,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)
@@ -2710,7 +2784,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;
@@ -2738,7 +2812,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;
     }
@@ -2771,9 +2845,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));
@@ -2824,9 +2899,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;
   }
 
@@ -2908,7 +2984,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));
@@ -2949,7 +3025,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;
   }
 
@@ -2972,9 +3048,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;
   }
 
@@ -3036,9 +3113,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;
        }
@@ -3047,8 +3123,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
 
@@ -3117,7 +3192,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;
 
@@ -3139,9 +3214,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;
@@ -3258,8 +3333,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;
 
@@ -3279,6 +3354,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
@@ -3308,6 +3387,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
 
@@ -3328,6 +3410,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)
@@ -3346,7 +3452,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;
@@ -3373,7 +3479,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);
 
@@ -3392,7 +3498,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);
 
@@ -3403,7 +3509,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);
 
@@ -3450,6 +3556,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 }
     };
@@ -3464,15 +3571,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
@@ -3481,13 +3590,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;
        }
       }
     }
@@ -3515,118 +3634,98 @@ static void CopyNativeLevel_RND_to_EM(struct LevelInfo *level)
     { 2, 2 },
   };
   struct LevelInfo_EM *level_em = level->native_em_level;
-  struct LEVEL *lev = level_em->lev;
-  struct PLAYER **ply = level_em->ply;
+  struct CAVE *cav = level_em->cav;
   int i, j, x, y;
 
-  lev->width  = MIN(level->fieldx, EM_MAX_CAVE_WIDTH);
-  lev->height = MIN(level->fieldy, EM_MAX_CAVE_HEIGHT);
+  cav->width  = MIN(level->fieldx, MAX_PLAYFIELD_WIDTH);
+  cav->height = MIN(level->fieldy, MAX_PLAYFIELD_HEIGHT);
 
-  lev->time_seconds     = level->time;
-  lev->required_initial = level->gems_needed;
+  cav->time_seconds    = level->time;
+  cav->gems_needed     = level->gems_needed;
 
-  lev->emerald_score   = level->score[SC_EMERALD];
-  lev->diamond_score   = level->score[SC_DIAMOND];
-  lev->alien_score     = level->score[SC_ROBOT];
-  lev->tank_score      = level->score[SC_SPACESHIP];
-  lev->bug_score       = level->score[SC_BUG];
-  lev->eater_score     = level->score[SC_YAMYAM];
-  lev->nut_score       = level->score[SC_NUT];
-  lev->dynamite_score  = level->score[SC_DYNAMITE];
-  lev->key_score       = level->score[SC_KEY];
-  lev->exit_score      = level->score[SC_TIME_BONUS];
+  cav->emerald_score   = level->score[SC_EMERALD];
+  cav->diamond_score   = level->score[SC_DIAMOND];
+  cav->alien_score     = level->score[SC_ROBOT];
+  cav->tank_score      = level->score[SC_SPACESHIP];
+  cav->bug_score       = level->score[SC_BUG];
+  cav->eater_score     = level->score[SC_YAMYAM];
+  cav->nut_score       = level->score[SC_NUT];
+  cav->dynamite_score  = level->score[SC_DYNAMITE];
+  cav->key_score       = level->score[SC_KEY];
+  cav->exit_score      = level->score[SC_TIME_BONUS];
+
+  cav->num_eater_arrays        = level->num_yamyam_contents;
 
   for (i = 0; i < MAX_ELEMENT_CONTENTS; i++)
     for (y = 0; y < 3; y++)
       for (x = 0; x < 3; x++)
-       lev->eater_array[i][y * 3 + x] =
-         map_element_RND_to_EM(level->yamyam_content[i].e[x][y]);
+       cav->eater_array[i][y * 3 + x] =
+         map_element_RND_to_EM_cave(level->yamyam_content[i].e[x][y]);
 
-  lev->amoeba_time             = level->amoeba_speed;
-  lev->wonderwall_time_initial = level->time_magic_wall;
-  lev->wheel_time              = level->time_wheel;
+  cav->amoeba_time             = level->amoeba_speed;
+  cav->wonderwall_time         = level->time_magic_wall;
+  cav->wheel_time              = level->time_wheel;
 
-  lev->android_move_time       = level->android_move_time;
-  lev->android_clone_time      = level->android_clone_time;
-  lev->ball_random             = level->ball_random;
-  lev->ball_state_initial      = level->ball_state_initial;
-  lev->ball_time               = level->ball_time;
-  lev->num_ball_arrays         = level->num_ball_contents;
+  cav->android_move_time       = level->android_move_time;
+  cav->android_clone_time      = level->android_clone_time;
+  cav->ball_random             = level->ball_random;
+  cav->ball_active             = level->ball_active_initial;
+  cav->ball_time               = level->ball_time;
+  cav->num_ball_arrays         = level->num_ball_contents;
 
-  lev->lenses_score            = level->lenses_score;
-  lev->magnify_score           = level->magnify_score;
-  lev->slurp_score             = level->slurp_score;
+  cav->lenses_score            = level->lenses_score;
+  cav->magnify_score           = level->magnify_score;
+  cav->slurp_score             = level->slurp_score;
 
-  lev->lenses_time             = level->lenses_time;
-  lev->magnify_time            = level->magnify_time;
+  cav->lenses_time             = level->lenses_time;
+  cav->magnify_time            = level->magnify_time;
 
-  lev->wind_direction_initial =
+  cav->wind_direction =
     map_direction_RND_to_EM(level->wind_direction_initial);
-  lev->wind_cnt_initial = (level->wind_direction_initial != MV_NONE ?
-                          lev->wind_time : 0);
 
   for (i = 0; i < MAX_ELEMENT_CONTENTS; i++)
     for (j = 0; j < 8; j++)
-      lev->ball_array[i][j] =
-       map_element_RND_to_EM(level->
-                             ball_content[i].e[ball_xy[j][0]][ball_xy[j][1]]);
+      cav->ball_array[i][j] =
+       map_element_RND_to_EM_cave(level->ball_content[i].
+                                  e[ball_xy[j][0]][ball_xy[j][1]]);
 
   map_android_clone_elements_RND_to_EM(level);
 
-  // first fill the complete playfield with the default border element
+  // first fill the complete playfield with the empty space element
   for (y = 0; y < EM_MAX_CAVE_HEIGHT; y++)
     for (x = 0; x < EM_MAX_CAVE_WIDTH; x++)
-      level_em->cave[x][y] = ZBORDER;
-
-  if (BorderElement == EL_STEELWALL)
-  {
-    for (y = 0; y < lev->height + 2; y++)
-      for (x = 0; x < lev->width + 2; x++)
-       level_em->cave[x + 1][y + 1] = map_element_RND_to_EM(EL_STEELWALL);
-  }
+      cav->cave[x][y] = Cblank;
 
   // then copy the real level contents from level file into the playfield
-  for (y = 0; y < lev->height; y++) for (x = 0; x < lev->width; x++)
+  for (y = 0; y < cav->height; y++) for (x = 0; x < cav->width; x++)
   {
-    int new_element = map_element_RND_to_EM(level->field[x][y]);
-    int offset = (BorderElement == EL_STEELWALL ? 1 : 0);
-    int xx = x + 1 + offset;
-    int yy = y + 1 + offset;
+    int new_element = map_element_RND_to_EM_cave(level->field[x][y]);
 
     if (level->field[x][y] == EL_AMOEBA_DEAD)
-      new_element = map_element_RND_to_EM(EL_AMOEBA_WET);
+      new_element = map_element_RND_to_EM_cave(EL_AMOEBA_WET);
 
-    level_em->cave[xx][yy] = new_element;
+    cav->cave[x][y] = new_element;
   }
 
   for (i = 0; i < MAX_PLAYERS; i++)
   {
-    ply[i]->x_initial = 0;
-    ply[i]->y_initial = 0;
+    cav->player_x[i] = -1;
+    cav->player_y[i] = -1;
   }
 
   // initialize player positions and delete players from the playfield
-  for (y = 0; y < lev->height; y++) for (x = 0; x < lev->width; x++)
+  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]);
-      int offset = (BorderElement == EL_STEELWALL ? 1 : 0);
-      int xx = x + 1 + offset;
-      int yy = y + 1 + offset;
 
-      ply[player_nr]->x_initial = xx;
-      ply[player_nr]->y_initial = yy;
+      cav->player_x[player_nr] = x;
+      cav->player_y[player_nr] = y;
 
-      level_em->cave[xx][yy] = map_element_RND_to_EM(EL_EMPTY);
+      cav->cave[x][y] = map_element_RND_to_EM_cave(EL_EMPTY);
     }
   }
-
-  if (BorderElement == EL_STEELWALL)
-  {
-    lev->width  += 2;
-    lev->height += 2;
-  }
 }
 
 static void CopyNativeLevel_EM_to_RND(struct LevelInfo *level)
@@ -3643,69 +3742,68 @@ static void CopyNativeLevel_EM_to_RND(struct LevelInfo *level)
     { 2, 2 },
   };
   struct LevelInfo_EM *level_em = level->native_em_level;
-  struct LEVEL *lev = level_em->lev;
-  struct PLAYER **ply = level_em->ply;
+  struct CAVE *cav = level_em->cav;
   int i, j, x, y;
 
-  level->fieldx = MIN(lev->width,  MAX_LEV_FIELDX);
-  level->fieldy = MIN(lev->height, MAX_LEV_FIELDY);
+  level->fieldx = MIN(cav->width,  MAX_LEV_FIELDX);
+  level->fieldy = MIN(cav->height, MAX_LEV_FIELDY);
 
-  level->time        = lev->time_seconds;
-  level->gems_needed = lev->required_initial;
+  level->time        = cav->time_seconds;
+  level->gems_needed = cav->gems_needed;
 
   sprintf(level->name, "Level %d", level->file_info.nr);
 
-  level->score[SC_EMERALD]     = lev->emerald_score;
-  level->score[SC_DIAMOND]     = lev->diamond_score;
-  level->score[SC_ROBOT]       = lev->alien_score;
-  level->score[SC_SPACESHIP]   = lev->tank_score;
-  level->score[SC_BUG]         = lev->bug_score;
-  level->score[SC_YAMYAM]      = lev->eater_score;
-  level->score[SC_NUT]         = lev->nut_score;
-  level->score[SC_DYNAMITE]    = lev->dynamite_score;
-  level->score[SC_KEY]         = lev->key_score;
-  level->score[SC_TIME_BONUS]  = lev->exit_score;
+  level->score[SC_EMERALD]     = cav->emerald_score;
+  level->score[SC_DIAMOND]     = cav->diamond_score;
+  level->score[SC_ROBOT]       = cav->alien_score;
+  level->score[SC_SPACESHIP]   = cav->tank_score;
+  level->score[SC_BUG]         = cav->bug_score;
+  level->score[SC_YAMYAM]      = cav->eater_score;
+  level->score[SC_NUT]         = cav->nut_score;
+  level->score[SC_DYNAMITE]    = cav->dynamite_score;
+  level->score[SC_KEY]         = cav->key_score;
+  level->score[SC_TIME_BONUS]  = cav->exit_score;
 
-  level->num_yamyam_contents = MAX_ELEMENT_CONTENTS;
+  level->num_yamyam_contents   = cav->num_eater_arrays;
 
-  for (i = 0; i < level->num_yamyam_contents; i++)
+  for (i = 0; i < MAX_ELEMENT_CONTENTS; i++)
     for (y = 0; y < 3; y++)
       for (x = 0; x < 3; x++)
        level->yamyam_content[i].e[x][y] =
-         map_element_EM_to_RND(lev->eater_array[i][y * 3 + x]);
+         map_element_EM_to_RND_cave(cav->eater_array[i][y * 3 + x]);
 
-  level->amoeba_speed          = lev->amoeba_time;
-  level->time_magic_wall       = lev->wonderwall_time_initial;
-  level->time_wheel            = lev->wheel_time;
+  level->amoeba_speed          = cav->amoeba_time;
+  level->time_magic_wall       = cav->wonderwall_time;
+  level->time_wheel            = cav->wheel_time;
 
-  level->android_move_time     = lev->android_move_time;
-  level->android_clone_time    = lev->android_clone_time;
-  level->ball_random           = lev->ball_random;
-  level->ball_state_initial    = lev->ball_state_initial;
-  level->ball_time             = lev->ball_time;
-  level->num_ball_contents     = lev->num_ball_arrays;
+  level->android_move_time     = cav->android_move_time;
+  level->android_clone_time    = cav->android_clone_time;
+  level->ball_random           = cav->ball_random;
+  level->ball_active_initial   = cav->ball_active;
+  level->ball_time             = cav->ball_time;
+  level->num_ball_contents     = cav->num_ball_arrays;
 
-  level->lenses_score          = lev->lenses_score;
-  level->magnify_score         = lev->magnify_score;
-  level->slurp_score           = lev->slurp_score;
+  level->lenses_score          = cav->lenses_score;
+  level->magnify_score         = cav->magnify_score;
+  level->slurp_score           = cav->slurp_score;
 
-  level->lenses_time           = lev->lenses_time;
-  level->magnify_time          = lev->magnify_time;
+  level->lenses_time           = cav->lenses_time;
+  level->magnify_time          = cav->magnify_time;
 
   level->wind_direction_initial =
-    map_direction_EM_to_RND(lev->wind_direction_initial);
+    map_direction_EM_to_RND(cav->wind_direction);
 
   for (i = 0; i < MAX_ELEMENT_CONTENTS; i++)
     for (j = 0; j < 8; j++)
       level->ball_content[i].e[ball_xy[j][0]][ball_xy[j][1]] =
-       map_element_EM_to_RND(lev->ball_array[i][j]);
+       map_element_EM_to_RND_cave(cav->ball_array[i][j]);
 
   map_android_clone_elements_EM_to_RND(level);
 
   // convert the playfield (some elements need special treatment)
   for (y = 0; y < level->fieldy; y++) for (x = 0; x < level->fieldx; x++)
   {
-    int new_element = map_element_EM_to_RND(level_em->cave[x + 1][y + 1]);
+    int new_element = map_element_EM_to_RND_cave(cav->cave[x][y]);
 
     if (new_element == EL_AMOEBA_WET && level->amoeba_speed == 0)
       new_element = EL_AMOEBA_DEAD;
@@ -3717,12 +3815,15 @@ static void CopyNativeLevel_EM_to_RND(struct LevelInfo *level)
   {
     // in case of all players set to the same field, use the first player
     int nr = MAX_PLAYERS - i - 1;
-    int jx = ply[nr]->x_initial - 1;
-    int jy = ply[nr]->y_initial - 1;
+    int jx = cav->player_x[nr];
+    int jy = cav->player_y[nr];
 
     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;
 }
 
 
@@ -3835,7 +3936,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);
       }
 
@@ -3844,8 +3945,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] =
@@ -3881,8 +3982,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;
     }
@@ -3892,7 +3992,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;
     }
@@ -3917,12 +4017,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++)
@@ -3958,8 +4057,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;
     }
@@ -4003,7 +4102,7 @@ static void CopyNativeTape_SP_to_RND(struct LevelInfo *level)
     int demo_repeat = (demo->data[i] & 0xf0) >> 4;
     int tape_action = map_key_SP_to_RND(demo_action);
     int tape_repeat = demo_repeat + 1;
-    byte action[MAX_PLAYERS] = { tape_action, 0, 0, 0 };
+    byte action[MAX_TAPE_ACTIONS] = { tape_action };
     boolean success = 0;
     int j;
 
@@ -4012,8 +4111,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;
     }
@@ -4288,7 +4387,7 @@ static int getMappedElement_DC(int element)
       break;
 
     case 0x13f5:
-      element = EL_YAMYAM;
+      element = EL_YAMYAM_UP;
       break;
 
     case 0x1425:
@@ -5314,7 +5413,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)
@@ -5322,7 +5421,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)
@@ -5330,7 +5429,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)
@@ -5338,7 +5437,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)
@@ -5569,7 +5668,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;
@@ -5617,7 +5717,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;
   }
@@ -5730,9 +5830,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,
@@ -5750,7 +5860,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;
   }
@@ -5768,8 +5878,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;
     }
@@ -5795,8 +5904,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;
        }
@@ -5814,8 +5922,7 @@ 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;
     }
@@ -5862,6 +5969,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)
@@ -5890,7 +6012,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;
   }
@@ -6064,7 +6186,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;
   }
@@ -6095,14 +6217,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;
   }
 }
@@ -6259,7 +6378,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)
@@ -6431,6 +6550,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)
@@ -6578,6 +6736,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)
@@ -6611,7 +6790,7 @@ static void LoadLevel_InitPlayfield(struct LevelInfo *level)
   // copy elements to runtime playfield array
   for (x = 0; x < MAX_LEV_FIELDX; x++)
     for (y = 0; y < MAX_LEV_FIELDY; y++)
-      Feld[x][y] = level->field[x][y];
+      Tile[x][y] = level->field[x][y];
 
   // initialize level size variables for faster access
   lev_fieldx = level->fieldx;
@@ -6638,6 +6817,7 @@ static void LoadLevelTemplate_LoadAndInit(void)
 
   LoadLevel_InitVersion(&level_template);
   LoadLevel_InitElements(&level_template);
+  LoadLevel_InitSettings(&level_template);
 
   ActivateLevelTemplate();
 }
@@ -6646,7 +6826,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;
   }
@@ -6678,6 +6858,7 @@ static void LoadLevel_LoadAndInit(struct NetworkLevelInfo *network_level)
   LoadLevel_InitVersion(&level);
   LoadLevel_InitElements(&level);
   LoadLevel_InitPlayfield(&level);
+  LoadLevel_InitSettings(&level);
 
   LoadLevel_InitNativeEngines(&level);
 }
@@ -6809,8 +6990,8 @@ static int SaveLevel_BODY(FILE *file, struct LevelInfo *level)
   int chunk_size = 0;
   int x, y;
 
-  for (y = 0; y < level->fieldy; y++) 
-    for (x = 0; x < level->fieldx; x++) 
+  for (y = 0; y < level->fieldy; y++)
+    for (x = 0; x < level->fieldx; x++)
       if (level->encoding_16bit_field)
        chunk_size += putFile16BitBE(file, level->field[x][y]);
       else
@@ -6887,7 +7068,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;
   }
 
@@ -6955,7 +7137,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
 
@@ -6984,7 +7166,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
 
@@ -7067,7 +7249,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
 
@@ -7145,7 +7327,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);
@@ -7185,7 +7367,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);
   }
 }
@@ -7436,6 +7618,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)
 {
@@ -7445,7 +7643,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;
   }
 
@@ -7526,6 +7725,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);
@@ -7570,7 +7781,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;
   }
@@ -7601,10 +7812,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
@@ -7625,17 +7881,60 @@ static void setTapeInfoToDefaults(void)
   // at least one (default: the first) player participates in every tape
   tape.num_participating_players = 1;
 
+  tape.property_bits = TAPE_PROPERTY_NONE;
+
   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;
 }
 
+static int getTapePosSize(struct TapeInfo *tape)
+{
+  int tape_pos_size = 0;
+
+  if (tape->use_key_actions)
+    tape_pos_size += tape->num_participating_players;
+
+  if (tape->use_mouse_actions)
+    tape_pos_size += 3;                // x and y position and mouse button mask
+
+  tape_pos_size += 1;          // tape action delay value
+
+  return tape_pos_size;
+}
+
+static void setTapeActionFlags(struct TapeInfo *tape, int value)
+{
+  tape->use_key_actions = FALSE;
+  tape->use_mouse_actions = FALSE;
+
+  if (value != TAPE_USE_MOUSE_ACTIONS_ONLY)
+    tape->use_key_actions = TRUE;
+
+  if (value != TAPE_USE_KEY_ACTIONS_ONLY)
+    tape->use_mouse_actions = TRUE;
+}
+
+static int getTapeActionValue(struct TapeInfo *tape)
+{
+  return (tape->use_key_actions &&
+         tape->use_mouse_actions ? TAPE_USE_KEY_AND_MOUSE_ACTIONS :
+         tape->use_key_actions   ? TAPE_USE_KEY_ACTIONS_ONLY :
+         tape->use_mouse_actions ? TAPE_USE_MOUSE_ACTIONS_ONLY :
+         TAPE_ACTIONS_DEFAULT);
+}
+
 static int LoadTape_VERS(File *file, int chunk_size, struct TapeInfo *tape)
 {
   tape->file_version = getFileVersion(file);
@@ -7671,9 +7970,10 @@ static int LoadTape_HEAD(File *file, int chunk_size, struct TapeInfo *tape)
       }
     }
 
-    tape->use_mouse = (getFile8Bit(file) == 1 ? TRUE : FALSE);
+    setTapeActionFlags(tape, getFile8Bit(file));
 
-    ReadUnusedBytesFromFile(file, TAPE_CHUNK_HEAD_UNUSED);
+    tape->property_bits = getFile8Bit(file);
+    tape->solved = getFile8Bit(file);
 
     engine_version = getFileVersion(file);
     if (engine_version > 0)
@@ -7685,18 +7985,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);
 
@@ -7708,8 +8023,7 @@ static int LoadTape_INFO(File *file, int chunk_size, struct TapeInfo *tape)
 static int LoadTape_BODY(File *file, int chunk_size, struct TapeInfo *tape)
 {
   int i, j;
-  int tape_pos_size =
-    (tape->use_mouse ? 3 : tape->num_participating_players) + 1;
+  int tape_pos_size = getTapePosSize(tape);
   int chunk_size_expected = tape_pos_size * tape->length;
 
   if (chunk_size_expected != chunk_size)
@@ -7722,25 +8036,17 @@ 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
       for (;i < tape->length; i++)
-       ReadUnusedBytesFromFile(file, tape->num_participating_players + 1);
+       ReadUnusedBytesFromFile(file, tape_pos_size);
 
       break;
     }
 
-    if (tape->use_mouse)
-    {
-      tape->pos[i].action[TAPE_ACTION_LX]     = getFile8Bit(file);
-      tape->pos[i].action[TAPE_ACTION_LY]     = getFile8Bit(file);
-      tape->pos[i].action[TAPE_ACTION_BUTTON] = getFile8Bit(file);
-
-      tape->pos[i].action[TAPE_ACTION_UNUSED] = 0;
-    }
-    else
+    if (tape->use_key_actions)
     {
       for (j = 0; j < MAX_PLAYERS; j++)
       {
@@ -7751,6 +8057,13 @@ static int LoadTape_BODY(File *file, int chunk_size, struct TapeInfo *tape)
       }
     }
 
+    if (tape->use_mouse_actions)
+    {
+      tape->pos[i].action[TAPE_ACTION_LX]     = getFile8Bit(file);
+      tape->pos[i].action[TAPE_ACTION_LY]     = getFile8Bit(file);
+      tape->pos[i].action[TAPE_ACTION_BUTTON] = getFile8Bit(file);
+    }
+
     tape->pos[i].delay = getFile8Bit(file);
 
     if (tape->file_version == FILE_VERSION_1_0)
@@ -7869,7 +8182,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;
     }
@@ -7918,7 +8231,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);
 
@@ -7937,7 +8250,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);
 
@@ -7948,7 +8261,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);
 
@@ -7977,6 +8290,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 }
@@ -7992,15 +8306,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
@@ -8014,7 +8330,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);
        }
       }
@@ -8027,9 +8343,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
 }
 
@@ -8052,6 +8371,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);
@@ -8074,14 +8415,20 @@ static void SaveTape_HEAD(FILE *file, struct TapeInfo *tape)
 
   putFile8Bit(file, store_participating_players);
 
-  putFile8Bit(file, (tape->use_mouse ? 1 : 0));
+  putFile8Bit(file, getTapeActionValue(tape));
 
-  // unused bytes not at the end here for 4-byte alignment of engine_version
-  WriteUnusedBytesToFile(file, TAPE_CHUNK_HEAD_UNUSED);
+  putFile8Bit(file, tape->property_bits);
+  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;
@@ -8101,247 +8448,949 @@ static void SaveTape_BODY(FILE *file, struct TapeInfo *tape)
 
   for (i = 0; i < tape->length; i++)
   {
-    if (tape->use_mouse)
+    if (tape->use_key_actions)
     {
-      putFile8Bit(file, tape->pos[i].action[TAPE_ACTION_LX]);
+      for (j = 0; j < MAX_PLAYERS; j++)
+       if (tape->player_participates[j])
+         putFile8Bit(file, tape->pos[i].action[j]);
+    }
+
+    if (tape->use_mouse_actions)
+    {
+      putFile8Bit(file, tape->pos[i].action[TAPE_ACTION_LX]);
       putFile8Bit(file, tape->pos[i].action[TAPE_ACTION_LY]);
       putFile8Bit(file, tape->pos[i].action[TAPE_ACTION_BUTTON]);
     }
-    else
+
+    putFile8Bit(file, tape->pos[i].delay);
+  }
+}
+
+void SaveTapeToFilename(char *filename)
+{
+  FILE *file;
+  int tape_pos_size;
+  int info_chunk_size;
+  int body_chunk_size;
+
+  if (!(file = fopen(filename, MODE_WRITE)))
+  {
+    Warn("cannot save level recording file '%s'", filename);
+
+    return;
+  }
+
+  tape_pos_size = getTapePosSize(&tape);
+
+  info_chunk_size = 2 + (strlen(tape.level_identifier) + 1) + 2;
+  body_chunk_size = tape_pos_size * tape.length;
+
+  putFileChunkBE(file, "RND1", CHUNK_SIZE_UNDEFINED);
+  putFileChunkBE(file, "TAPE", CHUNK_SIZE_NONE);
+
+  putFileChunkBE(file, "VERS", TAPE_CHUNK_VERS_SIZE);
+  SaveTape_VERS(file, &tape);
+
+  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);
+
+  putFileChunkBE(file, "BODY", body_chunk_size);
+  SaveTape_BODY(file, &tape);
+
+  fclose(file);
+
+  SetFilePermissions(filename, PERMS_PRIVATE);
+}
+
+static void SaveTapeExt(char *filename)
+{
+  int i;
+
+  tape.file_version = FILE_VERSION_ACTUAL;
+  tape.game_version = GAME_VERSION_ACTUAL;
+
+  tape.num_participating_players = 0;
+
+  // count number of participating players
+  for (i = 0; i < MAX_PLAYERS; i++)
+    if (tape.player_participates[i])
+      tape.num_participating_players++;
+
+  SaveTapeToFilename(filename);
+
+  tape.changed = FALSE;
+}
+
+void SaveTape(int nr)
+{
+  char *filename = getTapeFilename(nr);
+
+  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))
+  {
+    SaveTape(nr);
+
+    if (new_tape)
+      Request(msg_saved, REQ_CONFIRM | req_state_added);
+
+    tape_saved = TRUE;
+  }
+
+  return tape_saved;
+}
+
+boolean SaveTapeChecked(int nr)
+{
+  return SaveTapeCheckedExt(nr, "Replace old tape?", "Tape saved!", 0);
+}
+
+boolean SaveTapeChecked_LevelSolved(int nr)
+{
+  return SaveTapeCheckedExt(nr, "Level solved! Replace old tape?",
+                               "Level solved! Tape saved!", REQ_STAY_OPEN);
+}
+
+void DumpTape(struct TapeInfo *tape)
+{
+  int tape_frame_counter;
+  int i, j;
+
+  if (tape->no_valid_file)
+  {
+    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;
+
+  for (i = 0; i < tape->length; i++)
+  {
+    if (i >= MAX_TAPE_LEN)
+      break;
+
+    Print("%04d: ", i);
+
+    for (j = 0; j < MAX_PLAYERS; j++)
     {
-      for (j = 0; j < MAX_PLAYERS; j++)
-       if (tape->player_participates[j])
-         putFile8Bit(file, tape->pos[i].action[j]);
+      if (tape->player_participates[j])
+      {
+       int action = tape->pos[i].action[j];
+
+       Print("%d:%02x ", j, action);
+       Print("[%c%c%c%c|%c%c] - ",
+             (action & JOY_LEFT ? '<' : ' '),
+             (action & JOY_RIGHT ? '>' : ' '),
+             (action & JOY_UP ? '^' : ' '),
+             (action & JOY_DOWN ? 'v' : ' '),
+             (action & JOY_BUTTON_1 ? '1' : ' '),
+             (action & JOY_BUTTON_2 ? '2' : ' '));
+      }
+    }
+
+    Print("(%03d) ", tape->pos[i].delay);
+    Print("[%05d]\n", tape_frame_counter);
+
+    tape_frame_counter += tape->pos[i].delay;
+  }
+
+  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
+// ============================================================================
+
+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);
+  char cookie[MAX_LINE_LEN];
+  char line[MAX_LINE_LEN];
+  char *line_ptr;
+  FILE *file;
+
+  if (!(file = fopen(filename, MODE_READ)))
+    return;
+
+  // check file identifier
+  if (fgets(cookie, MAX_LINE_LEN, file) == NULL)
+    cookie[0] = '\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);
+
+    fclose(file);
+
+    return;
+  }
+
+  for (i = 0; i < MAX_SCORE_ENTRIES; i++)
+  {
+    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';
+
+    if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
+      line[strlen(line) - 1] = '\0';
+
+    for (line_ptr = line; *line_ptr; line_ptr++)
+    {
+      if (*line_ptr != ' ' && *line_ptr != '\t' && *line_ptr != '\0')
+      {
+       strncpy(scores.entry[i].name, line_ptr, MAX_PLAYER_NAME_LEN);
+       scores.entry[i].name[MAX_PLAYER_NAME_LEN] = '\0';
+       break;
+      }
     }
+  }
+
+  fclose(file);
+}
+
+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;
+  char *filename = getScoreFilename(nr);
+  FILE *file;
+
+  // used instead of "leveldir_current->subdir" (for network games)
+  InitScoreDirectory(levelset.identifier);
+
+  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;
 
-    putFile8Bit(file, tape->pos[i].delay);
+  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));
   }
 }
 
-void SaveTape(int nr)
+static void SaveScoreToFilename(char *filename)
 {
-  char *filename = getTapeFilename(nr);
   FILE *file;
-  int num_participating_players = 0;
-  int tape_pos_size;
   int info_chunk_size;
-  int body_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;
 
-  InitTapeDirectory(leveldir_current->subdir);
-
   if (!(file = fopen(filename, MODE_WRITE)))
   {
-    Error(ERR_WARN, "cannot save level recording file '%s'", filename);
+    Warn("cannot save score file '%s'", filename);
+
     return;
   }
 
-  tape.file_version = FILE_VERSION_ACTUAL;
-  tape.game_version = GAME_VERSION_ACTUAL;
+  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;
 
-  // count number of participating players
-  for (i = 0; i < MAX_PLAYERS; i++)
-    if (tape.player_participates[i])
-      num_participating_players++;
+  has_large_score_values = FALSE;
+  for (i = 0; i < scores.num_entries; i++)
+    if (scores.entry[i].score > 0xffff)
+      has_large_score_values = TRUE;
 
-  tape_pos_size = (tape.use_mouse ? 3 : num_participating_players) + 1;
+  putFileChunkBE(file, "RND1", CHUNK_SIZE_UNDEFINED);
+  putFileChunkBE(file, "SCOR", CHUNK_SIZE_NONE);
 
-  info_chunk_size = 2 + (strlen(tape.level_identifier) + 1) + 2;
-  body_chunk_size = tape_pos_size * tape.length;
+  putFileChunkBE(file, "VERS", SCORE_CHUNK_VERS_SIZE);
+  SaveScore_VERS(file, &scores);
 
-  putFileChunkBE(file, "RND1", CHUNK_SIZE_UNDEFINED);
-  putFileChunkBE(file, "TAPE", CHUNK_SIZE_NONE);
+  putFileChunkBE(file, "INFO", info_chunk_size);
+  SaveScore_INFO(file, &scores);
 
-  putFileChunkBE(file, "VERS", TAPE_CHUNK_VERS_SIZE);
-  SaveTape_VERS(file, &tape);
+  putFileChunkBE(file, "NAME", name_chunk_size);
+  SaveScore_NAME(file, &scores);
 
-  putFileChunkBE(file, "HEAD", TAPE_CHUNK_HEAD_SIZE);
-  SaveTape_HEAD(file, &tape);
+  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, "INFO", info_chunk_size);
-  SaveTape_INFO(file, &tape);
+  putFileChunkBE(file, "TIME", time_chunk_size);
+  SaveScore_TIME(file, &scores);
 
-  putFileChunkBE(file, "BODY", body_chunk_size);
-  SaveTape_BODY(file, &tape);
+  putFileChunkBE(file, "TAPE", tape_chunk_size);
+  SaveScore_TAPE(file, &scores);
 
   fclose(file);
 
   SetFilePermissions(filename, PERMS_PRIVATE);
-
-  tape.changed = FALSE;
 }
 
-static boolean SaveTapeCheckedExt(int nr, char *msg_replace, char *msg_saved,
-                                 unsigned int req_state_added)
+void SaveScore(int nr)
 {
-  char *filename = getTapeFilename(nr);
-  boolean new_tape = !fileExists(filename);
-  boolean tape_saved = FALSE;
+  char *filename = getScoreFilename(nr);
+  int i;
 
-  if (new_tape || Request(msg_replace, REQ_ASK | req_state_added))
-  {
-    SaveTape(nr);
+  // used instead of "leveldir_current->subdir" (for network games)
+  InitScoreDirectory(levelset.identifier);
 
-    if (new_tape)
-      Request(msg_saved, REQ_CONFIRM | req_state_added);
+  scores.file_version = FILE_VERSION_ACTUAL;
+  scores.game_version = GAME_VERSION_ACTUAL;
 
-    tape_saved = TRUE;
-  }
+  strncpy(scores.level_identifier, levelset.identifier, MAX_FILENAME_LEN);
+  scores.level_identifier[MAX_FILENAME_LEN] = '\0';
+  scores.level_nr = level_nr;
 
-  return tape_saved;
-}
+  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;
 
-boolean SaveTapeChecked(int nr)
-{
-  return SaveTapeCheckedExt(nr, "Replace old tape?", "Tape saved!", 0);
-}
+  scores.num_entries = i;
 
-boolean SaveTapeChecked_LevelSolved(int nr)
-{
-  return SaveTapeCheckedExt(nr, "Level solved! Replace old tape?",
-                               "Level solved! Tape saved!", REQ_STAY_OPEN);
+  if (scores.num_entries == 0)
+    return;
+
+  SaveScoreToFilename(filename);
 }
 
-void DumpTape(struct TapeInfo *tape)
+static void LoadServerScoreFromCache(int nr)
 {
-  int tape_frame_counter;
+  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;
 
-  if (tape->no_valid_file)
-  {
-    Error(ERR_WARN, "cannot dump -- no valid tape file found");
+  server_scores.num_entries = 0;
 
+  if (score_hash == NULL)
     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);
-  PrintLine("-", 79);
+  for (i = 0; i < MAX_SCORE_ENTRIES; i++)
+  {
+    score_entry = server_scores.entry[i];
 
-  tape_frame_counter = 0;
+    for (j = 0; score_mapping[j].value != NULL; j++)
+    {
+      char token[10];
 
-  for (i = 0; i < tape->length; i++)
-  {
-    if (i >= MAX_TAPE_LEN)
-      break;
+      sprintf(token, "%02d.%d", i, j);
 
-    Print("%04d: ", i);
+      char *value = getHashEntry(score_hash, token);
 
-    for (j = 0; j < MAX_PLAYERS; j++)
-    {
-      if (tape->player_participates[j])
+      if (value == NULL)
+       continue;
+
+      if (score_mapping[j].is_string)
       {
-       int action = tape->pos[i].action[j];
+       char *score_value = (char *)score_mapping[j].value;
+       int value_size = score_mapping[j].string_size;
 
-       Print("%d:%02x ", j, action);
-       Print("[%c%c%c%c|%c%c] - ",
-             (action & JOY_LEFT ? '<' : ' '),
-             (action & JOY_RIGHT ? '>' : ' '),
-             (action & JOY_UP ? '^' : ' '),
-             (action & JOY_DOWN ? 'v' : ' '),
-             (action & JOY_BUTTON_1 ? '1' : ' '),
-             (action & JOY_BUTTON_2 ? '2' : ' '));
+       strncpy(score_value, value, value_size);
+       score_value[value_size] = '\0';
       }
-    }
+      else
+      {
+       int *score_value = (int *)score_mapping[j].value;
 
-    Print("(%03d) ", tape->pos[i].delay);
-    Print("[%05d]\n", tape_frame_counter);
+       *score_value = atoi(value);
+      }
 
-    tape_frame_counter += tape->pos[i].delay;
+      server_scores.num_entries = i + 1;
+    }
+
+    server_scores.entry[i] = score_entry;
   }
 
-  PrintLine("-", 79);
+  freeSetupFileHash(score_hash);
 }
 
-
-// ============================================================================
-// score file functions
-// ============================================================================
-
-void LoadScore(int nr)
+void LoadServerScore(int nr, boolean download_score)
 {
-  int i;
-  char *filename = getScoreFilename(nr);
-  char cookie[MAX_LINE_LEN];
-  char line[MAX_LINE_LEN];
-  char *line_ptr;
-  FILE *file;
+  if (!setup.use_api_server)
+    return;
 
   // always start with reliable default values
-  for (i = 0; i < MAX_SCORE_ENTRIES; i++)
+  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)
   {
-    strcpy(highscore[i].Name, EMPTY_PLAYER_NAME);
-    highscore[i].Score = 0;
+    // 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);
   }
+}
 
-  if (!(file = fopen(filename, MODE_READ)))
-    return;
+void PrepareScoreTapesForUpload(char *leveldir_subdir)
+{
+  MarkTapeDirectoryUploadsAsIncomplete(leveldir_subdir);
 
-  // check file identifier
-  if (fgets(cookie, MAX_LINE_LEN, file) == NULL)
-    cookie[0] = '\0';
-  if (strlen(cookie) > 0 && cookie[strlen(cookie) - 1] == '\n')
-    cookie[strlen(cookie) - 1] = '\0';
+  // 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;
 
-  if (!checkCookieString(cookie, SCORE_COOKIE))
+  SaveSetup_ServerSetup();
+}
+
+void SaveServerScore(int nr, boolean tape_saved)
+{
+  if (!runtime.use_api_server)
   {
-    Error(ERR_WARN, "unknown format of score file '%s'", filename);
-    fclose(file);
+    PrepareScoreTapesForUpload(leveldir_current->subdir);
+
     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 (fgets(line, MAX_LINE_LEN, file) == NULL)
-      line[0] = '\0';
-
-    if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
-      line[strlen(line) - 1] = '\0';
+  ApiAddScoreAsThread(nr, tape_saved, NULL);
+}
 
-    for (line_ptr = line; *line_ptr; line_ptr++)
-    {
-      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';
-       break;
-      }
-    }
-  }
+void SaveServerScoreFromFile(int nr, boolean tape_saved,
+                            char *score_tape_filename)
+{
+  if (!runtime.use_api_server)
+    return;
 
-  fclose(file);
+  ApiAddScoreAsThread(nr, tape_saved, score_tape_filename);
 }
 
-void SaveScore(int nr)
+void LoadLocalAndServerScore(int nr, boolean download_score)
 {
-  int i;
-  int permissions = (program.global_scores ? PERMS_PUBLIC : PERMS_PRIVATE);
-  char *filename = getScoreFilename(nr);
-  FILE *file;
+  int last_added_local = scores.last_added_local;
+  boolean force_last_added = scores.force_last_added;
 
-  // used instead of "leveldir_current->subdir" (for network games)
-  InitScoreDirectory(levelset.identifier);
+  // needed if only showing server scores
+  setScoreInfoToDefaults();
 
-  if (!(file = fopen(filename, MODE_WRITE)))
-  {
-    Error(ERR_WARN, "cannot save score for level %d", nr);
-    return;
-  }
+  if (!strEqual(setup.scores_in_highscore_list, STR_SCORES_TYPE_SERVER_ONLY))
+    LoadScore(nr);
 
-  fprintf(file, "%s\n\n", SCORE_COOKIE);
+  // restore last added local score entry (before merging server scores)
+  scores.last_added = scores.last_added_local = last_added_local;
 
-  for (i = 0; i < MAX_SCORE_ENTRIES; i++)
-    fprintf(file, "%d %s\n", highscore[i].Score, highscore[i].Name);
+  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);
 
-  fclose(file);
+    // merge local scores with scores from server
+    MergeServerScore();
+  }
 
-  SetFilePermissions(filename, permissions);
+  if (force_last_added)
+    scores.force_last_added = force_last_added;
 }
 
 
@@ -8358,6 +9407,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"
@@ -8382,6 +9435,10 @@ static struct TokenInfo global_setup_tokens[] =
     TYPE_SWITCH,
     &setup.scroll_delay,                       "scroll_delay"
   },
+  {
+    TYPE_SWITCH,
+    &setup.forced_scroll_delay,                        "forced_scroll_delay"
+  },
   {
     TYPE_INTEGER,
     &setup.scroll_delay_value,                 "scroll_delay_value"
@@ -8402,6 +9459,10 @@ static struct TokenInfo global_setup_tokens[] =
     TYPE_SWITCH,
     &setup.autorecord,                         "automatic_tape_recording"
   },
+  {
+    TYPE_SWITCH,
+    &setup.auto_pause_on_start,                        "auto_pause_on_start"
+  },
   {
     TYPE_SWITCH,
     &setup.show_titlescreen,                   "show_titlescreen"
@@ -8432,7 +9493,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,
@@ -8470,6 +9535,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"
@@ -8482,6 +9555,14 @@ static struct TokenInfo global_setup_tokens[] =
     TYPE_SWITCH,
     &setup.prefer_aga_graphics,                        "prefer_aga_graphics"
   },
+  {
+    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"
@@ -8500,7 +9581,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,
@@ -8590,6 +9679,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[] =
@@ -8600,6 +9693,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[] =
 {
   {
@@ -8626,6 +9763,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[] =
@@ -8684,7 +9825,11 @@ static struct TokenInfo editor_cascade_setup_tokens[] =
   },
   {
     TYPE_SWITCH,
-    &setup.editor_cascade.el_ge,               "editor.cascade.el_ge"
+    &setup.editor_cascade.el_ge,               "editor.cascade.el_ge"
+  },
+  {
+    TYPE_SWITCH,
+    &setup.editor_cascade.el_es,               "editor.cascade.el_es"
   },
   {
     TYPE_SWITCH,
@@ -8710,6 +9855,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"
@@ -8859,6 +10012,10 @@ static struct TokenInfo player_setup_tokens[] =
 
 static struct TokenInfo system_setup_tokens[] =
 {
+  {
+    TYPE_STRING,
+    &setup.system.sdl_renderdriver,            "system.sdl_renderdriver"
+  },
   {
     TYPE_STRING,
     &setup.system.sdl_videodriver,             "system.sdl_videodriver"
@@ -8951,6 +10108,10 @@ static struct TokenInfo internal_setup_tokens[] =
     TYPE_BOOLEAN,
     &setup.internal.show_scaling_in_title,     "show_scaling_in_title"
   },
+  {
+    TYPE_BOOLEAN,
+    &setup.internal.create_user_levelset,      "create_user_levelset"
+  },
   {
     TYPE_BOOLEAN,
     &setup.internal.menu_game,                 "menu_game"
@@ -8991,6 +10152,38 @@ static struct TokenInfo internal_setup_tokens[] =
     TYPE_BOOLEAN,
     &setup.internal.menu_save_and_exit,                "menu_save_and_exit"
   },
+  {
+    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[] =
@@ -9086,6 +10279,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[] =
@@ -9096,26 +10297,13 @@ static struct TokenInfo options_setup_tokens[] =
   },
 };
 
-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;
@@ -9123,11 +10311,13 @@ static void setSetupInfoToDefaults(struct SetupInfo *si)
   si->sound_simple = TRUE;
   si->toons = TRUE;
   si->scroll_delay = TRUE;
+  si->forced_scroll_delay = FALSE;
   si->scroll_delay_value = STD_SCROLL_DELAY;
   si->engine_snapshot_mode = getStringCopy(STR_SNAPSHOT_MODE_DEFAULT);
   si->engine_snapshot_memory = SNAPSHOT_MEMORY_DEFAULT;
   si->fade_screens = TRUE;
   si->autorecord = TRUE;
+  si->auto_pause_on_start = FALSE;
   si->show_titlescreen = TRUE;
   si->quick_doors = FALSE;
   si->team_mode = FALSE;
@@ -9135,7 +10325,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;
@@ -9145,14 +10336,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);
@@ -9226,6 +10423,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;
@@ -9251,10 +10450,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;
@@ -9299,6 +10502,7 @@ static void setSetupInfoToDefaults(struct SetupInfo *si)
     si->input[i].key.drop  = (i == 0 ? DEFAULT_KEY_DROP  : KSYM_UNDEFINED);
   }
 
+  si->system.sdl_renderdriver = getStringCopy(ARG_DEFAULT);
   si->system.sdl_videodriver = getStringCopy(ARG_DEFAULT);
   si->system.sdl_audiodriver = getStringCopy(ARG_DEFAULT);
   si->system.audio_fragment_size = DEFAULT_AUDIO_FRAGMENT_SIZE;
@@ -9324,6 +10528,7 @@ static void setSetupInfoToDefaults(struct SetupInfo *si)
   si->internal.default_level_series = getStringCopy(UNDEFINED_LEVELSET);
   si->internal.choose_from_top_leveldir = FALSE;
   si->internal.show_scaling_in_title = TRUE;
+  si->internal.create_user_levelset = TRUE;
 
   si->internal.default_window_width  = WIN_XSIZE_DEFAULT;
   si->internal.default_window_height = WIN_YSIZE_DEFAULT;
@@ -9355,11 +10560,17 @@ 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;
 
 #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)
@@ -9367,6 +10578,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;
@@ -9385,6 +10611,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;
@@ -9406,10 +10633,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);
@@ -9431,6 +10669,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,
@@ -9441,16 +10681,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);
 
@@ -9544,6 +10781,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;
@@ -9557,19 +10807,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");
   }
 }
 
@@ -9578,7 +10865,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;
 
@@ -9594,7 +10881,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;
 
@@ -9604,6 +10891,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);
 
@@ -9635,6 +10928,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);
@@ -9655,6 +10977,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)
 {
@@ -9686,7 +11016,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;
   }
@@ -9704,7 +11034,7 @@ static void LoadSetup_ReadGameControllerMappings(SetupFileHash *mappings_hash,
   fclose(file);
 }
 
-void SaveSetup(void)
+void SaveSetup_Default(void)
 {
   char *filename = getSetupFilename();
   FILE *file;
@@ -9714,7 +11044,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;
   }
 
@@ -9723,7 +11054,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             ||
@@ -9791,7 +11123,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++)
@@ -9812,8 +11146,10 @@ void SaveSetup_AutoSetup(void)
 
   if (!(file = fopen(filename, MODE_WRITE)))
   {
-    Error(ERR_WARN, "cannot write auto setup file '%s'", filename);
+    Warn("cannot write auto setup file '%s'", filename);
+
     free(filename);
+
     return;
   }
 
@@ -9829,6 +11165,41 @@ void SaveSetup_AutoSetup(void)
   free(filename);
 }
 
+void SaveSetup_ServerSetup(void)
+{
+  char *filename = getPath2(getSetupDir(), SERVERSETUP_FILENAME);
+  FILE *file;
+  int i;
+
+  InitUserDataDirectory();
+
+  if (!(file = fopen(filename, MODE_WRITE)))
+  {
+    Warn("cannot write server setup file '%s'", filename);
+
+    free(filename);
+
+    return;
+  }
+
+  fprintFileHeader(file, SERVERSETUP_FILENAME);
+
+  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);
+
+  SetFilePermissions(filename, PERMS_PRIVATE);
+
+  free(filename);
+}
+
 void SaveSetup_EditorCascade(void)
 {
   char *filename = getPath2(getSetupDir(), EDITORCASCADE_FILENAME);
@@ -9839,8 +11210,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;
   }
 
@@ -9856,6 +11229,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)
 {
@@ -9863,7 +11244,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;
   }
@@ -9936,7 +11317,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;
 }
@@ -10066,11 +11447,19 @@ static int get_anim_parameter_value(char *s)
 {
   int event_value[] =
   {
-    ANIM_EVENT_CLICK
+    ANIM_EVENT_CLICK,
+    ANIM_EVENT_INIT,
+    ANIM_EVENT_START,
+    ANIM_EVENT_END,
+    ANIM_EVENT_POST
   };
   char *pattern_1[] =
   {
-    "click:anim_"
+    "click:anim_",
+    "init:anim_",
+    "start:anim_",
+    "end:anim_",
+    "post:anim_"
   };
   char *pattern_2 = ".part_";
   char *matching_char = NULL;
@@ -10160,6 +11549,9 @@ static int get_anim_parameter_values(char *s)
       string_has_parameter(s, "self"))
     event_value |= ANIM_EVENT_SELF;
 
+  if (string_has_parameter(s, "unclick:any"))
+    event_value |= ANIM_EVENT_UNCLICK_ANY;
+
   // if animation event found, add it to global animation event list
   if (event_value != ANIM_EVENT_NONE)
     list_pos = AddGlobalAnimEventValue(list_pos, event_value);
@@ -10182,6 +11574,10 @@ static int get_anim_parameter_values(char *s)
 
 static int get_anim_action_parameter_value(char *token)
 {
+  // check most common default case first to massively speed things up
+  if (strEqual(token, ARG_UNDEFINED))
+    return ANIM_EVENT_ACTION_NONE;
+
   int result = getImageIDFromToken(token);
 
   if (result == -1)
@@ -10201,6 +11597,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;
 
@@ -10253,6 +11661,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 :
@@ -10260,6 +11669,7 @@ 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 :
              ANIM_DEFAULT);
 
     if (string_has_parameter(value, "once"))
@@ -10279,7 +11689,10 @@ int get_parameter_value(char *value_raw, char *suffix, int type)
   {
     result = get_anim_parameter_values(value);
   }
-  else if (strEqual(suffix, ".init_event_action") ||
+  else if (strEqual(suffix, ".init_delay_action") ||
+          strEqual(suffix, ".anim_delay_action") ||
+          strEqual(suffix, ".post_delay_action") ||
+          strEqual(suffix, ".init_event_action") ||
           strEqual(suffix, ".anim_event_action"))
   {
     result = get_anim_action_parameter_value(value_raw);
@@ -10302,6 +11715,12 @@ 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;
+
     if (string_has_parameter(value, "passthrough_clicks"))
       result |= STYLE_PASSTHROUGH;
 
@@ -10317,6 +11736,12 @@ int get_parameter_value(char *value_raw, char *suffix, int type)
              string_has_parameter(value, "curtain")    ? FADE_MODE_CURTAIN :
              FADE_MODE_DEFAULT);
   }
+  else if (strEqual(suffix, ".auto_delay_unit"))
+  {
+    result = (string_has_parameter(value, "ms")     ? AUTO_DELAY_UNIT_MS :
+             string_has_parameter(value, "frames") ? AUTO_DELAY_UNIT_FRAMES :
+             AUTO_DELAY_UNIT_DEFAULT);
+  }
   else if (strPrefix(suffix, ".font"))         // (may also be ".font_xyz")
   {
     result = gfx.get_font_from_token_function(value);
@@ -10352,14 +11777,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 =
@@ -10367,6 +11796,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;
@@ -10383,10 +11818,14 @@ static void InitMenuDesignSettings_SpecialPreProcessing(void)
     title_initial_first_default.post_delay;
   titlescreen_initial_first_default.auto_delay =
     title_initial_first_default.auto_delay;
+  titlescreen_initial_first_default.auto_delay_unit =
+    title_initial_first_default.auto_delay_unit;
   titlescreen_first_default.fade_mode  = title_first_default.fade_mode;
   titlescreen_first_default.fade_delay = title_first_default.fade_delay;
   titlescreen_first_default.post_delay = title_first_default.post_delay;
   titlescreen_first_default.auto_delay = title_first_default.auto_delay;
+  titlescreen_first_default.auto_delay_unit =
+    title_first_default.auto_delay_unit;
   titlemessage_initial_first_default.fade_mode  =
     title_initial_first_default.fade_mode;
   titlemessage_initial_first_default.fade_delay =
@@ -10395,27 +11834,36 @@ static void InitMenuDesignSettings_SpecialPreProcessing(void)
     title_initial_first_default.post_delay;
   titlemessage_initial_first_default.auto_delay =
     title_initial_first_default.auto_delay;
+  titlemessage_initial_first_default.auto_delay_unit =
+    title_initial_first_default.auto_delay_unit;
   titlemessage_first_default.fade_mode  = title_first_default.fade_mode;
   titlemessage_first_default.fade_delay = title_first_default.fade_delay;
   titlemessage_first_default.post_delay = title_first_default.post_delay;
   titlemessage_first_default.auto_delay = title_first_default.auto_delay;
+  titlemessage_first_default.auto_delay_unit =
+    title_first_default.auto_delay_unit;
 
   titlescreen_initial_default.fade_mode  = title_initial_default.fade_mode;
   titlescreen_initial_default.fade_delay = title_initial_default.fade_delay;
   titlescreen_initial_default.post_delay = title_initial_default.post_delay;
   titlescreen_initial_default.auto_delay = title_initial_default.auto_delay;
+  titlescreen_initial_default.auto_delay_unit =
+    title_initial_default.auto_delay_unit;
   titlescreen_default.fade_mode  = title_default.fade_mode;
   titlescreen_default.fade_delay = title_default.fade_delay;
   titlescreen_default.post_delay = title_default.post_delay;
   titlescreen_default.auto_delay = title_default.auto_delay;
+  titlescreen_default.auto_delay_unit = title_default.auto_delay_unit;
   titlemessage_initial_default.fade_mode  = title_initial_default.fade_mode;
   titlemessage_initial_default.fade_delay = title_initial_default.fade_delay;
   titlemessage_initial_default.post_delay = title_initial_default.post_delay;
-  titlemessage_initial_default.auto_delay = title_initial_default.auto_delay;
+  titlemessage_initial_default.auto_delay_unit =
+    title_initial_default.auto_delay_unit;
   titlemessage_default.fade_mode  = title_default.fade_mode;
   titlemessage_default.fade_delay = title_default.fade_delay;
   titlemessage_default.post_delay = title_default.post_delay;
   titlemessage_default.auto_delay = title_default.auto_delay;
+  titlemessage_default.auto_delay_unit = title_default.auto_delay_unit;
 
   // special case: initialize "ARG_DEFAULT" values in static default config
   // (e.g., init "titlemessage_1.fade_mode" from "[titlemessage].fade_mode")
@@ -10804,6 +12252,7 @@ static void LoadMenuDesignSettingsFromFilename(char *filename)
     { TYPE_INTEGER,    &tfi.fade_delay,        ".fade_delay"           },
     { TYPE_INTEGER,    &tfi.post_delay,        ".post_delay"           },
     { TYPE_INTEGER,    &tfi.auto_delay,        ".auto_delay"           },
+    { TYPE_INTEGER,    &tfi.auto_delay_unit,   ".auto_delay_unit"      },
 
     { -1,              NULL,                   NULL                    }
   };
@@ -10826,6 +12275,7 @@ static void LoadMenuDesignSettingsFromFilename(char *filename)
     { TYPE_INTEGER,    &tmi.fade_delay,        ".fade_delay"           },
     { TYPE_INTEGER,    &tmi.post_delay,        ".post_delay"           },
     { TYPE_INTEGER,    &tmi.auto_delay,        ".auto_delay"           },
+    { TYPE_INTEGER,    &tmi.auto_delay_unit,   ".auto_delay_unit"      },
 
     { -1,              NULL,                   NULL                    }
   };
@@ -11150,15 +12600,7 @@ static void LoadMenuDesignSettingsFromFilename(char *filename)
       *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);
-
-    // (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);
-  }
+  InitMenuDesignSettings_FromHash(setup_file_hash, TRUE);
 
   freeSetupFileHash(setup_file_hash);
 }
@@ -11247,19 +12689,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;
@@ -11269,8 +12711,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
 }
 
@@ -11449,7 +12891,8 @@ void LoadMusicInfo(void)
 
   if ((dir = openDirectory(music_directory)) == NULL)
   {
-    Error(ERR_WARN, "cannot read music directory '%s'", music_directory);
+    Warn("cannot read music directory '%s'", music_directory);
+
     return;
   }
 
@@ -11514,6 +12957,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,
@@ -11537,18 +12992,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)
@@ -11752,12 +13207,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
 }
 
@@ -11789,8 +13244,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
@@ -11816,8 +13271,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;
 
@@ -11860,6 +13314,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);
 
@@ -11937,20 +13396,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);
@@ -11976,7 +13435,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);
 }
@@ -12074,7 +13662,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);