added option to not trigger further animations if CE change event consumed
[rocksndiamonds.git] / src / files.c
index 37e698162d054fe30bf172be8b66c9089841f634..d9addaf1fc074a8ef0f8bde81ead0dfd780f579d 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
                                         CONF_CONTENT_NUM_BYTES : 1)
 
 #define CONF_ELEMENT_BYTE_POS(i)       ((i) * CONF_ELEMENT_NUM_BYTES)
-#define CONF_ELEMENTS_ELEMENT(b,i)     ((b[CONF_ELEMENT_BYTE_POS(i)] << 8) |  \
+#define CONF_ELEMENTS_ELEMENT(b, i)    ((b[CONF_ELEMENT_BYTE_POS(i)] << 8) | \
                                        (b[CONF_ELEMENT_BYTE_POS(i) + 1]))
 
 #define CONF_CONTENT_ELEMENT_POS(c,x,y)        ((c) * CONF_CONTENT_NUM_ELEMENTS +    \
@@ -257,6 +261,18 @@ static struct LevelFileConfigInfo chunk_config_INFO[] =
     &li.solved_by_one_player,          FALSE
   },
 
+  {
+    -1,                                        -1,
+    TYPE_INTEGER,                      CONF_VALUE_8_BIT(12),
+    &li.time_score_base,               1
+  },
+
+  {
+    -1,                                        -1,
+    TYPE_BOOLEAN,                      CONF_VALUE_8_BIT(13),
+    &li.rate_time_over_score,          FALSE
+  },
+
   {
     -1,                                        -1,
     -1,                                        -1,
@@ -307,6 +323,16 @@ static struct LevelFileConfigInfo chunk_config_ELEM[] =
     TYPE_BOOLEAN,                      CONF_VALUE_8_BIT(15),
     &li.lazy_relocation,               FALSE
   },
+  {
+    EL_PLAYER_1,                       -1,
+    TYPE_BOOLEAN,                      CONF_VALUE_8_BIT(16),
+    &li.finish_dig_collect,            TRUE
+  },
+  {
+    EL_PLAYER_1,                       -1,
+    TYPE_BOOLEAN,                      CONF_VALUE_8_BIT(17),
+    &li.keep_walkable_ce,              FALSE
+  },
 
   // (these values are different for each player)
   {
@@ -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,
@@ -818,6 +850,18 @@ static struct LevelFileConfigInfo chunk_config_ELEM[] =
     &li.num_ball_contents,             4, MAX_ELEMENT_CONTENTS
   },
 
+  {
+    EL_SOKOBAN_FIELD_EMPTY,            -1,
+    TYPE_BOOLEAN,                      CONF_VALUE_8_BIT(1),
+    &li.sb_fields_needed,              TRUE
+  },
+
+  {
+    EL_SOKOBAN_OBJECT,                 -1,
+    TYPE_BOOLEAN,                      CONF_VALUE_8_BIT(1),
+    &li.sb_objects_needed,             TRUE
+  },
+
   {
     EL_MM_MCDUFFIN,                    -1,
     TYPE_BOOLEAN,                      CONF_VALUE_8_BIT(1),
@@ -860,11 +904,34 @@ static struct LevelFileConfigInfo chunk_config_ELEM[] =
     TYPE_INTEGER,                      CONF_VALUE_16_BIT(1),
     &li.mm_time_bomb,                  75
   },
+
   {
     EL_MM_GRAY_BALL,                   -1,
     TYPE_INTEGER,                      CONF_VALUE_16_BIT(1),
     &li.mm_time_ball,                  75
   },
+  {
+    EL_MM_GRAY_BALL,                   -1,
+    TYPE_INTEGER,                      CONF_VALUE_8_BIT(1),
+    &li.mm_ball_choice_mode,           ANIM_RANDOM
+  },
+  {
+    EL_MM_GRAY_BALL,                   -1,
+    TYPE_ELEMENT_LIST,                 CONF_VALUE_BYTES(1),
+    &li.mm_ball_content,               EL_EMPTY, NULL,
+    &li.num_mm_ball_contents,          8, MAX_MM_BALL_CONTENTS
+  },
+  {
+    EL_MM_GRAY_BALL,                   -1,
+    TYPE_BOOLEAN,                      CONF_VALUE_8_BIT(3),
+    &li.rotate_mm_ball_content,                TRUE
+  },
+  {
+    EL_MM_GRAY_BALL,                   -1,
+    TYPE_BOOLEAN,                      CONF_VALUE_8_BIT(2),
+    &li.explode_mm_ball,               FALSE
+  },
+
   {
     EL_MM_STEEL_BLOCK,                 -1,
     TYPE_INTEGER,                      CONF_VALUE_16_BIT(1),
@@ -1045,6 +1112,18 @@ static struct LevelFileConfigInfo chunk_config_CUSX_base[] =
     &xx_ei.move_delay_random,          0,
     &yy_ei.move_delay_random
   },
+  {
+    -1,                                        -1,
+    TYPE_INTEGER,                      CONF_VALUE_16_BIT(16),
+    &xx_ei.step_delay_fixed,           0,
+    &yy_ei.step_delay_fixed
+  },
+  {
+    -1,                                        -1,
+    TYPE_INTEGER,                      CONF_VALUE_16_BIT(17),
+    &xx_ei.step_delay_random,          0,
+    &yy_ei.step_delay_random
+  },
 
   {
     -1,                                        -1,
@@ -1321,6 +1400,26 @@ static struct LevelFileConfigInfo chunk_config_GRPX[] =
   }
 };
 
+static struct LevelFileConfigInfo chunk_config_EMPX[] =
+{
+  {
+    -1,                                        -1,
+    TYPE_BOOLEAN,                      CONF_VALUE_8_BIT(1),
+    &xx_ei.use_gfx_element,            FALSE
+  },
+  {
+    -1,                                        -1,
+    TYPE_ELEMENT,                      CONF_VALUE_16_BIT(1),
+    &xx_ei.gfx_element_initial,                EL_EMPTY_SPACE
+  },
+
+  {
+    -1,                                        -1,
+    -1,                                        -1,
+    NULL,                              -1
+  }
+};
+
 static struct LevelFileConfigInfo chunk_config_CONF[] =                // (OBSOLETE)
 {
   {
@@ -1765,6 +1864,16 @@ static void setLevelInfoToDefaults_Elements(struct LevelInfo *level)
     int element = i;
     struct ElementInfo *ei = &element_info[element];
 
+    if (element == EL_MM_GRAY_BALL)
+    {
+      struct LevelInfo_MM *level_mm = level->native_mm_level;
+      int j;
+
+      for (j = 0; j < level->num_mm_ball_contents; j++)
+       level->mm_ball_content[j] =
+         map_element_MM_to_RND(level_mm->ball_content[j]);
+    }
+
     // never initialize clipboard elements after the very first time
     // (to be able to use clipboard elements between several levels)
     if (IS_CLIPBOARD_ELEMENT(element) && clipboard_elements_initialized)
@@ -1838,6 +1947,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;
@@ -1906,13 +2025,13 @@ static void ActivateLevelTemplate(void)
     }
   }
 
-  /* Currently there is no special action needed to activate the template
-     data, because 'element_info' property settings overwrite the original
-     level data, while all other variables do not change. */
+  // Currently there is no special action needed to activate the template
+  // data, because 'element_info' property settings overwrite the original
+  // level data, while all other variables do not change.
 
-  /* Exception: 'from_level_template' elements in the original level playfield
-     are overwritten with the corresponding elements at the same position in
-     playfield from the level template. */
+  // Exception: 'from_level_template' elements in the original level playfield
+  // are overwritten with the corresponding elements at the same position in
+  // playfield from the level template.
 
   for (x = 0; x < level.fieldx; x++)
     for (y = 0; y < level.fieldy; y++)
@@ -2018,8 +2137,8 @@ static int getFileTypeFromMagicBytes(char *filename, int type)
 
 static boolean checkForPackageFromBasename(char *basename)
 {
-  /* !!! WON'T WORK ANYMORE IF getFileTypeFromBasename() ALSO DETECTS !!!
-     !!! SINGLE LEVELS (CURRENTLY ONLY DETECTS LEVEL PACKAGES         !!! */
+  // !!! WON'T WORK ANYMORE IF getFileTypeFromBasename() ALSO DETECTS !!!
+  // !!! SINGLE LEVELS (CURRENTLY ONLY DETECTS LEVEL PACKAGES         !!!
 
   return (getFileTypeFromBasename(basename) != LEVEL_FILE_TYPE_UNKNOWN);
 }
@@ -2052,7 +2171,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;
   }
@@ -2316,7 +2435,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
 
@@ -2357,7 +2476,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;
       }
@@ -2605,7 +2724,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;
@@ -2665,10 +2784,10 @@ 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) */
+    // older game versions that wrote level files with CUS1 chunks used
+    // different default push delay values (not yet stored in level file)
     element_info[element].push_delay_fixed = 2;
     element_info[element].push_delay_random = 8;
   }
@@ -2698,7 +2817,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;
@@ -2726,7 +2845,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;
     }
@@ -2759,9 +2878,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));
@@ -2812,9 +2932,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;
   }
 
@@ -2896,7 +3017,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));
@@ -2937,7 +3058,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;
   }
 
@@ -2960,9 +3081,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;
   }
 
@@ -3024,9 +3146,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;
        }
@@ -3035,8 +3156,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
 
@@ -3105,7 +3225,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;
 
@@ -3127,9 +3247,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;
@@ -3246,8 +3366,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;
 
@@ -3267,6 +3387,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
@@ -3296,6 +3420,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
 
@@ -3316,6 +3443,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)
@@ -3334,7 +3485,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;
@@ -3361,7 +3512,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);
 
@@ -3380,7 +3531,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);
 
@@ -3391,7 +3542,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);
 
@@ -3438,6 +3589,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 }
     };
@@ -3452,15 +3604,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
@@ -3469,13 +3623,23 @@ static void LoadLevelFromFileInfo_RND(struct LevelInfo *level,
        int chunk_size_expected =
          (chunk_info[i].loader)(file, chunk_size, level);
 
-       /* 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 < 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;
        }
       }
     }
@@ -3503,118 +3667,99 @@ 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_time = 9999;
+  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)
@@ -3631,69 +3776,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;
@@ -3705,12 +3849,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;
 }
 
 
@@ -3768,8 +3915,8 @@ static void CopyNativeLevel_RND_to_SP(struct LevelInfo *level)
     else if (element >= EL_SP_GRAVITY_PORT_RIGHT &&
             element <= EL_SP_GRAVITY_PORT_UP)
     {
-      /* change R'n'D style gravity inverting special port to normal port
-        (there are no gravity inverting ports in native Supaplex engine) */
+      // change R'n'D style gravity inverting special port to normal port
+      // (there are no gravity inverting ports in native Supaplex engine)
 
       gravity_port_found = TRUE;
       gravity_port_valid = FALSE;
@@ -3823,7 +3970,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);
       }
 
@@ -3832,8 +3979,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] =
@@ -3869,8 +4016,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;
     }
@@ -3880,13 +4026,13 @@ 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;
     }
 
-    /* change previous (wrong) gravity inverting special port to either
-       gravity enabling special port or gravity disabling special port */
+    // change previous (wrong) gravity inverting special port to either
+    // gravity enabling special port or gravity disabling special port
     level->field[port_x][port_y] +=
       (gravity == 1 ? EL_SP_GRAVITY_ON_PORT_RIGHT :
        EL_SP_GRAVITY_OFF_PORT_RIGHT) - EL_SP_GRAVITY_PORT_RIGHT;
@@ -3905,12 +4051,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++)
@@ -3946,8 +4091,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;
     }
@@ -3991,7 +4136,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;
 
@@ -4000,8 +4145,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;
     }
@@ -4018,7 +4163,7 @@ static void CopyNativeTape_SP_to_RND(struct LevelInfo *level)
 static void CopyNativeLevel_RND_to_MM(struct LevelInfo *level)
 {
   struct LevelInfo_MM *level_mm = level->native_mm_level;
-  int x, y;
+  int i, x, y;
 
   level_mm->fieldx = MIN(level->fieldx, MM_MAX_PLAYFIELD_WIDTH);
   level_mm->fieldy = MIN(level->fieldy, MM_MAX_PLAYFIELD_HEIGHT);
@@ -4027,9 +4172,13 @@ static void CopyNativeLevel_RND_to_MM(struct LevelInfo *level)
   level_mm->kettles_needed = level->gems_needed;
   level_mm->auto_count_kettles = level->auto_count_gems;
 
-  level_mm->laser_red = level->mm_laser_red;
-  level_mm->laser_green = level->mm_laser_green;
-  level_mm->laser_blue = level->mm_laser_blue;
+  level_mm->mm_laser_red   = level->mm_laser_red;
+  level_mm->mm_laser_green = level->mm_laser_green;
+  level_mm->mm_laser_blue  = level->mm_laser_blue;
+
+  level_mm->df_laser_red   = level->df_laser_red;
+  level_mm->df_laser_green = level->df_laser_green;
+  level_mm->df_laser_blue  = level->df_laser_blue;
 
   strcpy(level_mm->name, level->name);
   strcpy(level_mm->author, level->author);
@@ -4046,6 +4195,15 @@ static void CopyNativeLevel_RND_to_MM(struct LevelInfo *level)
   level_mm->time_ball    = level->mm_time_ball;
   level_mm->time_block   = level->mm_time_block;
 
+  level_mm->num_ball_contents = level->num_mm_ball_contents;
+  level_mm->ball_choice_mode = level->mm_ball_choice_mode;
+  level_mm->rotate_ball_content = level->rotate_mm_ball_content;
+  level_mm->explode_ball = level->explode_mm_ball;
+
+  for (i = 0; i < level->num_mm_ball_contents; i++)
+    level_mm->ball_content[i] =
+      map_element_RND_to_MM(level->mm_ball_content[i]);
+
   for (x = 0; x < level->fieldx; x++)
     for (y = 0; y < level->fieldy; y++)
       Ur[x][y] =
@@ -4055,7 +4213,7 @@ static void CopyNativeLevel_RND_to_MM(struct LevelInfo *level)
 static void CopyNativeLevel_MM_to_RND(struct LevelInfo *level)
 {
   struct LevelInfo_MM *level_mm = level->native_mm_level;
-  int x, y;
+  int i, x, y;
 
   level->fieldx = MIN(level_mm->fieldx, MAX_LEV_FIELDX);
   level->fieldy = MIN(level_mm->fieldy, MAX_LEV_FIELDY);
@@ -4064,9 +4222,13 @@ static void CopyNativeLevel_MM_to_RND(struct LevelInfo *level)
   level->gems_needed = level_mm->kettles_needed;
   level->auto_count_gems = level_mm->auto_count_kettles;
 
-  level->mm_laser_red = level_mm->laser_red;
-  level->mm_laser_green = level_mm->laser_green;
-  level->mm_laser_blue = level_mm->laser_blue;
+  level->mm_laser_red   = level_mm->mm_laser_red;
+  level->mm_laser_green = level_mm->mm_laser_green;
+  level->mm_laser_blue  = level_mm->mm_laser_blue;
+
+  level->df_laser_red   = level_mm->df_laser_red;
+  level->df_laser_green = level_mm->df_laser_green;
+  level->df_laser_blue  = level_mm->df_laser_blue;
 
   strcpy(level->name, level_mm->name);
 
@@ -4086,6 +4248,15 @@ static void CopyNativeLevel_MM_to_RND(struct LevelInfo *level)
   level->mm_time_ball  = level_mm->time_ball;
   level->mm_time_block = level_mm->time_block;
 
+  level->num_mm_ball_contents = level_mm->num_ball_contents;
+  level->mm_ball_choice_mode = level_mm->ball_choice_mode;
+  level->rotate_mm_ball_content = level_mm->rotate_ball_content;
+  level->explode_mm_ball = level_mm->explode_ball;
+
+  for (i = 0; i < level->num_mm_ball_contents; i++)
+    level->mm_ball_content[i] =
+      map_element_MM_to_RND(level_mm->ball_content[i]);
+
   for (x = 0; x < level->fieldx; x++)
     for (y = 0; y < level->fieldy; y++)
       level->field[x][y] = map_element_MM_to_RND(level_mm->field[x][y]);
@@ -4276,7 +4447,7 @@ static int getMappedElement_DC(int element)
       break;
 
     case 0x13f5:
-      element = EL_YAMYAM;
+      element = EL_YAMYAM_UP;
       break;
 
     case 0x1425:
@@ -5302,7 +5473,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)
@@ -5310,7 +5481,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)
@@ -5318,7 +5489,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)
@@ -5326,7 +5497,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)
@@ -5557,7 +5728,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;
@@ -5566,8 +5738,7 @@ static int getMappedElement_DC(int element)
   return getMappedElement(element);
 }
 
-static void LoadLevelFromFileStream_DC(File *file, struct LevelInfo *level,
-                                      int nr)
+static void LoadLevelFromFileStream_DC(File *file, struct LevelInfo *level)
 {
   byte header[DC_LEVEL_HEADER_SIZE];
   int envelope_size;
@@ -5605,7 +5776,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;
   }
@@ -5718,9 +5889,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);
 
-  /* Diamond Caves has the same (strange) behaviour as Emerald Mine that gems
-     can slip down from flat walls, like normal walls and steel walls */
+  // 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,
@@ -5738,7 +5919,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;
   }
@@ -5756,8 +5937,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;
     }
@@ -5783,8 +5963,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;
        }
@@ -5802,14 +5981,13 @@ static void LoadLevelFromFileInfo_DC(struct LevelInfo *level,
     {
       level->no_valid_file = TRUE;
 
-      Error(ERR_WARN, "unknown DC2 level file '%s' -- using empty level",
-           filename);
+      Warn("unknown DC2 level file '%s' -- using empty level", filename);
 
       return;
     }
   }
 
-  LoadLevelFromFileStream_DC(file, level, level_file_info->nr);
+  LoadLevelFromFileStream_DC(file, level);
 
   closeFile(file);
 }
@@ -5850,6 +6028,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)
@@ -5878,7 +6071,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;
   }
@@ -6052,7 +6245,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;
   }
@@ -6083,14 +6276,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;
   }
 }
@@ -6247,7 +6437,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)
@@ -6314,10 +6504,6 @@ static void LoadLevel_InitVersion(struct LevelInfo *level)
     level->extra_time_score = level->score[SC_TIME_BONUS];
   }
 
-  // game logic of "game of life" and "biomaze" was buggy before 4.1.1.1
-  if (level->game_version < VERSION_IDENT(4,1,1,1))
-    level->use_life_bugs = TRUE;
-
   if (level->game_version < VERSION_IDENT(3,2,0,7))
   {
     // default behaviour for snapping was "not continuous" before 3.2.0-7
@@ -6415,6 +6601,53 @@ static void LoadLevel_InitVersion(struct LevelInfo *level)
   // levels were solved by the first player entering an exit up to 4.1.0.0
   if (level->game_version <= VERSION_IDENT(4,1,0,0))
     level->solved_by_one_player = TRUE;
+
+  // game logic of "game of life" and "biomaze" was buggy before 4.1.1.1
+  if (level->game_version < VERSION_IDENT(4,1,1,1))
+    level->use_life_bugs = TRUE;
+
+  // 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)
@@ -6437,9 +6670,9 @@ static void LoadLevel_InitCustomElements(struct LevelInfo *level)
 {
   int i, j;
 
-  /* map custom element change events that have changed in newer versions
-     (these following values were accidentally changed in version 3.0.1)
-     (this seems to be needed only for 'ab_levelset3' and 'ab_levelset4') */
+  // map custom element change events that have changed in newer versions
+  // (these following values were accidentally changed in version 3.0.1)
+  // (this seems to be needed only for 'ab_levelset3' and 'ab_levelset4')
   if (level->game_version <= VERSION_IDENT(3,0,0,0))
   {
     for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
@@ -6562,6 +6795,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)
@@ -6595,7 +6849,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;
@@ -6622,6 +6876,7 @@ static void LoadLevelTemplate_LoadAndInit(void)
 
   LoadLevel_InitVersion(&level_template);
   LoadLevel_InitElements(&level_template);
+  LoadLevel_InitSettings(&level_template);
 
   ActivateLevelTemplate();
 }
@@ -6630,7 +6885,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;
   }
@@ -6662,6 +6917,7 @@ static void LoadLevel_LoadAndInit(struct NetworkLevelInfo *network_level)
   LoadLevel_InitVersion(&level);
   LoadLevel_InitElements(&level);
   LoadLevel_InitPlayfield(&level);
+  LoadLevel_InitSettings(&level);
 
   LoadLevel_InitNativeEngines(&level);
 }
@@ -6793,8 +7049,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
@@ -6871,7 +7127,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;
   }
 
@@ -6939,7 +7196,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
 
@@ -6968,7 +7225,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
 
@@ -7051,7 +7308,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
 
@@ -7129,7 +7386,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);
@@ -7169,7 +7426,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);
   }
 }
@@ -7420,6 +7677,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)
 {
@@ -7429,7 +7702,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;
   }
 
@@ -7510,6 +7784,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);
@@ -7554,7 +7840,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;
   }
@@ -7585,10 +7871,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
@@ -7609,24 +7940,67 @@ 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 LoadTape_VERS(File *file, int chunk_size, struct TapeInfo *tape)
+static int getTapePosSize(struct TapeInfo *tape)
 {
-  tape->file_version = getFileVersion(file);
-  tape->game_version = getFileVersion(file);
+  int tape_pos_size = 0;
 
-  return chunk_size;
-}
+  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);
+  tape->game_version = getFileVersion(file);
+
+  return chunk_size;
+}
 
 static int LoadTape_HEAD(File *file, int chunk_size, struct TapeInfo *tape)
 {
@@ -7655,9 +8029,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)
@@ -7669,18 +8044,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);
 
@@ -7692,8 +8082,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)
@@ -7706,25 +8095,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++)
       {
@@ -7735,6 +8116,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)
@@ -7853,7 +8241,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;
     }
@@ -7902,7 +8290,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);
 
@@ -7921,7 +8309,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);
 
@@ -7932,7 +8320,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);
 
@@ -7961,6 +8349,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 }
@@ -7976,15 +8365,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
@@ -7993,12 +8384,12 @@ void LoadTapeFromFilename(char *filename)
        int chunk_size_expected =
          (chunk_info[i].loader)(file, chunk_size, &tape);
 
-       /* 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 */
+       // 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 tape file '%s'",
+         Warn("wrong size (%d) of chunk '%s' in tape file '%s'",
                chunk_size, chunk_name, filename);
        }
       }
@@ -8011,9 +8402,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
 }
 
@@ -8036,6 +8430,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);
@@ -8058,14 +8474,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;
@@ -8085,50 +8507,39 @@ static void SaveTape_BODY(FILE *file, struct TapeInfo *tape)
 
   for (i = 0; i < tape->length; i++)
   {
-    if (tape->use_mouse)
-    {
-      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
+    if (tape->use_key_actions)
     {
       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]);
+    }
+
     putFile8Bit(file, tape->pos[i].delay);
   }
 }
 
-void SaveTape(int nr)
+void SaveTapeToFilename(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 i;
-
-  InitTapeDirectory(leveldir_current->subdir);
 
   if (!(file = fopen(filename, MODE_WRITE)))
   {
-    Error(ERR_WARN, "cannot save level recording file '%s'", filename);
+    Warn("cannot save level recording file '%s'", filename);
+
     return;
   }
 
-  tape.file_version = FILE_VERSION_ACTUAL;
-  tape.game_version = GAME_VERSION_ACTUAL;
-
-  // count number of participating players
-  for (i = 0; i < MAX_PLAYERS; i++)
-    if (tape.player_participates[i])
-      num_participating_players++;
-
-  tape_pos_size = (tape.use_mouse ? 3 : num_participating_players) + 1;
+  tape_pos_size = getTapePosSize(&tape);
 
   info_chunk_size = 2 + (strlen(tape.level_identifier) + 1) + 2;
   body_chunk_size = tape_pos_size * tape.length;
@@ -8142,6 +8553,12 @@ void SaveTape(int nr)
   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);
 
@@ -8151,22 +8568,59 @@ void SaveTape(int nr)
   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;
 }
 
-static boolean SaveTapeCheckedExt(int nr, char *msg_replace, char *msg_saved)
+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))
+  if (new_tape || Request(msg_replace, REQ_ASK | req_state_added))
   {
     SaveTape(nr);
 
     if (new_tape)
-      Request(msg_saved, REQ_CONFIRM);
+      Request(msg_saved, REQ_CONFIRM | req_state_added);
 
     tape_saved = TRUE;
   }
@@ -8176,13 +8630,13 @@ static boolean SaveTapeCheckedExt(int nr, char *msg_replace, char *msg_saved)
 
 boolean SaveTapeChecked(int nr)
 {
-  return SaveTapeCheckedExt(nr, "Replace old tape?", "Tape saved!");
+  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!");
+                               "Level solved! Tape saved!", REQ_STAY_OPEN);
 }
 
 void DumpTape(struct TapeInfo *tape)
@@ -8192,17 +8646,53 @@ void DumpTape(struct TapeInfo *tape)
 
   if (tape->no_valid_file)
   {
-    Error(ERR_WARN, "cannot dump -- no valid tape file found");
+    Warn("cannot dump -- no valid tape file found");
 
     return;
   }
 
   PrintLine("-", 79);
+
   Print("Tape of Level %03d (file version %08d, game version %08d)\n",
        tape->level_nr, tape->file_version, tape->game_version);
   Print("                  (effective engine version %08d)\n",
        tape->engine_version);
   Print("Level series identifier: '%s'\n", tape->level_identifier);
+
+  Print("Solution tape: %s\n",
+       tape->solved ? "yes" :
+       tape->game_version < VERSION_IDENT(4,3,2,3) ? "unknown" : "no");
+
+  Print("Special tape properties: ");
+  if (tape->property_bits == TAPE_PROPERTY_NONE)
+    Print("[none]");
+  if (tape->property_bits & TAPE_PROPERTY_EM_RANDOM_BUG)
+    Print("[em_random_bug]");
+  if (tape->property_bits & TAPE_PROPERTY_GAME_SPEED)
+    Print("[game_speed]");
+  if (tape->property_bits & TAPE_PROPERTY_PAUSE_MODE)
+    Print("[pause]");
+  if (tape->property_bits & TAPE_PROPERTY_SINGLE_STEP)
+    Print("[single_step]");
+  if (tape->property_bits & TAPE_PROPERTY_SNAPSHOT)
+    Print("[snapshot]");
+  if (tape->property_bits & TAPE_PROPERTY_REPLAYED)
+    Print("[replayed]");
+  if (tape->property_bits & TAPE_PROPERTY_TAS_KEYS)
+    Print("[tas_keys]");
+  if (tape->property_bits & TAPE_PROPERTY_SMALL_GRAPHICS)
+    Print("[small_graphics]");
+  Print("\n");
+
+  int year2 = tape->date / 10000;
+  int year4 = (year2 < 70 ? 2000 + year2 : 1900 + year2);
+  int month_index_raw = (tape->date / 100) % 100;
+  int month_index = month_index_raw % 12;      // prevent invalid index
+  int month = month_index + 1;
+  int day = tape->date % 100;
+
+  Print("Tape date: %04d-%02d-%02d\n", year4, month, day);
+
   PrintLine("-", 79);
 
   tape_frame_counter = 0;
@@ -8240,12 +8730,84 @@ void DumpTape(struct TapeInfo *tape)
   PrintLine("-", 79);
 }
 
+void DumpTapes(void)
+{
+  static LevelDirTree *dumptape_leveldir = NULL;
+
+  dumptape_leveldir = getTreeInfoFromIdentifier(leveldir_first,
+                                               global.dumptape_leveldir);
+
+  if (dumptape_leveldir == NULL)
+    Fail("no such level identifier: '%s'", global.dumptape_leveldir);
+
+  if (global.dumptape_level_nr < dumptape_leveldir->first_level ||
+      global.dumptape_level_nr > dumptape_leveldir->last_level)
+    Fail("no such level number: %d", global.dumptape_level_nr);
+
+  leveldir_current = dumptape_leveldir;
+
+  if (options.mytapes)
+    LoadTape(global.dumptape_level_nr);
+  else
+    LoadSolutionTape(global.dumptape_level_nr);
+
+  DumpTape(&tape);
+
+  CloseAllAndExit(0);
+}
+
 
 // ============================================================================
 // score file functions
 // ============================================================================
 
-void LoadScore(int nr)
+static void setScoreInfoToDefaultsExt(struct ScoreInfo *scores)
+{
+  int i;
+
+  for (i = 0; i < MAX_SCORE_ENTRIES; i++)
+  {
+    strcpy(scores->entry[i].tape_basename, UNDEFINED_FILENAME);
+    strcpy(scores->entry[i].name, EMPTY_PLAYER_NAME);
+    scores->entry[i].score = 0;
+    scores->entry[i].time = 0;
+
+    scores->entry[i].id = -1;
+    strcpy(scores->entry[i].tape_date,    UNKNOWN_NAME);
+    strcpy(scores->entry[i].platform,     UNKNOWN_NAME);
+    strcpy(scores->entry[i].version,      UNKNOWN_NAME);
+    strcpy(scores->entry[i].country_name, UNKNOWN_NAME);
+    strcpy(scores->entry[i].country_code, "??");
+  }
+
+  scores->num_entries = 0;
+  scores->last_added = -1;
+  scores->last_added_local = -1;
+
+  scores->updated = FALSE;
+  scores->uploaded = FALSE;
+  scores->tape_downloaded = FALSE;
+  scores->force_last_added = FALSE;
+
+  // The following values are intentionally not reset here:
+  // - last_level_nr
+  // - last_entry_nr
+  // - next_level_nr
+  // - continue_playing
+  // - continue_on_return
+}
+
+static void setScoreInfoToDefaults(void)
+{
+  setScoreInfoToDefaultsExt(&scores);
+}
+
+static void setServerScoreInfoToDefaults(void)
+{
+  setScoreInfoToDefaultsExt(&server_scores);
+}
+
+static void LoadScore_OLD(int nr)
 {
   int i;
   char *filename = getScoreFilename(nr);
@@ -8254,13 +8816,6 @@ void LoadScore(int nr)
   char *line_ptr;
   FILE *file;
 
-  // always start with reliable default values
-  for (i = 0; i < MAX_SCORE_ENTRIES; i++)
-  {
-    strcpy(highscore[i].Name, EMPTY_PLAYER_NAME);
-    highscore[i].Score = 0;
-  }
-
   if (!(file = fopen(filename, MODE_READ)))
     return;
 
@@ -8270,17 +8825,20 @@ void LoadScore(int nr)
   if (strlen(cookie) > 0 && cookie[strlen(cookie) - 1] == '\n')
     cookie[strlen(cookie) - 1] = '\0';
 
-  if (!checkCookieString(cookie, SCORE_COOKIE))
+  if (!checkCookieString(cookie, SCORE_COOKIE_TMPL))
   {
-    Error(ERR_WARN, "unknown format of score file '%s'", filename);
+    Warn("unknown format of score file '%s'", filename);
+
     fclose(file);
+
     return;
   }
 
   for (i = 0; i < MAX_SCORE_ENTRIES; i++)
   {
-    if (fscanf(file, "%d", &highscore[i].Score) == EOF)
-      Error(ERR_WARN, "fscanf() failed; %s", strerror(errno));
+    if (fscanf(file, "%d", &scores.entry[i].score) == EOF)
+      Warn("fscanf() failed; %s", strerror(errno));
+
     if (fgets(line, MAX_LINE_LEN, file) == NULL)
       line[0] = '\0';
 
@@ -8291,8 +8849,8 @@ void LoadScore(int nr)
     {
       if (*line_ptr != ' ' && *line_ptr != '\t' && *line_ptr != '\0')
       {
-       strncpy(highscore[i].Name, line_ptr, MAX_PLAYER_NAME_LEN);
-       highscore[i].Name[MAX_PLAYER_NAME_LEN] = '\0';
+       strncpy(scores.entry[i].name, line_ptr, MAX_PLAYER_NAME_LEN);
+       scores.entry[i].name[MAX_PLAYER_NAME_LEN] = '\0';
        break;
       }
     }
@@ -8301,523 +8859,1565 @@ void LoadScore(int nr)
   fclose(file);
 }
 
-void SaveScore(int nr)
+static void ConvertScore_OLD(void)
 {
-  int i;
-  int permissions = (program.global_scores ? PERMS_PUBLIC : PERMS_PRIVATE);
-  char *filename = getScoreFilename(nr);
-  FILE *file;
-
-  // used instead of "leveldir_current->subdir" (for network games)
-  InitScoreDirectory(levelset.identifier);
-
-  if (!(file = fopen(filename, MODE_WRITE)))
-  {
-    Error(ERR_WARN, "cannot save score for level %d", nr);
+  // only convert score to time for levels that rate playing time over score
+  if (!level.rate_time_over_score)
     return;
-  }
 
-  fprintf(file, "%s\n\n", SCORE_COOKIE);
+  // 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++)
-    fprintf(file, "%d %s\n", highscore[i].Score, highscore[i].Name);
-
-  fclose(file);
+  {
+    int score = scores.entry[i].score;
 
-  SetFilePermissions(filename, permissions);
+    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);
 
-// ============================================================================
-// setup file functions
-// ============================================================================
+  return chunk_size;
+}
 
-#define TOKEN_STR_PLAYER_PREFIX                        "player_"
+static int LoadScore_INFO(File *file, int chunk_size, struct ScoreInfo *scores)
+{
+  char *level_identifier = NULL;
+  int level_identifier_size;
+  int i;
 
-// global setup
-enum
-{
-  SETUP_TOKEN_PLAYER_NAME = 0,
-  SETUP_TOKEN_SOUND,
-  SETUP_TOKEN_SOUND_LOOPS,
-  SETUP_TOKEN_SOUND_MUSIC,
-  SETUP_TOKEN_SOUND_SIMPLE,
-  SETUP_TOKEN_TOONS,
-  SETUP_TOKEN_SCROLL_DELAY,
-  SETUP_TOKEN_SCROLL_DELAY_VALUE,
-  SETUP_TOKEN_ENGINE_SNAPSHOT_MODE,
-  SETUP_TOKEN_ENGINE_SNAPSHOT_MEMORY,
-  SETUP_TOKEN_FADE_SCREENS,
-  SETUP_TOKEN_AUTORECORD,
-  SETUP_TOKEN_SHOW_TITLESCREEN,
-  SETUP_TOKEN_QUICK_DOORS,
-  SETUP_TOKEN_TEAM_MODE,
-  SETUP_TOKEN_HANDICAP,
-  SETUP_TOKEN_SKIP_LEVELS,
-  SETUP_TOKEN_INCREMENT_LEVELS,
-  SETUP_TOKEN_AUTO_PLAY_NEXT_LEVEL,
-  SETUP_TOKEN_SKIP_SCORES_AFTER_GAME,
-  SETUP_TOKEN_TIME_LIMIT,
-  SETUP_TOKEN_FULLSCREEN,
-  SETUP_TOKEN_WINDOW_SCALING_PERCENT,
-  SETUP_TOKEN_WINDOW_SCALING_QUALITY,
-  SETUP_TOKEN_SCREEN_RENDERING_MODE,
-  SETUP_TOKEN_VSYNC_MODE,
-  SETUP_TOKEN_ASK_ON_ESCAPE,
-  SETUP_TOKEN_ASK_ON_ESCAPE_EDITOR,
-  SETUP_TOKEN_ASK_ON_GAME_OVER,
-  SETUP_TOKEN_QUICK_SWITCH,
-  SETUP_TOKEN_INPUT_ON_FOCUS,
-  SETUP_TOKEN_PREFER_AGA_GRAPHICS,
-  SETUP_TOKEN_GAME_SPEED_EXTENDED,
-  SETUP_TOKEN_GAME_FRAME_DELAY,
-  SETUP_TOKEN_SP_SHOW_BORDER_ELEMENTS,
-  SETUP_TOKEN_SMALL_GAME_GRAPHICS,
-  SETUP_TOKEN_SHOW_SNAPSHOT_BUTTONS,
-  SETUP_TOKEN_GRAPHICS_SET,
-  SETUP_TOKEN_SOUNDS_SET,
-  SETUP_TOKEN_MUSIC_SET,
-  SETUP_TOKEN_OVERRIDE_LEVEL_GRAPHICS,
-  SETUP_TOKEN_OVERRIDE_LEVEL_SOUNDS,
-  SETUP_TOKEN_OVERRIDE_LEVEL_MUSIC,
-  SETUP_TOKEN_VOLUME_SIMPLE,
-  SETUP_TOKEN_VOLUME_LOOPS,
-  SETUP_TOKEN_VOLUME_MUSIC,
-  SETUP_TOKEN_NETWORK_MODE,
-  SETUP_TOKEN_NETWORK_PLAYER_NR,
-  SETUP_TOKEN_NETWORK_SERVER_HOSTNAME,
-  SETUP_TOKEN_TOUCH_CONTROL_TYPE,
-  SETUP_TOKEN_TOUCH_MOVE_DISTANCE,
-  SETUP_TOKEN_TOUCH_DROP_DISTANCE,
-  SETUP_TOKEN_TOUCH_TRANSPARENCY,
-  SETUP_TOKEN_TOUCH_DRAW_OUTLINED,
-  SETUP_TOKEN_TOUCH_DRAW_PRESSED,
-  SETUP_TOKEN_TOUCH_GRID_XSIZE_0,
-  SETUP_TOKEN_TOUCH_GRID_YSIZE_0,
-  SETUP_TOKEN_TOUCH_GRID_XSIZE_1,
-  SETUP_TOKEN_TOUCH_GRID_YSIZE_1,
-
-  NUM_GLOBAL_SETUP_TOKENS
-};
+  level_identifier_size = getFile16BitBE(file);
 
-// auto setup
-enum
-{
-  SETUP_TOKEN_AUTO_EDITOR_ZOOM_TILESIZE = 0,
+  level_identifier = checked_malloc(level_identifier_size);
 
-  NUM_AUTO_SETUP_TOKENS
-};
+  for (i = 0; i < level_identifier_size; i++)
+    level_identifier[i] = getFile8Bit(file);
 
-// editor setup
-enum
-{
-  SETUP_TOKEN_EDITOR_EL_CLASSIC = 0,
-  SETUP_TOKEN_EDITOR_EL_CUSTOM,
-  SETUP_TOKEN_EDITOR_EL_USER_DEFINED,
-  SETUP_TOKEN_EDITOR_EL_DYNAMIC,
-  SETUP_TOKEN_EDITOR_EL_HEADLINES,
-  SETUP_TOKEN_EDITOR_SHOW_ELEMENT_TOKEN,
+  strncpy(scores->level_identifier, level_identifier, MAX_FILENAME_LEN);
+  scores->level_identifier[MAX_FILENAME_LEN] = '\0';
 
-  NUM_EDITOR_SETUP_TOKENS
-};
+  checked_free(level_identifier);
 
-// editor cascade setup
-enum
-{
-  SETUP_TOKEN_EDITOR_CASCADE_BD = 0,
-  SETUP_TOKEN_EDITOR_CASCADE_EM,
-  SETUP_TOKEN_EDITOR_CASCADE_EMC,
-  SETUP_TOKEN_EDITOR_CASCADE_RND,
-  SETUP_TOKEN_EDITOR_CASCADE_SB,
-  SETUP_TOKEN_EDITOR_CASCADE_SP,
-  SETUP_TOKEN_EDITOR_CASCADE_DC,
-  SETUP_TOKEN_EDITOR_CASCADE_DX,
-  SETUP_TOKEN_EDITOR_CASCADE_TEXT,
-  SETUP_TOKEN_EDITOR_CASCADE_STEELTEXT,
-  SETUP_TOKEN_EDITOR_CASCADE_CE,
-  SETUP_TOKEN_EDITOR_CASCADE_GE,
-  SETUP_TOKEN_EDITOR_CASCADE_REF,
-  SETUP_TOKEN_EDITOR_CASCADE_USER,
-  SETUP_TOKEN_EDITOR_CASCADE_DYNAMIC,
-
-  NUM_EDITOR_CASCADE_SETUP_TOKENS
-};
+  scores->level_nr = getFile16BitBE(file);
+  scores->num_entries = getFile16BitBE(file);
 
-// shortcut setup
-enum
-{
-  SETUP_TOKEN_SHORTCUT_SAVE_GAME = 0,
-  SETUP_TOKEN_SHORTCUT_LOAD_GAME,
-  SETUP_TOKEN_SHORTCUT_TOGGLE_PAUSE,
-  SETUP_TOKEN_SHORTCUT_FOCUS_PLAYER_1,
-  SETUP_TOKEN_SHORTCUT_FOCUS_PLAYER_2,
-  SETUP_TOKEN_SHORTCUT_FOCUS_PLAYER_3,
-  SETUP_TOKEN_SHORTCUT_FOCUS_PLAYER_4,
-  SETUP_TOKEN_SHORTCUT_FOCUS_PLAYER_ALL,
-  SETUP_TOKEN_SHORTCUT_TAPE_EJECT,
-  SETUP_TOKEN_SHORTCUT_TAPE_EXTRA,
-  SETUP_TOKEN_SHORTCUT_TAPE_STOP,
-  SETUP_TOKEN_SHORTCUT_TAPE_PAUSE,
-  SETUP_TOKEN_SHORTCUT_TAPE_RECORD,
-  SETUP_TOKEN_SHORTCUT_TAPE_PLAY,
-  SETUP_TOKEN_SHORTCUT_SOUND_SIMPLE,
-  SETUP_TOKEN_SHORTCUT_SOUND_LOOPS,
-  SETUP_TOKEN_SHORTCUT_SOUND_MUSIC,
-  SETUP_TOKEN_SHORTCUT_SNAP_LEFT,
-  SETUP_TOKEN_SHORTCUT_SNAP_RIGHT,
-  SETUP_TOKEN_SHORTCUT_SNAP_UP,
-  SETUP_TOKEN_SHORTCUT_SNAP_DOWN,
-
-  NUM_SHORTCUT_SETUP_TOKENS
-};
+  chunk_size = 2 + level_identifier_size + 2 + 2;
 
-// player setup
-enum
-{
-  SETUP_TOKEN_PLAYER_USE_JOYSTICK = 0,
-  SETUP_TOKEN_PLAYER_JOY_DEVICE_NAME,
-  SETUP_TOKEN_PLAYER_JOY_XLEFT,
-  SETUP_TOKEN_PLAYER_JOY_XMIDDLE,
-  SETUP_TOKEN_PLAYER_JOY_XRIGHT,
-  SETUP_TOKEN_PLAYER_JOY_YUPPER,
-  SETUP_TOKEN_PLAYER_JOY_YMIDDLE,
-  SETUP_TOKEN_PLAYER_JOY_YLOWER,
-  SETUP_TOKEN_PLAYER_JOY_SNAP,
-  SETUP_TOKEN_PLAYER_JOY_DROP,
-  SETUP_TOKEN_PLAYER_KEY_LEFT,
-  SETUP_TOKEN_PLAYER_KEY_RIGHT,
-  SETUP_TOKEN_PLAYER_KEY_UP,
-  SETUP_TOKEN_PLAYER_KEY_DOWN,
-  SETUP_TOKEN_PLAYER_KEY_SNAP,
-  SETUP_TOKEN_PLAYER_KEY_DROP,
-
-  NUM_PLAYER_SETUP_TOKENS
-};
+  return chunk_size;
+}
 
-// system setup
-enum
+static int LoadScore_NAME(File *file, int chunk_size, struct ScoreInfo *scores)
 {
-  SETUP_TOKEN_SYSTEM_SDL_VIDEODRIVER = 0,
-  SETUP_TOKEN_SYSTEM_SDL_AUDIODRIVER,
-  SETUP_TOKEN_SYSTEM_AUDIO_FRAGMENT_SIZE,
+  int i, j;
 
-  NUM_SYSTEM_SETUP_TOKENS
-};
+  for (i = 0; i < scores->num_entries; i++)
+  {
+    for (j = 0; j < MAX_PLAYER_NAME_LEN; j++)
+      scores->entry[i].name[j] = getFile8Bit(file);
 
-// internal setup
-enum
-{
-  SETUP_TOKEN_INT_PROGRAM_TITLE = 0,
-  SETUP_TOKEN_INT_PROGRAM_VERSION,
-  SETUP_TOKEN_INT_PROGRAM_AUTHOR,
-  SETUP_TOKEN_INT_PROGRAM_EMAIL,
-  SETUP_TOKEN_INT_PROGRAM_WEBSITE,
-  SETUP_TOKEN_INT_PROGRAM_COPYRIGHT,
-  SETUP_TOKEN_INT_PROGRAM_COMPANY,
-  SETUP_TOKEN_INT_PROGRAM_ICON_FILE,
-  SETUP_TOKEN_INT_DEFAULT_GRAPHICS_SET,
-  SETUP_TOKEN_INT_DEFAULT_SOUNDS_SET,
-  SETUP_TOKEN_INT_DEFAULT_MUSIC_SET,
-  SETUP_TOKEN_INT_FALLBACK_GRAPHICS_FILE,
-  SETUP_TOKEN_INT_FALLBACK_SOUNDS_FILE,
-  SETUP_TOKEN_INT_FALLBACK_MUSIC_FILE,
-  SETUP_TOKEN_INT_DEFAULT_LEVEL_SERIES,
-  SETUP_TOKEN_INT_CHOOSE_FROM_TOP_LEVELDIR,
-  SETUP_TOKEN_INT_SHOW_SCALING_IN_TITLE,
-  SETUP_TOKEN_INT_DEFAULT_WINDOW_WIDTH,
-  SETUP_TOKEN_INT_DEFAULT_WINDOW_HEIGHT,
-
-  NUM_INTERNAL_SETUP_TOKENS
-};
+    scores->entry[i].name[MAX_PLAYER_NAME_LEN] = '\0';
+  }
 
-// debug setup
-enum
-{
-  SETUP_TOKEN_DEBUG_FRAME_DELAY_0 = 0,
-  SETUP_TOKEN_DEBUG_FRAME_DELAY_1,
-  SETUP_TOKEN_DEBUG_FRAME_DELAY_2,
-  SETUP_TOKEN_DEBUG_FRAME_DELAY_3,
-  SETUP_TOKEN_DEBUG_FRAME_DELAY_4,
-  SETUP_TOKEN_DEBUG_FRAME_DELAY_5,
-  SETUP_TOKEN_DEBUG_FRAME_DELAY_6,
-  SETUP_TOKEN_DEBUG_FRAME_DELAY_7,
-  SETUP_TOKEN_DEBUG_FRAME_DELAY_8,
-  SETUP_TOKEN_DEBUG_FRAME_DELAY_9,
-  SETUP_TOKEN_DEBUG_FRAME_DELAY_KEY_0,
-  SETUP_TOKEN_DEBUG_FRAME_DELAY_KEY_1,
-  SETUP_TOKEN_DEBUG_FRAME_DELAY_KEY_2,
-  SETUP_TOKEN_DEBUG_FRAME_DELAY_KEY_3,
-  SETUP_TOKEN_DEBUG_FRAME_DELAY_KEY_4,
-  SETUP_TOKEN_DEBUG_FRAME_DELAY_KEY_5,
-  SETUP_TOKEN_DEBUG_FRAME_DELAY_KEY_6,
-  SETUP_TOKEN_DEBUG_FRAME_DELAY_KEY_7,
-  SETUP_TOKEN_DEBUG_FRAME_DELAY_KEY_8,
-  SETUP_TOKEN_DEBUG_FRAME_DELAY_KEY_9,
-  SETUP_TOKEN_DEBUG_FRAME_DELAY_USE_MOD_KEY,
-  SETUP_TOKEN_DEBUG_FRAME_DELAY_GAME_ONLY,
-  SETUP_TOKEN_DEBUG_SHOW_FRAMES_PER_SECOND,
-
-  NUM_DEBUG_SETUP_TOKENS
-};
+  chunk_size = scores->num_entries * MAX_PLAYER_NAME_LEN;
 
-// options setup
-enum
-{
-  SETUP_TOKEN_OPTIONS_VERBOSE = 0,
+  return chunk_size;
+}
 
-  NUM_OPTIONS_SETUP_TOKENS
-};
+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);
 
-static struct SetupInfo si;
-static struct SetupAutoSetupInfo sasi;
-static struct SetupEditorInfo sei;
-static struct SetupEditorCascadeInfo seci;
-static struct SetupShortcutInfo ssi;
-static struct SetupInputInfo sii;
-static struct SetupSystemInfo syi;
-static struct SetupInternalInfo sxi;
-static struct SetupDebugInfo sdi;
-static struct OptionInfo soi;
+  chunk_size = scores->num_entries * 2;
 
-static struct TokenInfo global_setup_tokens[] =
-{
-  { TYPE_STRING, &si.player_name,             "player_name"            },
-  { TYPE_SWITCH, &si.sound,                   "sound"                  },
-  { TYPE_SWITCH, &si.sound_loops,             "repeating_sound_loops"  },
-  { TYPE_SWITCH, &si.sound_music,             "background_music"       },
-  { TYPE_SWITCH, &si.sound_simple,            "simple_sound_effects"   },
-  { TYPE_SWITCH, &si.toons,                   "toons"                  },
-  { TYPE_SWITCH, &si.scroll_delay,            "scroll_delay"           },
-  { TYPE_INTEGER,&si.scroll_delay_value,      "scroll_delay_value"     },
-  { TYPE_STRING, &si.engine_snapshot_mode,    "engine_snapshot_mode"   },
-  { TYPE_INTEGER,&si.engine_snapshot_memory,  "engine_snapshot_memory" },
-  { TYPE_SWITCH, &si.fade_screens,            "fade_screens"           },
-  { TYPE_SWITCH, &si.autorecord,              "automatic_tape_recording"},
-  { TYPE_SWITCH, &si.show_titlescreen,        "show_titlescreen"       },
-  { TYPE_SWITCH, &si.quick_doors,             "quick_doors"            },
-  { TYPE_SWITCH, &si.team_mode,               "team_mode"              },
-  { TYPE_SWITCH, &si.handicap,                "handicap"               },
-  { TYPE_SWITCH, &si.skip_levels,             "skip_levels"            },
-  { TYPE_SWITCH, &si.increment_levels,        "increment_levels"       },
-  { TYPE_SWITCH, &si.auto_play_next_level,    "auto_play_next_level"   },
-  { TYPE_SWITCH, &si.skip_scores_after_game,  "skip_scores_after_game" },
-  { TYPE_SWITCH, &si.time_limit,              "time_limit"             },
-  { TYPE_SWITCH, &si.fullscreen,              "fullscreen"             },
-  { TYPE_INTEGER,&si.window_scaling_percent,  "window_scaling_percent" },
-  { TYPE_STRING, &si.window_scaling_quality,  "window_scaling_quality" },
-  { TYPE_STRING, &si.screen_rendering_mode,   "screen_rendering_mode"  },
-  { TYPE_STRING, &si.vsync_mode,              "vsync_mode"             },
-  { TYPE_SWITCH, &si.ask_on_escape,           "ask_on_escape"          },
-  { TYPE_SWITCH, &si.ask_on_escape_editor,    "ask_on_escape_editor"   },
-  { TYPE_SWITCH, &si.ask_on_game_over,        "ask_on_game_over"       },
-  { TYPE_SWITCH, &si.quick_switch,            "quick_player_switch"    },
-  { TYPE_SWITCH, &si.input_on_focus,          "input_on_focus"         },
-  { TYPE_SWITCH, &si.prefer_aga_graphics,     "prefer_aga_graphics"    },
-  { TYPE_SWITCH, &si.game_speed_extended,     "game_speed_extended"    },
-  { TYPE_INTEGER,&si.game_frame_delay,        "game_frame_delay"       },
-  { TYPE_SWITCH, &si.sp_show_border_elements, "sp_show_border_elements"        },
-  { TYPE_SWITCH, &si.small_game_graphics,     "small_game_graphics"    },
-  { TYPE_SWITCH, &si.show_snapshot_buttons,   "show_snapshot_buttons"  },
-  { TYPE_STRING, &si.graphics_set,            "graphics_set"           },
-  { TYPE_STRING, &si.sounds_set,              "sounds_set"             },
-  { TYPE_STRING, &si.music_set,               "music_set"              },
-  { TYPE_SWITCH3,&si.override_level_graphics, "override_level_graphics"        },
-  { TYPE_SWITCH3,&si.override_level_sounds,   "override_level_sounds"  },
-  { TYPE_SWITCH3,&si.override_level_music,    "override_level_music"   },
-  { TYPE_INTEGER,&si.volume_simple,           "volume_simple"          },
-  { TYPE_INTEGER,&si.volume_loops,            "volume_loops"           },
-  { TYPE_INTEGER,&si.volume_music,            "volume_music"           },
-  { TYPE_SWITCH, &si.network_mode,            "network_mode"           },
-  { TYPE_PLAYER, &si.network_player_nr,       "network_player"         },
-  { TYPE_STRING, &si.network_server_hostname, "network_server_hostname"        },
-  { TYPE_STRING, &si.touch.control_type,      "touch.control_type"     },
-  { TYPE_INTEGER,&si.touch.move_distance,     "touch.move_distance"    },
-  { TYPE_INTEGER,&si.touch.drop_distance,     "touch.drop_distance"    },
-  { TYPE_INTEGER,&si.touch.transparency,      "touch.transparency"     },
-  { TYPE_INTEGER,&si.touch.draw_outlined,     "touch.draw_outlined"    },
-  { TYPE_INTEGER,&si.touch.draw_pressed,      "touch.draw_pressed"     },
-  { TYPE_INTEGER,&si.touch.grid_xsize[0],     "touch.virtual_buttons.0.xsize" },
-  { TYPE_INTEGER,&si.touch.grid_ysize[0],     "touch.virtual_buttons.0.ysize" },
-  { TYPE_INTEGER,&si.touch.grid_xsize[1],     "touch.virtual_buttons.1.xsize" },
-  { TYPE_INTEGER,&si.touch.grid_ysize[1],     "touch.virtual_buttons.1.ysize" },
-};
+  return chunk_size;
+}
 
-static struct TokenInfo auto_setup_tokens[] =
+static int LoadScore_SC4R(File *file, int chunk_size, struct ScoreInfo *scores)
 {
-  { TYPE_INTEGER,&sasi.editor_zoom_tilesize,   "editor.zoom_tilesize"  },
-};
+  int i;
 
-static struct TokenInfo editor_setup_tokens[] =
-{
-  { TYPE_SWITCH, &sei.el_classic,      "editor.el_classic"             },
-  { TYPE_SWITCH, &sei.el_custom,       "editor.el_custom"              },
-  { TYPE_SWITCH, &sei.el_user_defined, "editor.el_user_defined"        },
-  { TYPE_SWITCH, &sei.el_dynamic,      "editor.el_dynamic"             },
-  { TYPE_SWITCH, &sei.el_headlines,    "editor.el_headlines"           },
-  { TYPE_SWITCH, &sei.show_element_token,"editor.show_element_token"   },
-};
+  for (i = 0; i < scores->num_entries; i++)
+    scores->entry[i].score = getFile32BitBE(file);
 
-static struct TokenInfo editor_cascade_setup_tokens[] =
-{
-  { TYPE_SWITCH, &seci.el_bd,          "editor.cascade.el_bd"          },
-  { TYPE_SWITCH, &seci.el_em,          "editor.cascade.el_em"          },
-  { TYPE_SWITCH, &seci.el_emc,         "editor.cascade.el_emc"         },
-  { TYPE_SWITCH, &seci.el_rnd,         "editor.cascade.el_rnd"         },
-  { TYPE_SWITCH, &seci.el_sb,          "editor.cascade.el_sb"          },
-  { TYPE_SWITCH, &seci.el_sp,          "editor.cascade.el_sp"          },
-  { TYPE_SWITCH, &seci.el_dc,          "editor.cascade.el_dc"          },
-  { TYPE_SWITCH, &seci.el_dx,          "editor.cascade.el_dx"          },
-  { TYPE_SWITCH, &seci.el_mm,          "editor.cascade.el_mm"          },
-  { TYPE_SWITCH, &seci.el_df,          "editor.cascade.el_df"          },
-  { TYPE_SWITCH, &seci.el_chars,       "editor.cascade.el_chars"       },
-  { TYPE_SWITCH, &seci.el_steel_chars, "editor.cascade.el_steel_chars" },
-  { TYPE_SWITCH, &seci.el_ce,          "editor.cascade.el_ce"          },
-  { TYPE_SWITCH, &seci.el_ge,          "editor.cascade.el_ge"          },
-  { TYPE_SWITCH, &seci.el_ref,         "editor.cascade.el_ref"         },
-  { TYPE_SWITCH, &seci.el_user,                "editor.cascade.el_user"        },
-  { TYPE_SWITCH, &seci.el_dynamic,     "editor.cascade.el_dynamic"     },
-};
+  chunk_size = scores->num_entries * 4;
 
-static struct TokenInfo shortcut_setup_tokens[] =
-{
-  { TYPE_KEY_X11, &ssi.save_game,      "shortcut.save_game"            },
-  { TYPE_KEY_X11, &ssi.load_game,      "shortcut.load_game"            },
-  { TYPE_KEY_X11, &ssi.toggle_pause,   "shortcut.toggle_pause"         },
-  { TYPE_KEY_X11, &ssi.focus_player[0],        "shortcut.focus_player_1"       },
-  { TYPE_KEY_X11, &ssi.focus_player[1],        "shortcut.focus_player_2"       },
-  { TYPE_KEY_X11, &ssi.focus_player[2],        "shortcut.focus_player_3"       },
-  { TYPE_KEY_X11, &ssi.focus_player[3],        "shortcut.focus_player_4"       },
-  { TYPE_KEY_X11, &ssi.focus_player_all,"shortcut.focus_player_all"    },
-  { TYPE_KEY_X11, &ssi.tape_eject,     "shortcut.tape_eject"           },
-  { TYPE_KEY_X11, &ssi.tape_extra,     "shortcut.tape_extra"           },
-  { TYPE_KEY_X11, &ssi.tape_stop,      "shortcut.tape_stop"            },
-  { TYPE_KEY_X11, &ssi.tape_pause,     "shortcut.tape_pause"           },
-  { TYPE_KEY_X11, &ssi.tape_record,    "shortcut.tape_record"          },
-  { TYPE_KEY_X11, &ssi.tape_play,      "shortcut.tape_play"            },
-  { TYPE_KEY_X11, &ssi.sound_simple,   "shortcut.sound_simple"         },
-  { TYPE_KEY_X11, &ssi.sound_loops,    "shortcut.sound_loops"          },
-  { TYPE_KEY_X11, &ssi.sound_music,    "shortcut.sound_music"          },
-  { TYPE_KEY_X11, &ssi.snap_left,      "shortcut.snap_left"            },
-  { TYPE_KEY_X11, &ssi.snap_right,     "shortcut.snap_right"           },
-  { TYPE_KEY_X11, &ssi.snap_up,                "shortcut.snap_up"              },
-  { TYPE_KEY_X11, &ssi.snap_down,      "shortcut.snap_down"            },
-};
+  return chunk_size;
+}
 
-static struct TokenInfo player_setup_tokens[] =
+static int LoadScore_TIME(File *file, int chunk_size, struct ScoreInfo *scores)
 {
-  { TYPE_BOOLEAN, &sii.use_joystick,   ".use_joystick"                 },
-  { TYPE_STRING,  &sii.joy.device_name,        ".joy.device_name"              },
-  { TYPE_INTEGER, &sii.joy.xleft,      ".joy.xleft"                    },
-  { TYPE_INTEGER, &sii.joy.xmiddle,    ".joy.xmiddle"                  },
-  { TYPE_INTEGER, &sii.joy.xright,     ".joy.xright"                   },
-  { TYPE_INTEGER, &sii.joy.yupper,     ".joy.yupper"                   },
-  { TYPE_INTEGER, &sii.joy.ymiddle,    ".joy.ymiddle"                  },
-  { TYPE_INTEGER, &sii.joy.ylower,     ".joy.ylower"                   },
-  { TYPE_INTEGER, &sii.joy.snap,       ".joy.snap_field"               },
-  { TYPE_INTEGER, &sii.joy.drop,       ".joy.place_bomb"               },
-  { TYPE_KEY_X11, &sii.key.left,       ".key.move_left"                },
-  { TYPE_KEY_X11, &sii.key.right,      ".key.move_right"               },
-  { TYPE_KEY_X11, &sii.key.up,         ".key.move_up"                  },
-  { TYPE_KEY_X11, &sii.key.down,       ".key.move_down"                },
-  { TYPE_KEY_X11, &sii.key.snap,       ".key.snap_field"               },
-  { TYPE_KEY_X11, &sii.key.drop,       ".key.place_bomb"               },
-};
+  int i;
 
-static struct TokenInfo system_setup_tokens[] =
-{
-  { TYPE_STRING,  &syi.sdl_videodriver,    "system.sdl_videodriver"    },
-  { TYPE_STRING,  &syi.sdl_audiodriver,           "system.sdl_audiodriver"     },
-  { TYPE_INTEGER, &syi.audio_fragment_size,"system.audio_fragment_size"        },
-};
+  for (i = 0; i < scores->num_entries; i++)
+    scores->entry[i].time = getFile32BitBE(file);
 
-static struct TokenInfo internal_setup_tokens[] =
-{
-  { TYPE_STRING, &sxi.program_title,           "program_title"         },
-  { TYPE_STRING, &sxi.program_version,         "program_version"       },
-  { TYPE_STRING, &sxi.program_author,          "program_author"        },
-  { TYPE_STRING, &sxi.program_email,           "program_email"         },
-  { TYPE_STRING, &sxi.program_website,         "program_website"       },
-  { TYPE_STRING, &sxi.program_copyright,       "program_copyright"     },
-  { TYPE_STRING, &sxi.program_company,         "program_company"       },
-  { TYPE_STRING, &sxi.program_icon_file,       "program_icon_file"     },
-  { TYPE_STRING, &sxi.default_graphics_set,    "default_graphics_set"  },
-  { TYPE_STRING, &sxi.default_sounds_set,      "default_sounds_set"    },
-  { TYPE_STRING, &sxi.default_music_set,       "default_music_set"     },
-  { TYPE_STRING, &sxi.fallback_graphics_file,  "fallback_graphics_file"},
-  { TYPE_STRING, &sxi.fallback_sounds_file,    "fallback_sounds_file"  },
-  { TYPE_STRING, &sxi.fallback_music_file,     "fallback_music_file"   },
-  { TYPE_STRING, &sxi.default_level_series,    "default_level_series"  },
-  { TYPE_BOOLEAN,&sxi.choose_from_top_leveldir,        "choose_from_top_leveldir" },
-  { TYPE_BOOLEAN,&sxi.show_scaling_in_title,   "show_scaling_in_title" },
-  { TYPE_INTEGER,&sxi.default_window_width,    "default_window_width"  },
-  { TYPE_INTEGER,&sxi.default_window_height,   "default_window_height" },
-};
+  chunk_size = scores->num_entries * 4;
 
-static struct TokenInfo debug_setup_tokens[] =
-{
-  { TYPE_INTEGER, &sdi.frame_delay[0],         "debug.frame_delay_0"   },
-  { TYPE_INTEGER, &sdi.frame_delay[1],         "debug.frame_delay_1"   },
-  { TYPE_INTEGER, &sdi.frame_delay[2],         "debug.frame_delay_2"   },
-  { TYPE_INTEGER, &sdi.frame_delay[3],         "debug.frame_delay_3"   },
-  { TYPE_INTEGER, &sdi.frame_delay[4],         "debug.frame_delay_4"   },
-  { TYPE_INTEGER, &sdi.frame_delay[5],         "debug.frame_delay_5"   },
-  { TYPE_INTEGER, &sdi.frame_delay[6],         "debug.frame_delay_6"   },
-  { TYPE_INTEGER, &sdi.frame_delay[7],         "debug.frame_delay_7"   },
-  { TYPE_INTEGER, &sdi.frame_delay[8],         "debug.frame_delay_8"   },
-  { TYPE_INTEGER, &sdi.frame_delay[9],         "debug.frame_delay_9"   },
-  { TYPE_KEY_X11, &sdi.frame_delay_key[0],     "debug.key.frame_delay_0" },
-  { TYPE_KEY_X11, &sdi.frame_delay_key[1],     "debug.key.frame_delay_1" },
-  { TYPE_KEY_X11, &sdi.frame_delay_key[2],     "debug.key.frame_delay_2" },
-  { TYPE_KEY_X11, &sdi.frame_delay_key[3],     "debug.key.frame_delay_3" },
-  { TYPE_KEY_X11, &sdi.frame_delay_key[4],     "debug.key.frame_delay_4" },
-  { TYPE_KEY_X11, &sdi.frame_delay_key[5],     "debug.key.frame_delay_5" },
-  { TYPE_KEY_X11, &sdi.frame_delay_key[6],     "debug.key.frame_delay_6" },
-  { TYPE_KEY_X11, &sdi.frame_delay_key[7],     "debug.key.frame_delay_7" },
-  { TYPE_KEY_X11, &sdi.frame_delay_key[8],     "debug.key.frame_delay_8" },
-  { TYPE_KEY_X11, &sdi.frame_delay_key[9],     "debug.key.frame_delay_9" },
-  { TYPE_BOOLEAN, &sdi.frame_delay_use_mod_key,"debug.frame_delay.use_mod_key"},
-  { TYPE_BOOLEAN, &sdi.frame_delay_game_only,  "debug.frame_delay.game_only" },
-  { TYPE_BOOLEAN, &sdi.show_frames_per_second, "debug.show_frames_per_second" },
-};
+  return chunk_size;
+}
 
-static struct TokenInfo options_setup_tokens[] =
+static int LoadScore_TAPE(File *file, int chunk_size, struct ScoreInfo *scores)
 {
-  { TYPE_BOOLEAN, &soi.verbose,                "options.verbose"               },
-};
+  int i, j;
 
-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);
+  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);
 
-  strncpy(login_name_new, login_name, MAX_PLAYER_NAME_LEN);
-  login_name_new[MAX_PLAYER_NAME_LEN] = '\0';
+    scores->entry[i].tape_basename[MAX_SCORE_TAPE_BASENAME_LEN] = '\0';
+  }
 
-  if (strlen(login_name) > MAX_PLAYER_NAME_LEN)                // name has been cut
-    if (strchr(login_name_new, ' '))
-      *strchr(login_name_new, ' ') = '\0';
+  chunk_size = scores->num_entries * MAX_SCORE_TAPE_BASENAME_LEN;
 
-  return login_name_new;
+  return chunk_size;
 }
 
-static void setSetupInfoToDefaults(struct SetupInfo *si)
+void LoadScore(int nr)
 {
-  int i;
-
-  si->player_name = get_corrected_login_name(getLoginName());
-
-  si->sound = TRUE;
+  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;
+
+  for (i = 0; i < scores->num_entries; i++)
+  {
+    int size = strlen(scores->entry[i].tape_basename);
+
+    for (j = 0; j < MAX_SCORE_TAPE_BASENAME_LEN; j++)
+      putFile8Bit(file, (j < size ? scores->entry[i].tape_basename[j] : 0));
+  }
+}
+
+static void SaveScoreToFilename(char *filename)
+{
+  FILE *file;
+  int info_chunk_size;
+  int name_chunk_size;
+  int scor_chunk_size;
+  int sc4r_chunk_size;
+  int time_chunk_size;
+  int tape_chunk_size;
+  boolean has_large_score_values;
+  int i;
+
+  if (!(file = fopen(filename, MODE_WRITE)))
+  {
+    Warn("cannot save score file '%s'", filename);
+
+    return;
+  }
+
+  info_chunk_size = 2 + (strlen(scores.level_identifier) + 1) + 2 + 2;
+  name_chunk_size = scores.num_entries * MAX_PLAYER_NAME_LEN;
+  scor_chunk_size = scores.num_entries * 2;
+  sc4r_chunk_size = scores.num_entries * 4;
+  time_chunk_size = scores.num_entries * 4;
+  tape_chunk_size = scores.num_entries * MAX_SCORE_TAPE_BASENAME_LEN;
+
+  has_large_score_values = FALSE;
+  for (i = 0; i < scores.num_entries; i++)
+    if (scores.entry[i].score > 0xffff)
+      has_large_score_values = TRUE;
+
+  putFileChunkBE(file, "RND1", CHUNK_SIZE_UNDEFINED);
+  putFileChunkBE(file, "SCOR", CHUNK_SIZE_NONE);
+
+  putFileChunkBE(file, "VERS", SCORE_CHUNK_VERS_SIZE);
+  SaveScore_VERS(file, &scores);
+
+  putFileChunkBE(file, "INFO", info_chunk_size);
+  SaveScore_INFO(file, &scores);
+
+  putFileChunkBE(file, "NAME", name_chunk_size);
+  SaveScore_NAME(file, &scores);
+
+  if (has_large_score_values)
+  {
+    putFileChunkBE(file, "SC4R", sc4r_chunk_size);
+    SaveScore_SC4R(file, &scores);
+  }
+  else
+  {
+    putFileChunkBE(file, "SCOR", scor_chunk_size);
+    SaveScore_SCOR(file, &scores);
+  }
+
+  putFileChunkBE(file, "TIME", time_chunk_size);
+  SaveScore_TIME(file, &scores);
+
+  putFileChunkBE(file, "TAPE", tape_chunk_size);
+  SaveScore_TAPE(file, &scores);
+
+  fclose(file);
+
+  SetFilePermissions(filename, PERMS_PRIVATE);
+}
+
+void SaveScore(int nr)
+{
+  char *filename = getScoreFilename(nr);
+  int i;
+
+  // used instead of "leveldir_current->subdir" (for network games)
+  InitScoreDirectory(levelset.identifier);
+
+  scores.file_version = FILE_VERSION_ACTUAL;
+  scores.game_version = GAME_VERSION_ACTUAL;
+
+  strncpy(scores.level_identifier, levelset.identifier, MAX_FILENAME_LEN);
+  scores.level_identifier[MAX_FILENAME_LEN] = '\0';
+  scores.level_nr = level_nr;
+
+  for (i = 0; i < MAX_SCORE_ENTRIES; i++)
+    if (scores.entry[i].score == 0 &&
+        scores.entry[i].time == 0 &&
+        strEqual(scores.entry[i].name, EMPTY_PLAYER_NAME))
+      break;
+
+  scores.num_entries = i;
+
+  if (scores.num_entries == 0)
+    return;
+
+  SaveScoreToFilename(filename);
+}
+
+static void LoadServerScoreFromCache(int nr)
+{
+  struct ScoreEntry score_entry;
+  struct
+  {
+    void *value;
+    boolean is_string;
+    int string_size;
+  }
+  score_mapping[] =
+  {
+    { &score_entry.score,              FALSE,  0                       },
+    { &score_entry.time,               FALSE,  0                       },
+    { score_entry.name,                        TRUE,   MAX_PLAYER_NAME_LEN     },
+    { score_entry.tape_basename,       TRUE,   MAX_FILENAME_LEN        },
+    { score_entry.tape_date,           TRUE,   MAX_ISO_DATE_LEN        },
+    { &score_entry.id,                 FALSE,  0                       },
+    { score_entry.platform,            TRUE,   MAX_PLATFORM_TEXT_LEN   },
+    { score_entry.version,             TRUE,   MAX_VERSION_TEXT_LEN    },
+    { score_entry.country_code,                TRUE,   MAX_COUNTRY_CODE_LEN    },
+    { score_entry.country_name,                TRUE,   MAX_COUNTRY_NAME_LEN    },
+
+    { NULL,                            FALSE,  0                       }
+  };
+  char *filename = getScoreCacheFilename(nr);
+  SetupFileHash *score_hash = loadSetupFileHash(filename);
+  int i, j;
+
+  server_scores.num_entries = 0;
+
+  if (score_hash == NULL)
+    return;
+
+  for (i = 0; i < MAX_SCORE_ENTRIES; i++)
+  {
+    score_entry = server_scores.entry[i];
+
+    for (j = 0; score_mapping[j].value != NULL; j++)
+    {
+      char token[10];
+
+      sprintf(token, "%02d.%d", i, j);
+
+      char *value = getHashEntry(score_hash, token);
+
+      if (value == NULL)
+       continue;
+
+      if (score_mapping[j].is_string)
+      {
+       char *score_value = (char *)score_mapping[j].value;
+       int value_size = score_mapping[j].string_size;
+
+       strncpy(score_value, value, value_size);
+       score_value[value_size] = '\0';
+      }
+      else
+      {
+       int *score_value = (int *)score_mapping[j].value;
+
+       *score_value = atoi(value);
+      }
+
+      server_scores.num_entries = i + 1;
+    }
+
+    server_scores.entry[i] = score_entry;
+  }
+
+  freeSetupFileHash(score_hash);
+}
+
+void LoadServerScore(int nr, boolean download_score)
+{
+  if (!setup.use_api_server)
+    return;
+
+  // always start with reliable default values
+  setServerScoreInfoToDefaults();
+
+  // 1st step: load server scores from cache file (which may not exist)
+  // (this should prevent reading it while the thread is writing to it)
+  LoadServerScoreFromCache(nr);
+
+  if (download_score && runtime.use_api_server)
+  {
+    // 2nd step: download server scores from score server to cache file
+    // (as thread, as it might time out if the server is not reachable)
+    ApiGetScoreAsThread(nr);
+  }
+}
+
+void PrepareScoreTapesForUpload(char *leveldir_subdir)
+{
+  MarkTapeDirectoryUploadsAsIncomplete(leveldir_subdir);
+
+  // if score tape not uploaded, ask for uploading missing tapes later
+  if (!setup.has_remaining_tapes)
+    setup.ask_for_remaining_tapes = TRUE;
+
+  setup.provide_uploading_tapes = TRUE;
+  setup.has_remaining_tapes = TRUE;
+
+  SaveSetup_ServerSetup();
+}
+
+void SaveServerScore(int nr, boolean tape_saved)
+{
+  if (!runtime.use_api_server)
+  {
+    PrepareScoreTapesForUpload(leveldir_current->subdir);
+
+    return;
+  }
+
+  ApiAddScoreAsThread(nr, tape_saved, NULL);
+}
+
+void SaveServerScoreFromFile(int nr, boolean tape_saved,
+                            char *score_tape_filename)
+{
+  if (!runtime.use_api_server)
+    return;
+
+  ApiAddScoreAsThread(nr, tape_saved, score_tape_filename);
+}
+
+void LoadLocalAndServerScore(int nr, boolean download_score)
+{
+  int last_added_local = scores.last_added_local;
+  boolean force_last_added = scores.force_last_added;
+
+  // needed if only showing server scores
+  setScoreInfoToDefaults();
+
+  if (!strEqual(setup.scores_in_highscore_list, STR_SCORES_TYPE_SERVER_ONLY))
+    LoadScore(nr);
+
+  // restore last added local score entry (before merging server scores)
+  scores.last_added = scores.last_added_local = last_added_local;
+
+  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);
+
+    // merge local scores with scores from server
+    MergeServerScore();
+  }
+
+  if (force_last_added)
+    scores.force_last_added = force_last_added;
+}
+
+
+// ============================================================================
+// setup file functions
+// ============================================================================
+
+#define TOKEN_STR_PLAYER_PREFIX                        "player_"
+
+
+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"
+  },
+  {
+    TYPE_SWITCH,
+    &setup.sound_loops,                                "repeating_sound_loops"
+  },
+  {
+    TYPE_SWITCH,
+    &setup.sound_music,                                "background_music"
+  },
+  {
+    TYPE_SWITCH,
+    &setup.sound_simple,                       "simple_sound_effects"
+  },
+  {
+    TYPE_SWITCH,
+    &setup.toons,                              "toons"
+  },
+  {
+    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"
+  },
+  {
+    TYPE_STRING,
+    &setup.engine_snapshot_mode,               "engine_snapshot_mode"
+  },
+  {
+    TYPE_INTEGER,
+    &setup.engine_snapshot_memory,             "engine_snapshot_memory"
+  },
+  {
+    TYPE_SWITCH,
+    &setup.fade_screens,                       "fade_screens"
+  },
+  {
+    TYPE_SWITCH,
+    &setup.autorecord,                         "automatic_tape_recording"
+  },
+  {
+    TYPE_SWITCH,
+    &setup.autorecord_after_replay,            "autorecord_after_replay"
+  },
+  {
+    TYPE_SWITCH,
+    &setup.auto_pause_on_start,                        "auto_pause_on_start"
+  },
+  {
+    TYPE_SWITCH,
+    &setup.show_titlescreen,                   "show_titlescreen"
+  },
+  {
+    TYPE_SWITCH,
+    &setup.quick_doors,                                "quick_doors"
+  },
+  {
+    TYPE_SWITCH,
+    &setup.team_mode,                          "team_mode"
+  },
+  {
+    TYPE_SWITCH,
+    &setup.handicap,                           "handicap"
+  },
+  {
+    TYPE_SWITCH,
+    &setup.skip_levels,                                "skip_levels"
+  },
+  {
+    TYPE_SWITCH,
+    &setup.increment_levels,                   "increment_levels"
+  },
+  {
+    TYPE_SWITCH,
+    &setup.auto_play_next_level,               "auto_play_next_level"
+  },
+  {
+    TYPE_SWITCH,
+    &setup.count_score_after_game,             "count_score_after_game"
+  },
+  {
+    TYPE_SWITCH,
+    &setup.show_scores_after_game,             "show_scores_after_game"
+  },
+  {
+    TYPE_SWITCH,
+    &setup.time_limit,                         "time_limit"
+  },
+  {
+    TYPE_SWITCH,
+    &setup.fullscreen,                         "fullscreen"
+  },
+  {
+    TYPE_INTEGER,
+    &setup.window_scaling_percent,             "window_scaling_percent"
+  },
+  {
+    TYPE_STRING,
+    &setup.window_scaling_quality,             "window_scaling_quality"
+  },
+  {
+    TYPE_STRING,
+    &setup.screen_rendering_mode,              "screen_rendering_mode"
+  },
+  {
+    TYPE_STRING,
+    &setup.vsync_mode,                         "vsync_mode"
+  },
+  {
+    TYPE_SWITCH,
+    &setup.ask_on_escape,                      "ask_on_escape"
+  },
+  {
+    TYPE_SWITCH,
+    &setup.ask_on_escape_editor,               "ask_on_escape_editor"
+  },
+  {
+    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"
+  },
+  {
+    TYPE_SWITCH,
+    &setup.input_on_focus,                     "input_on_focus"
+  },
+  {
+    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"
+  },
+  {
+    TYPE_INTEGER,
+    &setup.game_frame_delay,                   "game_frame_delay"
+  },
+  {
+    TYPE_SWITCH,
+    &setup.sp_show_border_elements,            "sp_show_border_elements"
+  },
+  {
+    TYPE_SWITCH,
+    &setup.small_game_graphics,                        "small_game_graphics"
+  },
+  {
+    TYPE_SWITCH,
+    &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,
+    &setup.graphics_set,                       "graphics_set"
+  },
+  {
+    TYPE_STRING,
+    &setup.sounds_set,                         "sounds_set"
+  },
+  {
+    TYPE_STRING,
+    &setup.music_set,                          "music_set"
+  },
+  {
+    TYPE_SWITCH3,
+    &setup.override_level_graphics,            "override_level_graphics"
+  },
+  {
+    TYPE_SWITCH3,
+    &setup.override_level_sounds,              "override_level_sounds"
+  },
+  {
+    TYPE_SWITCH3,
+    &setup.override_level_music,               "override_level_music"
+  },
+  {
+    TYPE_INTEGER,
+    &setup.volume_simple,                      "volume_simple"
+  },
+  {
+    TYPE_INTEGER,
+    &setup.volume_loops,                       "volume_loops"
+  },
+  {
+    TYPE_INTEGER,
+    &setup.volume_music,                       "volume_music"
+  },
+  {
+    TYPE_SWITCH,
+    &setup.network_mode,                       "network_mode"
+  },
+  {
+    TYPE_PLAYER,
+    &setup.network_player_nr,                  "network_player"
+  },
+  {
+    TYPE_STRING,
+    &setup.network_server_hostname,            "network_server_hostname"
+  },
+  {
+    TYPE_STRING,
+    &setup.touch.control_type,                 "touch.control_type"
+  },
+  {
+    TYPE_INTEGER,
+    &setup.touch.move_distance,                        "touch.move_distance"
+  },
+  {
+    TYPE_INTEGER,
+    &setup.touch.drop_distance,                        "touch.drop_distance"
+  },
+  {
+    TYPE_INTEGER,
+    &setup.touch.transparency,                 "touch.transparency"
+  },
+  {
+    TYPE_INTEGER,
+    &setup.touch.draw_outlined,                        "touch.draw_outlined"
+  },
+  {
+    TYPE_INTEGER,
+    &setup.touch.draw_pressed,                 "touch.draw_pressed"
+  },
+  {
+    TYPE_INTEGER,
+    &setup.touch.grid_xsize[0],                        "touch.virtual_buttons.0.xsize"
+  },
+  {
+    TYPE_INTEGER,
+    &setup.touch.grid_ysize[0],                        "touch.virtual_buttons.0.ysize"
+  },
+  {
+    TYPE_INTEGER,
+    &setup.touch.grid_xsize[1],                        "touch.virtual_buttons.1.xsize"
+  },
+  {
+    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[] =
+{
+  {
+    TYPE_INTEGER,
+    &setup.auto_setup.editor_zoom_tilesize,    "editor.zoom_tilesize"
+  },
+};
+
+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[] =
+{
+  {
+    TYPE_SWITCH,
+    &setup.editor.el_classic,                  "editor.el_classic"
+  },
+  {
+    TYPE_SWITCH,
+    &setup.editor.el_custom,                   "editor.el_custom"
+  },
+  {
+    TYPE_SWITCH,
+    &setup.editor.el_user_defined,             "editor.el_user_defined"
+  },
+  {
+    TYPE_SWITCH,
+    &setup.editor.el_dynamic,                  "editor.el_dynamic"
+  },
+  {
+    TYPE_SWITCH,
+    &setup.editor.el_headlines,                        "editor.el_headlines"
+  },
+  {
+    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[] =
+{
+  {
+    TYPE_SWITCH,
+    &setup.editor_cascade.el_bd,               "editor.cascade.el_bd"
+  },
+  {
+    TYPE_SWITCH,
+    &setup.editor_cascade.el_em,               "editor.cascade.el_em"
+  },
+  {
+    TYPE_SWITCH,
+    &setup.editor_cascade.el_emc,              "editor.cascade.el_emc"
+  },
+  {
+    TYPE_SWITCH,
+    &setup.editor_cascade.el_rnd,              "editor.cascade.el_rnd"
+  },
+  {
+    TYPE_SWITCH,
+    &setup.editor_cascade.el_sb,               "editor.cascade.el_sb"
+  },
+  {
+    TYPE_SWITCH,
+    &setup.editor_cascade.el_sp,               "editor.cascade.el_sp"
+  },
+  {
+    TYPE_SWITCH,
+    &setup.editor_cascade.el_dc,               "editor.cascade.el_dc"
+  },
+  {
+    TYPE_SWITCH,
+    &setup.editor_cascade.el_dx,               "editor.cascade.el_dx"
+  },
+  {
+    TYPE_SWITCH,
+    &setup.editor_cascade.el_mm,               "editor.cascade.el_mm"
+  },
+  {
+    TYPE_SWITCH,
+    &setup.editor_cascade.el_df,               "editor.cascade.el_df"
+  },
+  {
+    TYPE_SWITCH,
+    &setup.editor_cascade.el_chars,            "editor.cascade.el_chars"
+  },
+  {
+    TYPE_SWITCH,
+    &setup.editor_cascade.el_steel_chars,      "editor.cascade.el_steel_chars"
+  },
+  {
+    TYPE_SWITCH,
+    &setup.editor_cascade.el_ce,               "editor.cascade.el_ce"
+  },
+  {
+    TYPE_SWITCH,
+    &setup.editor_cascade.el_ge,               "editor.cascade.el_ge"
+  },
+  {
+    TYPE_SWITCH,
+    &setup.editor_cascade.el_es,               "editor.cascade.el_es"
+  },
+  {
+    TYPE_SWITCH,
+    &setup.editor_cascade.el_ref,              "editor.cascade.el_ref"
+  },
+  {
+    TYPE_SWITCH,
+    &setup.editor_cascade.el_user,             "editor.cascade.el_user"
+  },
+  {
+    TYPE_SWITCH,
+    &setup.editor_cascade.el_dynamic,          "editor.cascade.el_dynamic"
+  },
+};
+
+static struct TokenInfo shortcut_setup_tokens[] =
+{
+  {
+    TYPE_KEY_X11,
+    &setup.shortcut.save_game,                 "shortcut.save_game"
+  },
+  {
+    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"
+  },
+  {
+    TYPE_KEY_X11,
+    &setup.shortcut.focus_player[0],           "shortcut.focus_player_1"
+  },
+  {
+    TYPE_KEY_X11,
+    &setup.shortcut.focus_player[1],           "shortcut.focus_player_2"
+  },
+  {
+    TYPE_KEY_X11,
+    &setup.shortcut.focus_player[2],           "shortcut.focus_player_3"
+  },
+  {
+    TYPE_KEY_X11,
+    &setup.shortcut.focus_player[3],           "shortcut.focus_player_4"
+  },
+  {
+    TYPE_KEY_X11,
+    &setup.shortcut.focus_player_all,          "shortcut.focus_player_all"
+  },
+  {
+    TYPE_KEY_X11,
+    &setup.shortcut.tape_eject,                        "shortcut.tape_eject"
+  },
+  {
+    TYPE_KEY_X11,
+    &setup.shortcut.tape_extra,                        "shortcut.tape_extra"
+  },
+  {
+    TYPE_KEY_X11,
+    &setup.shortcut.tape_stop,                 "shortcut.tape_stop"
+  },
+  {
+    TYPE_KEY_X11,
+    &setup.shortcut.tape_pause,                        "shortcut.tape_pause"
+  },
+  {
+    TYPE_KEY_X11,
+    &setup.shortcut.tape_record,               "shortcut.tape_record"
+  },
+  {
+    TYPE_KEY_X11,
+    &setup.shortcut.tape_play,                 "shortcut.tape_play"
+  },
+  {
+    TYPE_KEY_X11,
+    &setup.shortcut.sound_simple,              "shortcut.sound_simple"
+  },
+  {
+    TYPE_KEY_X11,
+    &setup.shortcut.sound_loops,               "shortcut.sound_loops"
+  },
+  {
+    TYPE_KEY_X11,
+    &setup.shortcut.sound_music,               "shortcut.sound_music"
+  },
+  {
+    TYPE_KEY_X11,
+    &setup.shortcut.snap_left,                 "shortcut.snap_left"
+  },
+  {
+    TYPE_KEY_X11,
+    &setup.shortcut.snap_right,                        "shortcut.snap_right"
+  },
+  {
+    TYPE_KEY_X11,
+    &setup.shortcut.snap_up,                   "shortcut.snap_up"
+  },
+  {
+    TYPE_KEY_X11,
+    &setup.shortcut.snap_down,                 "shortcut.snap_down"
+  },
+};
+
+static struct SetupInputInfo setup_input;
+static struct TokenInfo player_setup_tokens[] =
+{
+  {
+    TYPE_BOOLEAN,
+    &setup_input.use_joystick,                 ".use_joystick"
+  },
+  {
+    TYPE_STRING,
+    &setup_input.joy.device_name,              ".joy.device_name"
+  },
+  {
+    TYPE_INTEGER,
+    &setup_input.joy.xleft,                    ".joy.xleft"
+  },
+  {
+    TYPE_INTEGER,
+    &setup_input.joy.xmiddle,                  ".joy.xmiddle"
+  },
+  {
+    TYPE_INTEGER,
+    &setup_input.joy.xright,                   ".joy.xright"
+  },
+  {
+    TYPE_INTEGER,
+    &setup_input.joy.yupper,                   ".joy.yupper"
+  },
+  {
+    TYPE_INTEGER,
+    &setup_input.joy.ymiddle,                  ".joy.ymiddle"
+  },
+  {
+    TYPE_INTEGER,
+    &setup_input.joy.ylower,                   ".joy.ylower"
+  },
+  {
+    TYPE_INTEGER,
+    &setup_input.joy.snap,                     ".joy.snap_field"
+  },
+  {
+    TYPE_INTEGER,
+    &setup_input.joy.drop,                     ".joy.place_bomb"
+  },
+  {
+    TYPE_KEY_X11,
+    &setup_input.key.left,                     ".key.move_left"
+  },
+  {
+    TYPE_KEY_X11,
+    &setup_input.key.right,                    ".key.move_right"
+  },
+  {
+    TYPE_KEY_X11,
+    &setup_input.key.up,                       ".key.move_up"
+  },
+  {
+    TYPE_KEY_X11,
+    &setup_input.key.down,                     ".key.move_down"
+  },
+  {
+    TYPE_KEY_X11,
+    &setup_input.key.snap,                     ".key.snap_field"
+  },
+  {
+    TYPE_KEY_X11,
+    &setup_input.key.drop,                     ".key.place_bomb"
+  },
+};
+
+static struct TokenInfo system_setup_tokens[] =
+{
+  {
+    TYPE_STRING,
+    &setup.system.sdl_renderdriver,            "system.sdl_renderdriver"
+  },
+  {
+    TYPE_STRING,
+    &setup.system.sdl_videodriver,             "system.sdl_videodriver"
+  },
+  {
+    TYPE_STRING,
+    &setup.system.sdl_audiodriver,             "system.sdl_audiodriver"
+  },
+  {
+    TYPE_INTEGER,
+    &setup.system.audio_fragment_size,         "system.audio_fragment_size"
+  },
+};
+
+static struct TokenInfo internal_setup_tokens[] =
+{
+  {
+    TYPE_STRING,
+    &setup.internal.program_title,             "program_title"
+  },
+  {
+    TYPE_STRING,
+    &setup.internal.program_version,           "program_version"
+  },
+  {
+    TYPE_STRING,
+    &setup.internal.program_author,            "program_author"
+  },
+  {
+    TYPE_STRING,
+    &setup.internal.program_email,             "program_email"
+  },
+  {
+    TYPE_STRING,
+    &setup.internal.program_website,           "program_website"
+  },
+  {
+    TYPE_STRING,
+    &setup.internal.program_copyright,         "program_copyright"
+  },
+  {
+    TYPE_STRING,
+    &setup.internal.program_company,           "program_company"
+  },
+  {
+    TYPE_STRING,
+    &setup.internal.program_icon_file,         "program_icon_file"
+  },
+  {
+    TYPE_STRING,
+    &setup.internal.default_graphics_set,      "default_graphics_set"
+  },
+  {
+    TYPE_STRING,
+    &setup.internal.default_sounds_set,                "default_sounds_set"
+  },
+  {
+    TYPE_STRING,
+    &setup.internal.default_music_set,         "default_music_set"
+  },
+  {
+    TYPE_STRING,
+    &setup.internal.fallback_graphics_file,    "fallback_graphics_file"
+  },
+  {
+    TYPE_STRING,
+    &setup.internal.fallback_sounds_file,      "fallback_sounds_file"
+  },
+  {
+    TYPE_STRING,
+    &setup.internal.fallback_music_file,       "fallback_music_file"
+  },
+  {
+    TYPE_STRING,
+    &setup.internal.default_level_series,      "default_level_series"
+  },
+  {
+    TYPE_INTEGER,
+    &setup.internal.default_window_width,      "default_window_width"
+  },
+  {
+    TYPE_INTEGER,
+    &setup.internal.default_window_height,     "default_window_height"
+  },
+  {
+    TYPE_BOOLEAN,
+    &setup.internal.choose_from_top_leveldir,  "choose_from_top_leveldir"
+  },
+  {
+    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.info_screens_from_main,    "info_screens_from_main"
+  },
+  {
+    TYPE_BOOLEAN,
+    &setup.internal.menu_game,                 "menu_game"
+  },
+  {
+    TYPE_BOOLEAN,
+    &setup.internal.menu_engines,              "menu_engines"
+  },
+  {
+    TYPE_BOOLEAN,
+    &setup.internal.menu_editor,               "menu_editor"
+  },
+  {
+    TYPE_BOOLEAN,
+    &setup.internal.menu_graphics,             "menu_graphics"
+  },
+  {
+    TYPE_BOOLEAN,
+    &setup.internal.menu_sound,                        "menu_sound"
+  },
+  {
+    TYPE_BOOLEAN,
+    &setup.internal.menu_artwork,              "menu_artwork"
+  },
+  {
+    TYPE_BOOLEAN,
+    &setup.internal.menu_input,                        "menu_input"
+  },
+  {
+    TYPE_BOOLEAN,
+    &setup.internal.menu_touch,                        "menu_touch"
+  },
+  {
+    TYPE_BOOLEAN,
+    &setup.internal.menu_shortcuts,            "menu_shortcuts"
+  },
+  {
+    TYPE_BOOLEAN,
+    &setup.internal.menu_exit,                 "menu_exit"
+  },
+  {
+    TYPE_BOOLEAN,
+    &setup.internal.menu_save_and_exit,                "menu_save_and_exit"
+  },
+  {
+    TYPE_BOOLEAN,
+    &setup.internal.menu_shortcuts_various,    "menu_shortcuts_various"
+  },
+  {
+    TYPE_BOOLEAN,
+    &setup.internal.menu_shortcuts_focus,      "menu_shortcuts_focus"
+  },
+  {
+    TYPE_BOOLEAN,
+    &setup.internal.menu_shortcuts_tape,       "menu_shortcuts_tape"
+  },
+  {
+    TYPE_BOOLEAN,
+    &setup.internal.menu_shortcuts_sound,      "menu_shortcuts_sound"
+  },
+  {
+    TYPE_BOOLEAN,
+    &setup.internal.menu_shortcuts_snap,       "menu_shortcuts_snap"
+  },
+  {
+    TYPE_BOOLEAN,
+    &setup.internal.info_title,                        "info_title"
+  },
+  {
+    TYPE_BOOLEAN,
+    &setup.internal.info_elements,             "info_elements"
+  },
+  {
+    TYPE_BOOLEAN,
+    &setup.internal.info_music,                        "info_music"
+  },
+  {
+    TYPE_BOOLEAN,
+    &setup.internal.info_credits,              "info_credits"
+  },
+  {
+    TYPE_BOOLEAN,
+    &setup.internal.info_program,              "info_program"
+  },
+  {
+    TYPE_BOOLEAN,
+    &setup.internal.info_version,              "info_version"
+  },
+  {
+    TYPE_BOOLEAN,
+    &setup.internal.info_levelset,             "info_levelset"
+  },
+  {
+    TYPE_BOOLEAN,
+    &setup.internal.info_exit,                 "info_exit"
+  },
+};
+
+static struct TokenInfo debug_setup_tokens[] =
+{
+  {
+    TYPE_INTEGER,
+    &setup.debug.frame_delay[0],               "debug.frame_delay_0"
+  },
+  {
+    TYPE_INTEGER,
+    &setup.debug.frame_delay[1],               "debug.frame_delay_1"
+  },
+  {
+    TYPE_INTEGER,
+    &setup.debug.frame_delay[2],               "debug.frame_delay_2"
+  },
+  {
+    TYPE_INTEGER,
+    &setup.debug.frame_delay[3],               "debug.frame_delay_3"
+  },
+  {
+    TYPE_INTEGER,
+    &setup.debug.frame_delay[4],               "debug.frame_delay_4"
+  },
+  {
+    TYPE_INTEGER,
+    &setup.debug.frame_delay[5],               "debug.frame_delay_5"
+  },
+  {
+    TYPE_INTEGER,
+    &setup.debug.frame_delay[6],               "debug.frame_delay_6"
+  },
+  {
+    TYPE_INTEGER,
+    &setup.debug.frame_delay[7],               "debug.frame_delay_7"
+  },
+  {
+    TYPE_INTEGER,
+    &setup.debug.frame_delay[8],               "debug.frame_delay_8"
+  },
+  {
+    TYPE_INTEGER,
+    &setup.debug.frame_delay[9],               "debug.frame_delay_9"
+  },
+  {
+    TYPE_KEY_X11,
+    &setup.debug.frame_delay_key[0],           "debug.key.frame_delay_0"
+  },
+  {
+    TYPE_KEY_X11,
+    &setup.debug.frame_delay_key[1],           "debug.key.frame_delay_1"
+  },
+  {
+    TYPE_KEY_X11,
+    &setup.debug.frame_delay_key[2],           "debug.key.frame_delay_2"
+  },
+  {
+    TYPE_KEY_X11,
+    &setup.debug.frame_delay_key[3],           "debug.key.frame_delay_3"
+  },
+  {
+    TYPE_KEY_X11,
+    &setup.debug.frame_delay_key[4],           "debug.key.frame_delay_4"
+  },
+  {
+    TYPE_KEY_X11,
+    &setup.debug.frame_delay_key[5],           "debug.key.frame_delay_5"
+  },
+  {
+    TYPE_KEY_X11,
+    &setup.debug.frame_delay_key[6],           "debug.key.frame_delay_6"
+  },
+  {
+    TYPE_KEY_X11,
+    &setup.debug.frame_delay_key[7],           "debug.key.frame_delay_7"
+  },
+  {
+    TYPE_KEY_X11,
+    &setup.debug.frame_delay_key[8],           "debug.key.frame_delay_8"
+  },
+  {
+    TYPE_KEY_X11,
+    &setup.debug.frame_delay_key[9],           "debug.key.frame_delay_9"
+  },
+  {
+    TYPE_BOOLEAN,
+    &setup.debug.frame_delay_use_mod_key,      "debug.frame_delay.use_mod_key"},
+  {
+    TYPE_BOOLEAN,
+    &setup.debug.frame_delay_game_only,                "debug.frame_delay.game_only"
+  },
+  {
+    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[] =
+{
+  {
+    TYPE_BOOLEAN,
+    &setup.options.verbose,                    "options.verbose"
+  },
+  {
+    TYPE_BOOLEAN,
+    &setup.options.debug,                      "options.debug"
+  },
+  {
+    TYPE_STRING,
+    &setup.options.debug_mode,                 "options.debug_mode"
+  },
+};
+
+static void setSetupInfoToDefaults(struct SetupInfo *si)
+{
+  int i;
+
+  si->player_name = getStringCopy(getDefaultUserName(user.nr));
+
+  si->multiple_users = TRUE;
+
+  si->sound = TRUE;
   si->sound_loops = TRUE;
   si->sound_music = TRUE;
   si->sound_simple = TRUE;
   si->toons = TRUE;
   si->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->autorecord_after_replay = TRUE;
+  si->auto_pause_on_start = FALSE;
   si->show_titlescreen = TRUE;
   si->quick_doors = FALSE;
   si->team_mode = FALSE;
@@ -8825,7 +10425,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;
@@ -8835,854 +10436,1519 @@ 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);
+  si->music_set    = getStringCopy(MUS_CLASSIC_SUBDIR);
+
+  si->override_level_graphics = FALSE;
+  si->override_level_sounds = FALSE;
+  si->override_level_music = FALSE;
+
+  si->volume_simple = 100;             // percent
+  si->volume_loops = 100;              // percent
+  si->volume_music = 100;              // percent
+
+  si->network_mode = FALSE;
+  si->network_player_nr = 0;           // first player
+  si->network_server_hostname = getStringCopy(STR_NETWORK_AUTO_DETECT);
+
+  si->touch.control_type = getStringCopy(TOUCH_CONTROL_DEFAULT);
+  si->touch.move_distance = TOUCH_MOVE_DISTANCE_DEFAULT;       // percent
+  si->touch.drop_distance = TOUCH_DROP_DISTANCE_DEFAULT;       // percent
+  si->touch.transparency = TOUCH_TRANSPARENCY_DEFAULT;         // percent
+  si->touch.draw_outlined = TRUE;
+  si->touch.draw_pressed = TRUE;
+
+  for (i = 0; i < 2; i++)
+  {
+    char *default_grid_button[6][2] =
+    {
+      { "      ", "  ^^  " },
+      { "      ", "  ^^  " },
+      { "      ", "<<  >>" },
+      { "      ", "<<  >>" },
+      { "111222", "  vv  " },
+      { "111222", "  vv  " }
+    };
+    int grid_xsize = DEFAULT_GRID_XSIZE(i);
+    int grid_ysize = DEFAULT_GRID_YSIZE(i);
+    int min_xsize = MIN(6, grid_xsize);
+    int min_ysize = MIN(6, grid_ysize);
+    int startx = grid_xsize - min_xsize;
+    int starty = grid_ysize - min_ysize;
+    int x, y;
+
+    // virtual buttons grid can only be set to defaults if video is initialized
+    // (this will be repeated if virtual buttons are not loaded from setup file)
+    if (video.initialized)
+    {
+      si->touch.grid_xsize[i] = grid_xsize;
+      si->touch.grid_ysize[i] = grid_ysize;
+    }
+    else
+    {
+      si->touch.grid_xsize[i] = -1;
+      si->touch.grid_ysize[i] = -1;
+    }
+
+    for (x = 0; x < MAX_GRID_XSIZE; x++)
+      for (y = 0; y < MAX_GRID_YSIZE; y++)
+       si->touch.grid_button[i][x][y] = CHAR_GRID_BUTTON_NONE;
+
+    for (x = 0; x < min_xsize; x++)
+      for (y = 0; y < min_ysize; y++)
+       si->touch.grid_button[i][x][starty + y] =
+         default_grid_button[y][0][x];
+
+    for (x = 0; x < min_xsize; x++)
+      for (y = 0; y < min_ysize; y++)
+       si->touch.grid_button[i][startx + x][starty + y] =
+         default_grid_button[y][1][x];
+  }
+
+  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;
+  si->editor.el_more                   = TRUE;
+  si->editor.el_sokoban                        = TRUE;
+  si->editor.el_supaplex               = TRUE;
+  si->editor.el_diamond_caves          = TRUE;
+  si->editor.el_dx_boulderdash         = TRUE;
+
+  si->editor.el_mirror_magic           = TRUE;
+  si->editor.el_deflektor              = TRUE;
+
+  si->editor.el_chars                  = TRUE;
+  si->editor.el_steel_chars            = TRUE;
+
+  si->editor.el_classic                        = TRUE;
+  si->editor.el_custom                 = TRUE;
+
+  si->editor.el_user_defined           = FALSE;
+  si->editor.el_dynamic                        = TRUE;
+
+  si->editor.el_headlines              = TRUE;
+
+  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;
+  si->shortcut.focus_player[1] = DEFAULT_KEY_FOCUS_PLAYER_2;
+  si->shortcut.focus_player[2] = DEFAULT_KEY_FOCUS_PLAYER_3;
+  si->shortcut.focus_player[3] = DEFAULT_KEY_FOCUS_PLAYER_4;
+  si->shortcut.focus_player_all        = DEFAULT_KEY_FOCUS_PLAYER_ALL;
+
+  si->shortcut.tape_eject      = DEFAULT_KEY_TAPE_EJECT;
+  si->shortcut.tape_extra      = DEFAULT_KEY_TAPE_EXTRA;
+  si->shortcut.tape_stop       = DEFAULT_KEY_TAPE_STOP;
+  si->shortcut.tape_pause      = DEFAULT_KEY_TAPE_PAUSE;
+  si->shortcut.tape_record     = DEFAULT_KEY_TAPE_RECORD;
+  si->shortcut.tape_play       = DEFAULT_KEY_TAPE_PLAY;
+
+  si->shortcut.sound_simple    = DEFAULT_KEY_SOUND_SIMPLE;
+  si->shortcut.sound_loops     = DEFAULT_KEY_SOUND_LOOPS;
+  si->shortcut.sound_music     = DEFAULT_KEY_SOUND_MUSIC;
+
+  si->shortcut.snap_left       = DEFAULT_KEY_SNAP_LEFT;
+  si->shortcut.snap_right      = DEFAULT_KEY_SNAP_RIGHT;
+  si->shortcut.snap_up         = DEFAULT_KEY_SNAP_UP;
+  si->shortcut.snap_down       = DEFAULT_KEY_SNAP_DOWN;
+
+  for (i = 0; i < MAX_PLAYERS; i++)
+  {
+    si->input[i].use_joystick = FALSE;
+    si->input[i].joy.device_name=getStringCopy(getDeviceNameFromJoystickNr(i));
+    si->input[i].joy.xleft   = JOYSTICK_XLEFT;
+    si->input[i].joy.xmiddle = JOYSTICK_XMIDDLE;
+    si->input[i].joy.xright  = JOYSTICK_XRIGHT;
+    si->input[i].joy.yupper  = JOYSTICK_YUPPER;
+    si->input[i].joy.ymiddle = JOYSTICK_YMIDDLE;
+    si->input[i].joy.ylower  = JOYSTICK_YLOWER;
+    si->input[i].joy.snap  = (i == 0 ? JOY_BUTTON_1 : 0);
+    si->input[i].joy.drop  = (i == 0 ? JOY_BUTTON_2 : 0);
+    si->input[i].key.left  = (i == 0 ? DEFAULT_KEY_LEFT  : KSYM_UNDEFINED);
+    si->input[i].key.right = (i == 0 ? DEFAULT_KEY_RIGHT : KSYM_UNDEFINED);
+    si->input[i].key.up    = (i == 0 ? DEFAULT_KEY_UP    : KSYM_UNDEFINED);
+    si->input[i].key.down  = (i == 0 ? DEFAULT_KEY_DOWN  : KSYM_UNDEFINED);
+    si->input[i].key.snap  = (i == 0 ? DEFAULT_KEY_SNAP  : KSYM_UNDEFINED);
+    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;
+
+  si->internal.program_title     = getStringCopy(PROGRAM_TITLE_STRING);
+  si->internal.program_version   = getStringCopy(getProgramRealVersionString());
+  si->internal.program_author    = getStringCopy(PROGRAM_AUTHOR_STRING);
+  si->internal.program_email     = getStringCopy(PROGRAM_EMAIL_STRING);
+  si->internal.program_website   = getStringCopy(PROGRAM_WEBSITE_STRING);
+  si->internal.program_copyright = getStringCopy(PROGRAM_COPYRIGHT_STRING);
+  si->internal.program_company   = getStringCopy(PROGRAM_COMPANY_STRING);
+
+  si->internal.program_icon_file = getStringCopy(PROGRAM_ICON_FILENAME);
+
+  si->internal.default_graphics_set = getStringCopy(GFX_CLASSIC_SUBDIR);
+  si->internal.default_sounds_set   = getStringCopy(SND_CLASSIC_SUBDIR);
+  si->internal.default_music_set    = getStringCopy(MUS_CLASSIC_SUBDIR);
+
+  si->internal.fallback_graphics_file = getStringCopy(UNDEFINED_FILENAME);
+  si->internal.fallback_sounds_file   = getStringCopy(UNDEFINED_FILENAME);
+  si->internal.fallback_music_file    = getStringCopy(UNDEFINED_FILENAME);
+
+  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.info_screens_from_main = FALSE;
+
+  si->internal.default_window_width  = WIN_XSIZE_DEFAULT;
+  si->internal.default_window_height = WIN_YSIZE_DEFAULT;
+
+  si->debug.frame_delay[0] = DEFAULT_FRAME_DELAY_0;
+  si->debug.frame_delay[1] = DEFAULT_FRAME_DELAY_1;
+  si->debug.frame_delay[2] = DEFAULT_FRAME_DELAY_2;
+  si->debug.frame_delay[3] = DEFAULT_FRAME_DELAY_3;
+  si->debug.frame_delay[4] = DEFAULT_FRAME_DELAY_4;
+  si->debug.frame_delay[5] = DEFAULT_FRAME_DELAY_5;
+  si->debug.frame_delay[6] = DEFAULT_FRAME_DELAY_6;
+  si->debug.frame_delay[7] = DEFAULT_FRAME_DELAY_7;
+  si->debug.frame_delay[8] = DEFAULT_FRAME_DELAY_8;
+  si->debug.frame_delay[9] = DEFAULT_FRAME_DELAY_9;
+
+  si->debug.frame_delay_key[0] = DEFAULT_KEY_FRAME_DELAY_0;
+  si->debug.frame_delay_key[1] = DEFAULT_KEY_FRAME_DELAY_1;
+  si->debug.frame_delay_key[2] = DEFAULT_KEY_FRAME_DELAY_2;
+  si->debug.frame_delay_key[3] = DEFAULT_KEY_FRAME_DELAY_3;
+  si->debug.frame_delay_key[4] = DEFAULT_KEY_FRAME_DELAY_4;
+  si->debug.frame_delay_key[5] = DEFAULT_KEY_FRAME_DELAY_5;
+  si->debug.frame_delay_key[6] = DEFAULT_KEY_FRAME_DELAY_6;
+  si->debug.frame_delay_key[7] = DEFAULT_KEY_FRAME_DELAY_7;
+  si->debug.frame_delay_key[8] = DEFAULT_KEY_FRAME_DELAY_8;
+  si->debug.frame_delay_key[9] = DEFAULT_KEY_FRAME_DELAY_9;
+
+  si->debug.frame_delay_use_mod_key = DEFAULT_FRAME_DELAY_USE_MOD_KEY;
+  si->debug.frame_delay_game_only   = DEFAULT_FRAME_DELAY_GAME_ONLY;
+
+  si->debug.show_frames_per_second = FALSE;
+
+  si->debug.xsn_mode = AUTO;
+  si->debug.xsn_percent = 0;
+
+  si->options.verbose = FALSE;
+  si->options.debug = FALSE;
+  si->options.debug_mode = getStringCopy(ARG_UNDEFINED_STRING);
+
+#if defined(PLATFORM_ANDROID)
+  si->fullscreen = TRUE;
+  si->touch.overlay_buttons = TRUE;
+#endif
+
+  setHideSetupEntry(&setup.debug.xsn_mode);
+}
+
+static void setSetupInfoToDefaults_AutoSetup(struct SetupInfo *si)
+{
+  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;
+  si->editor_cascade.el_em             = TRUE;
+  si->editor_cascade.el_emc            = TRUE;
+  si->editor_cascade.el_rnd            = TRUE;
+  si->editor_cascade.el_sb             = TRUE;
+  si->editor_cascade.el_sp             = TRUE;
+  si->editor_cascade.el_dc             = TRUE;
+  si->editor_cascade.el_dx             = TRUE;
+
+  si->editor_cascade.el_mm             = TRUE;
+  si->editor_cascade.el_df             = TRUE;
+
+  si->editor_cascade.el_chars          = FALSE;
+  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;
+}
+
+#define MAX_HIDE_SETUP_TOKEN_SIZE              20
+
+static char *getHideSetupToken(void *setup_value)
+{
+  static char hide_setup_token[MAX_HIDE_SETUP_TOKEN_SIZE];
+
+  if (setup_value != NULL)
+    snprintf(hide_setup_token, MAX_HIDE_SETUP_TOKEN_SIZE, "%p", setup_value);
+
+  return hide_setup_token;
+}
+
+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);
+
+  return (setup_value != NULL &&
+         getHashEntry(hide_setup_hash, hide_setup_token) != NULL);
+}
+
+static void setSetupInfoFromTokenText(SetupFileHash *setup_file_hash,
+                                     struct TokenInfo *token_info,
+                                     int token_nr, char *token_text)
+{
+  char *token_hide_text = getStringCat2(token_text, ".hide");
+  char *token_hide_value = getHashEntry(setup_file_hash, token_hide_text);
+
+  // set the value of this setup option in the setup option structure
+  setSetupInfo(token_info, token_nr, getHashEntry(setup_file_hash, token_text));
+
+  // 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,
+                                     struct TokenInfo *token_info,
+                                     int token_nr)
+{
+  setSetupInfoFromTokenText(setup_file_hash, token_info, token_nr,
+                           token_info[token_nr].text);
+}
+
+static void decodeSetupFileHash_Default(SetupFileHash *setup_file_hash)
+{
+  int i, pnr;
+
+  if (!setup_file_hash)
+    return;
+
+  for (i = 0; i < ARRAY_SIZE(global_setup_tokens); i++)
+    setSetupInfoFromTokenInfo(setup_file_hash, global_setup_tokens, i);
+
+  setup.touch.grid_initialized = TRUE;
+  for (i = 0; i < 2; i++)
+  {
+    int grid_xsize = setup.touch.grid_xsize[i];
+    int grid_ysize = setup.touch.grid_ysize[i];
+    int x, y;
+
+    // if virtual buttons are not loaded from setup file, repeat initializing
+    // virtual buttons grid with default values later when video is initialized
+    if (grid_xsize == -1 ||
+       grid_ysize == -1)
+    {
+      setup.touch.grid_initialized = FALSE;
+
+      continue;
+    }
+
+    for (y = 0; y < grid_ysize; y++)
+    {
+      char token_string[MAX_LINE_LEN];
+
+      sprintf(token_string, "touch.virtual_buttons.%d.%02d", i, y);
+
+      char *value_string = getHashEntry(setup_file_hash, token_string);
+
+      if (value_string == NULL)
+       continue;
+
+      for (x = 0; x < grid_xsize; x++)
+      {
+       char c = value_string[x];
+
+       setup.touch.grid_button[i][x][y] =
+         (c == '.' ? CHAR_GRID_BUTTON_NONE : c);
+      }
+    }
+  }
+
+  for (i = 0; i < ARRAY_SIZE(editor_setup_tokens); i++)
+    setSetupInfoFromTokenInfo(setup_file_hash, editor_setup_tokens, i);
+
+  for (i = 0; i < ARRAY_SIZE(shortcut_setup_tokens); i++)
+    setSetupInfoFromTokenInfo(setup_file_hash, shortcut_setup_tokens, i);
+
+  for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
+  {
+    char prefix[30];
+
+    sprintf(prefix, "%s%d", TOKEN_STR_PLAYER_PREFIX, pnr + 1);
+
+    setup_input = setup.input[pnr];
+    for (i = 0; i < ARRAY_SIZE(player_setup_tokens); i++)
+    {
+      char full_token[100];
+
+      sprintf(full_token, "%s%s", prefix, player_setup_tokens[i].text);
+      setSetupInfoFromTokenText(setup_file_hash, player_setup_tokens, i,
+                               full_token);
+    }
+    setup.input[pnr] = setup_input;
+  }
+
+  for (i = 0; i < ARRAY_SIZE(system_setup_tokens); i++)
+    setSetupInfoFromTokenInfo(setup_file_hash, system_setup_tokens, i);
+
+  for (i = 0; i < ARRAY_SIZE(internal_setup_tokens); i++)
+    setSetupInfoFromTokenInfo(setup_file_hash, internal_setup_tokens, i);
+
+  for (i = 0; i < ARRAY_SIZE(debug_setup_tokens); i++)
+    setSetupInfoFromTokenInfo(setup_file_hash, debug_setup_tokens, i);
+
+  for (i = 0; i < ARRAY_SIZE(options_setup_tokens); i++)
+    setSetupInfoFromTokenInfo(setup_file_hash, options_setup_tokens, i);
+
+  setHideRelatedSetupEntries();
+}
+
+static void decodeSetupFileHash_AutoSetup(SetupFileHash *setup_file_hash)
+{
+  int i;
 
-  si->graphics_set = getStringCopy(GFX_CLASSIC_SUBDIR);
-  si->sounds_set   = getStringCopy(SND_CLASSIC_SUBDIR);
-  si->music_set    = getStringCopy(MUS_CLASSIC_SUBDIR);
+  if (!setup_file_hash)
+    return;
 
-  si->override_level_graphics = FALSE;
-  si->override_level_sounds = FALSE;
-  si->override_level_music = FALSE;
+  for (i = 0; i < ARRAY_SIZE(auto_setup_tokens); i++)
+    setSetupInfo(auto_setup_tokens, i,
+                getHashEntry(setup_file_hash,
+                             auto_setup_tokens[i].text));
+}
 
-  si->volume_simple = 100;             // percent
-  si->volume_loops = 100;              // percent
-  si->volume_music = 100;              // percent
+static void decodeSetupFileHash_ServerSetup(SetupFileHash *setup_file_hash)
+{
+  int i;
 
-  si->network_mode = FALSE;
-  si->network_player_nr = 0;           // first player
-  si->network_server_hostname = getStringCopy(STR_NETWORK_AUTO_DETECT);
+  if (!setup_file_hash)
+    return;
 
-  si->touch.control_type = getStringCopy(TOUCH_CONTROL_DEFAULT);
-  si->touch.move_distance = TOUCH_MOVE_DISTANCE_DEFAULT;       // percent
-  si->touch.drop_distance = TOUCH_DROP_DISTANCE_DEFAULT;       // percent
-  si->touch.transparency = TOUCH_TRANSPARENCY_DEFAULT;         // percent
-  si->touch.draw_outlined = TRUE;
-  si->touch.draw_pressed = TRUE;
+  for (i = 0; i < ARRAY_SIZE(server_setup_tokens); i++)
+    setSetupInfo(server_setup_tokens, i,
+                getHashEntry(setup_file_hash,
+                             server_setup_tokens[i].text));
+}
 
-  for (i = 0; i < 2; i++)
+static void decodeSetupFileHash_EditorCascade(SetupFileHash *setup_file_hash)
+{
+  int i;
+
+  if (!setup_file_hash)
+    return;
+
+  for (i = 0; i < ARRAY_SIZE(editor_cascade_setup_tokens); i++)
+    setSetupInfo(editor_cascade_setup_tokens, i,
+                getHashEntry(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)
   {
-    char *default_grid_button[6][2] =
-    {
-      { "      ", "  ^^  " },
-      { "      ", "  ^^  " },
-      { "      ", "<<  >>" },
-      { "      ", "<<  >>" },
-      { "111222", "  vv  " },
-      { "111222", "  vv  " }
-    };
-    int grid_xsize = DEFAULT_GRID_XSIZE(i);
-    int grid_ysize = DEFAULT_GRID_YSIZE(i);
-    int min_xsize = MIN(6, grid_xsize);
-    int min_ysize = MIN(6, grid_ysize);
-    int startx = grid_xsize - min_xsize;
-    int starty = grid_ysize - min_ysize;
-    int x, y;
+    for (i = 0; i < MAX_PLAYER_NAMES; i++)
+      checked_free(global.user_names[i]);
 
-    // virtual buttons grid can only be set to defaults if video is initialized
-    // (this will be repeated if virtual buttons are not loaded from setup file)
-    if (video.initialized)
-    {
-      si->touch.grid_xsize[i] = grid_xsize;
-      si->touch.grid_ysize[i] = grid_ysize;
-    }
-    else
+    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)
     {
-      si->touch.grid_xsize[i] = -1;
-      si->touch.grid_ysize[i] = -1;
-    }
+      char *player_name = getHashEntry(setup_file_hash, "player_name");
 
-    for (x = 0; x < MAX_GRID_XSIZE; x++)
-      for (y = 0; y < MAX_GRID_YSIZE; y++)
-       si->touch.grid_button[i][x][y] = CHAR_GRID_BUTTON_NONE;
+      global.user_names[i] = getFixedUserName(player_name);
 
-    for (x = 0; x < min_xsize; x++)
-      for (y = 0; y < min_ysize; y++)
-       si->touch.grid_button[i][x][starty + y] =
-         default_grid_button[y][0][x];
+      freeSetupFileHash(setup_file_hash);
+    }
 
-    for (x = 0; x < min_xsize; x++)
-      for (y = 0; y < min_ysize; y++)
-       si->touch.grid_button[i][startx + x][starty + y] =
-         default_grid_button[y][1][x];
+    if (global.user_names[i] == NULL)
+      global.user_names[i] = getStringCopy(getDefaultUserName(i));
   }
 
-  si->touch.grid_initialized           = video.initialized;
+  user.nr = last_user_nr;
+}
 
-  si->editor.el_boulderdash            = TRUE;
-  si->editor.el_emerald_mine           = TRUE;
-  si->editor.el_emerald_mine_club      = TRUE;
-  si->editor.el_more                   = TRUE;
-  si->editor.el_sokoban                        = TRUE;
-  si->editor.el_supaplex               = TRUE;
-  si->editor.el_diamond_caves          = TRUE;
-  si->editor.el_dx_boulderdash         = TRUE;
+void LoadSetupFromFilename(char *filename)
+{
+  SetupFileHash *setup_file_hash = loadSetupFileHash(filename);
 
-  si->editor.el_mirror_magic           = TRUE;
-  si->editor.el_deflektor              = TRUE;
+  if (setup_file_hash)
+  {
+    decodeSetupFileHash_Default(setup_file_hash);
 
-  si->editor.el_chars                  = TRUE;
-  si->editor.el_steel_chars            = TRUE;
+    freeSetupFileHash(setup_file_hash);
+  }
+  else
+  {
+    Debug("setup", "using default setup values");
+  }
+}
 
-  si->editor.el_classic                        = TRUE;
-  si->editor.el_custom                 = TRUE;
+static void LoadSetup_SpecialPostProcessing(void)
+{
+  char *player_name_new;
 
-  si->editor.el_user_defined           = FALSE;
-  si->editor.el_dynamic                        = TRUE;
+  // needed to work around problems with fixed length strings
+  player_name_new = getFixedUserName(setup.player_name);
+  free(setup.player_name);
+  setup.player_name = player_name_new;
 
-  si->editor.el_headlines              = TRUE;
+  // "scroll_delay: on(3) / off(0)" was replaced by scroll delay value
+  if (setup.scroll_delay == FALSE)
+  {
+    setup.scroll_delay_value = MIN_SCROLL_DELAY;
+    setup.scroll_delay = TRUE;                 // now always "on"
+  }
 
-  si->editor.show_element_token                = FALSE;
+  // make sure that scroll delay value stays inside valid range
+  setup.scroll_delay_value =
+    MIN(MAX(MIN_SCROLL_DELAY, setup.scroll_delay_value), MAX_SCROLL_DELAY);
+}
 
-  si->editor.use_template_for_new_levels = TRUE;
+void LoadSetup_Default(void)
+{
+  char *filename;
 
-  si->shortcut.save_game       = DEFAULT_KEY_SAVE_GAME;
-  si->shortcut.load_game       = DEFAULT_KEY_LOAD_GAME;
-  si->shortcut.toggle_pause    = DEFAULT_KEY_TOGGLE_PAUSE;
+  // always start with reliable default values
+  setSetupInfoToDefaults(&setup);
 
-  si->shortcut.focus_player[0] = DEFAULT_KEY_FOCUS_PLAYER_1;
-  si->shortcut.focus_player[1] = DEFAULT_KEY_FOCUS_PLAYER_2;
-  si->shortcut.focus_player[2] = DEFAULT_KEY_FOCUS_PLAYER_3;
-  si->shortcut.focus_player[3] = DEFAULT_KEY_FOCUS_PLAYER_4;
-  si->shortcut.focus_player_all        = DEFAULT_KEY_FOCUS_PLAYER_ALL;
+  // try to load setup values from default setup file
+  filename = getDefaultSetupFilename();
 
-  si->shortcut.tape_eject      = DEFAULT_KEY_TAPE_EJECT;
-  si->shortcut.tape_extra      = DEFAULT_KEY_TAPE_EXTRA;
-  si->shortcut.tape_stop       = DEFAULT_KEY_TAPE_STOP;
-  si->shortcut.tape_pause      = DEFAULT_KEY_TAPE_PAUSE;
-  si->shortcut.tape_record     = DEFAULT_KEY_TAPE_RECORD;
-  si->shortcut.tape_play       = DEFAULT_KEY_TAPE_PLAY;
+  if (fileExists(filename))
+    LoadSetupFromFilename(filename);
 
-  si->shortcut.sound_simple    = DEFAULT_KEY_SOUND_SIMPLE;
-  si->shortcut.sound_loops     = DEFAULT_KEY_SOUND_LOOPS;
-  si->shortcut.sound_music     = DEFAULT_KEY_SOUND_MUSIC;
+  // try to load setup values from platform setup file
+  filename = getPlatformSetupFilename();
 
-  si->shortcut.snap_left       = DEFAULT_KEY_SNAP_LEFT;
-  si->shortcut.snap_right      = DEFAULT_KEY_SNAP_RIGHT;
-  si->shortcut.snap_up         = DEFAULT_KEY_SNAP_UP;
-  si->shortcut.snap_down       = DEFAULT_KEY_SNAP_DOWN;
+  if (fileExists(filename))
+    LoadSetupFromFilename(filename);
 
-  for (i = 0; i < MAX_PLAYERS; i++)
-  {
-    si->input[i].use_joystick = FALSE;
-    si->input[i].joy.device_name=getStringCopy(getDeviceNameFromJoystickNr(i));
-    si->input[i].joy.xleft   = JOYSTICK_XLEFT;
-    si->input[i].joy.xmiddle = JOYSTICK_XMIDDLE;
-    si->input[i].joy.xright  = JOYSTICK_XRIGHT;
-    si->input[i].joy.yupper  = JOYSTICK_YUPPER;
-    si->input[i].joy.ymiddle = JOYSTICK_YMIDDLE;
-    si->input[i].joy.ylower  = JOYSTICK_YLOWER;
-    si->input[i].joy.snap  = (i == 0 ? JOY_BUTTON_1 : 0);
-    si->input[i].joy.drop  = (i == 0 ? JOY_BUTTON_2 : 0);
-    si->input[i].key.left  = (i == 0 ? DEFAULT_KEY_LEFT  : KSYM_UNDEFINED);
-    si->input[i].key.right = (i == 0 ? DEFAULT_KEY_RIGHT : KSYM_UNDEFINED);
-    si->input[i].key.up    = (i == 0 ? DEFAULT_KEY_UP    : KSYM_UNDEFINED);
-    si->input[i].key.down  = (i == 0 ? DEFAULT_KEY_DOWN  : KSYM_UNDEFINED);
-    si->input[i].key.snap  = (i == 0 ? DEFAULT_KEY_SNAP  : KSYM_UNDEFINED);
-    si->input[i].key.drop  = (i == 0 ? DEFAULT_KEY_DROP  : KSYM_UNDEFINED);
-  }
+  // try to load setup values from user setup file
+  filename = getSetupFilename();
 
-  si->system.sdl_videodriver = getStringCopy(ARG_DEFAULT);
-  si->system.sdl_audiodriver = getStringCopy(ARG_DEFAULT);
-  si->system.audio_fragment_size = DEFAULT_AUDIO_FRAGMENT_SIZE;
+  LoadSetupFromFilename(filename);
 
-  si->internal.program_title     = getStringCopy(PROGRAM_TITLE_STRING);
-  si->internal.program_version   = getStringCopy(getProgramRealVersionString());
-  si->internal.program_author    = getStringCopy(PROGRAM_AUTHOR_STRING);
-  si->internal.program_email     = getStringCopy(PROGRAM_EMAIL_STRING);
-  si->internal.program_website   = getStringCopy(PROGRAM_WEBSITE_STRING);
-  si->internal.program_copyright = getStringCopy(PROGRAM_COPYRIGHT_STRING);
-  si->internal.program_company   = getStringCopy(PROGRAM_COMPANY_STRING);
+  LoadSetup_SpecialPostProcessing();
+}
 
-  si->internal.program_icon_file = getStringCopy(PROGRAM_ICON_FILENAME);
+void LoadSetup_AutoSetup(void)
+{
+  char *filename = getPath2(getSetupDir(), AUTOSETUP_FILENAME);
+  SetupFileHash *setup_file_hash = NULL;
 
-  si->internal.default_graphics_set = getStringCopy(GFX_CLASSIC_SUBDIR);
-  si->internal.default_sounds_set   = getStringCopy(SND_CLASSIC_SUBDIR);
-  si->internal.default_music_set    = getStringCopy(MUS_CLASSIC_SUBDIR);
+  // always start with reliable default values
+  setSetupInfoToDefaults_AutoSetup(&setup);
 
-  si->internal.fallback_graphics_file = getStringCopy(UNDEFINED_FILENAME);
-  si->internal.fallback_sounds_file   = getStringCopy(UNDEFINED_FILENAME);
-  si->internal.fallback_music_file    = getStringCopy(UNDEFINED_FILENAME);
+  setup_file_hash = loadSetupFileHash(filename);
 
-  si->internal.default_level_series = getStringCopy(UNDEFINED_LEVELSET);
-  si->internal.choose_from_top_leveldir = FALSE;
-  si->internal.show_scaling_in_title = TRUE;
+  if (setup_file_hash)
+  {
+    decodeSetupFileHash_AutoSetup(setup_file_hash);
 
-  si->internal.default_window_width  = WIN_XSIZE_DEFAULT;
-  si->internal.default_window_height = WIN_YSIZE_DEFAULT;
+    freeSetupFileHash(setup_file_hash);
+  }
 
-  si->debug.frame_delay[0] = DEFAULT_FRAME_DELAY_0;
-  si->debug.frame_delay[1] = DEFAULT_FRAME_DELAY_1;
-  si->debug.frame_delay[2] = DEFAULT_FRAME_DELAY_2;
-  si->debug.frame_delay[3] = DEFAULT_FRAME_DELAY_3;
-  si->debug.frame_delay[4] = DEFAULT_FRAME_DELAY_4;
-  si->debug.frame_delay[5] = DEFAULT_FRAME_DELAY_5;
-  si->debug.frame_delay[6] = DEFAULT_FRAME_DELAY_6;
-  si->debug.frame_delay[7] = DEFAULT_FRAME_DELAY_7;
-  si->debug.frame_delay[8] = DEFAULT_FRAME_DELAY_8;
-  si->debug.frame_delay[9] = DEFAULT_FRAME_DELAY_9;
+  free(filename);
+}
 
-  si->debug.frame_delay_key[0] = DEFAULT_KEY_FRAME_DELAY_0;
-  si->debug.frame_delay_key[1] = DEFAULT_KEY_FRAME_DELAY_1;
-  si->debug.frame_delay_key[2] = DEFAULT_KEY_FRAME_DELAY_2;
-  si->debug.frame_delay_key[3] = DEFAULT_KEY_FRAME_DELAY_3;
-  si->debug.frame_delay_key[4] = DEFAULT_KEY_FRAME_DELAY_4;
-  si->debug.frame_delay_key[5] = DEFAULT_KEY_FRAME_DELAY_5;
-  si->debug.frame_delay_key[6] = DEFAULT_KEY_FRAME_DELAY_6;
-  si->debug.frame_delay_key[7] = DEFAULT_KEY_FRAME_DELAY_7;
-  si->debug.frame_delay_key[8] = DEFAULT_KEY_FRAME_DELAY_8;
-  si->debug.frame_delay_key[9] = DEFAULT_KEY_FRAME_DELAY_9;
+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);
 
-  si->debug.frame_delay_use_mod_key = DEFAULT_FRAME_DELAY_USE_MOD_KEY;
-  si->debug.frame_delay_game_only   = DEFAULT_FRAME_DELAY_GAME_ONLY;
+  if (setup_file_hash)
+  {
+    decodeSetupFileHash_ServerSetup(setup_file_hash);
 
-  si->debug.show_frames_per_second = FALSE;
+    freeSetupFileHash(setup_file_hash);
+  }
 
-  si->options.verbose = FALSE;
+  free(filename);
 
-#if defined(PLATFORM_ANDROID)
-  si->fullscreen = TRUE;
-#endif
-}
+  if (setup.player_uuid == NULL)
+  {
+    // player UUID does not yet exist in setup file
+    setup.player_uuid = getStringCopy(getUUID());
+    setup.player_version = 2;
 
-static void setSetupInfoToDefaults_AutoSetup(struct SetupInfo *si)
-{
-  si->auto_setup.editor_zoom_tilesize = MINI_TILESIZE;
+    SaveSetup_ServerSetup();
+  }
 }
 
-static void setSetupInfoToDefaults_EditorCascade(struct SetupInfo *si)
+void LoadSetup_EditorCascade(void)
 {
-  si->editor_cascade.el_bd             = TRUE;
-  si->editor_cascade.el_em             = TRUE;
-  si->editor_cascade.el_emc            = TRUE;
-  si->editor_cascade.el_rnd            = TRUE;
-  si->editor_cascade.el_sb             = TRUE;
-  si->editor_cascade.el_sp             = TRUE;
-  si->editor_cascade.el_dc             = TRUE;
-  si->editor_cascade.el_dx             = TRUE;
-
-  si->editor_cascade.el_mm             = TRUE;
-  si->editor_cascade.el_df             = TRUE;
+  char *filename = getPath2(getSetupDir(), EDITORCASCADE_FILENAME);
+  SetupFileHash *setup_file_hash = NULL;
 
-  si->editor_cascade.el_chars          = FALSE;
-  si->editor_cascade.el_steel_chars    = FALSE;
-  si->editor_cascade.el_ce             = FALSE;
-  si->editor_cascade.el_ge             = FALSE;
-  si->editor_cascade.el_ref            = FALSE;
-  si->editor_cascade.el_user           = FALSE;
-  si->editor_cascade.el_dynamic                = FALSE;
-}
+  // always start with reliable default values
+  setSetupInfoToDefaults_EditorCascade(&setup);
 
-#define MAX_HIDE_SETUP_TOKEN_SIZE              20
+  setup_file_hash = loadSetupFileHash(filename);
 
-static char *getHideSetupToken(void *setup_value)
-{
-  static char hide_setup_token[MAX_HIDE_SETUP_TOKEN_SIZE];
+  if (setup_file_hash)
+  {
+    decodeSetupFileHash_EditorCascade(setup_file_hash);
 
-  if (setup_value != NULL)
-    snprintf(hide_setup_token, MAX_HIDE_SETUP_TOKEN_SIZE, "%p", setup_value);
+    freeSetupFileHash(setup_file_hash);
+  }
 
-  return hide_setup_token;
+  free(filename);
 }
 
-void setHideSetupEntry(void *setup_value)
+void LoadSetup(void)
 {
-  char *hide_setup_token = getHideSetupToken(setup_value);
-
-  if (setup_value != NULL)
-    setHashEntry(hide_setup_hash, hide_setup_token, "");
+  LoadSetup_Default();
+  LoadSetup_AutoSetup();
+  LoadSetup_ServerSetup();
+  LoadSetup_EditorCascade();
 }
 
-static void setHideSetupEntryRaw(char *token_text, void *setup_value_raw)
+static void addGameControllerMappingToHash(SetupFileHash *mappings_hash,
+                                          char *mapping_line)
 {
-  // !!! DIRTY WORKAROUND; TO BE FIXED AFTER THE MM ENGINE RELEASE !!!
-  void *setup_value = setup_value_raw - (void *)&si + (void *)&setup;
+  char mapping_guid[MAX_LINE_LEN];
+  char *mapping_start, *mapping_end;
 
-  setHideSetupEntry(setup_value);
-}
+  // get GUID from game controller mapping line: copy complete line
+  strncpy(mapping_guid, mapping_line, MAX_LINE_LEN - 1);
+  mapping_guid[MAX_LINE_LEN - 1] = '\0';
 
-boolean hideSetupEntry(void *setup_value)
-{
-  char *hide_setup_token = getHideSetupToken(setup_value);
+  // get GUID from game controller mapping line: cut after GUID part
+  mapping_start = strchr(mapping_guid, ',');
+  if (mapping_start != NULL)
+    *mapping_start = '\0';
 
-  return (setup_value != NULL &&
-         getHashEntry(hide_setup_hash, hide_setup_token) != NULL);
+  // cut newline from game controller mapping line
+  mapping_end = strchr(mapping_line, '\n');
+  if (mapping_end != NULL)
+    *mapping_end = '\0';
+
+  // add mapping entry to game controller mappings hash
+  setHashEntry(mappings_hash, mapping_guid, mapping_line);
 }
 
-static void setSetupInfoFromTokenText(SetupFileHash *setup_file_hash,
-                                     struct TokenInfo *token_info,
-                                     int token_nr, char *token_text)
+static void LoadSetup_ReadGameControllerMappings(SetupFileHash *mappings_hash,
+                                                char *filename)
 {
-  char *token_hide_text = getStringCat2(token_text, ".hide");
-  char *token_hide_value = getHashEntry(setup_file_hash, token_hide_text);
+  FILE *file;
 
-  // set the value of this setup option in the setup option structure
-  setSetupInfo(token_info, token_nr, getHashEntry(setup_file_hash, token_text));
+  if (!(file = fopen(filename, MODE_READ)))
+  {
+    Warn("cannot read game controller mappings file '%s'", filename);
 
-  // check if this setup option should be hidden in the setup menu
-  if (token_hide_value != NULL && get_boolean_from_string(token_hide_value))
-    setHideSetupEntryRaw(token_text, token_info[token_nr].value);
-}
+    return;
+  }
 
-static void setSetupInfoFromTokenInfo(SetupFileHash *setup_file_hash,
-                                     struct TokenInfo *token_info,
-                                     int token_nr)
-{
-  setSetupInfoFromTokenText(setup_file_hash, token_info, token_nr,
-                           token_info[token_nr].text);
+  while (!feof(file))
+  {
+    char line[MAX_LINE_LEN];
+
+    if (!fgets(line, MAX_LINE_LEN, file))
+      break;
+
+    addGameControllerMappingToHash(mappings_hash, line);
+  }
+
+  fclose(file);
 }
 
-static void decodeSetupFileHash(SetupFileHash *setup_file_hash)
+void SaveSetup_Default(void)
 {
+  char *filename = getSetupFilename();
+  FILE *file;
   int i, pnr;
 
-  if (!setup_file_hash)
+  InitUserDataDirectory();
+
+  if (!(file = fopen(filename, MODE_WRITE)))
+  {
+    Warn("cannot write setup file '%s'", filename);
+
     return;
+  }
 
-  if (hide_setup_hash == NULL)
-    hide_setup_hash = newSetupFileHash();
+  fprintFileHeader(file, SETUP_FILENAME);
 
-  // global setup
-  si = setup;
-  for (i = 0; i < NUM_GLOBAL_SETUP_TOKENS; i++)
-    setSetupInfoFromTokenInfo(setup_file_hash, global_setup_tokens, i);
-  setup = si;
+  for (i = 0; i < ARRAY_SIZE(global_setup_tokens); i++)
+  {
+    // just to make things nicer :)
+    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             ||
+       global_setup_tokens[i].value == &setup.touch.control_type       ||
+       global_setup_tokens[i].value == &setup.touch.grid_xsize[0]      ||
+       global_setup_tokens[i].value == &setup.touch.grid_xsize[1])
+      fprintf(file, "\n");
+
+    fprintf(file, "%s\n", getSetupLine(global_setup_tokens, "", i));
+  }
 
-  // virtual buttons setup
-  setup.touch.grid_initialized = TRUE;
   for (i = 0; i < 2; i++)
   {
     int grid_xsize = setup.touch.grid_xsize[i];
     int grid_ysize = setup.touch.grid_ysize[i];
     int x, y;
 
-    // if virtual buttons are not loaded from setup file, repeat initializing
-    // virtual buttons grid with default values later when video is initialized
-    if (grid_xsize == -1 ||
-       grid_ysize == -1)
-    {
-      setup.touch.grid_initialized = FALSE;
-
-      continue;
-    }
+    fprintf(file, "\n");
 
     for (y = 0; y < grid_ysize; y++)
     {
       char token_string[MAX_LINE_LEN];
+      char value_string[MAX_LINE_LEN];
 
       sprintf(token_string, "touch.virtual_buttons.%d.%02d", i, y);
 
-      char *value_string = getHashEntry(setup_file_hash, token_string);
-
-      if (value_string == NULL)
-       continue;
-
       for (x = 0; x < grid_xsize; x++)
       {
-       char c = value_string[x];
+       char c = setup.touch.grid_button[i][x][y];
 
-       setup.touch.grid_button[i][x][y] =
-         (c == '.' ? CHAR_GRID_BUTTON_NONE : c);
+       value_string[x] = (c == CHAR_GRID_BUTTON_NONE ? '.' : c);
       }
+
+      value_string[grid_xsize] = '\0';
+
+      fprintf(file, "%s\n", getFormattedSetupEntry(token_string, value_string));
     }
   }
 
-  // editor setup
-  sei = setup.editor;
-  for (i = 0; i < NUM_EDITOR_SETUP_TOKENS; i++)
-    setSetupInfoFromTokenInfo(setup_file_hash, editor_setup_tokens, i);
-  setup.editor = sei;
+  fprintf(file, "\n");
+  for (i = 0; i < ARRAY_SIZE(editor_setup_tokens); i++)
+    fprintf(file, "%s\n", getSetupLine(editor_setup_tokens, "", i));
 
-  // shortcut setup
-  ssi = setup.shortcut;
-  for (i = 0; i < NUM_SHORTCUT_SETUP_TOKENS; i++)
-    setSetupInfoFromTokenInfo(setup_file_hash, shortcut_setup_tokens, i);
-  setup.shortcut = ssi;
+  fprintf(file, "\n");
+  for (i = 0; i < ARRAY_SIZE(shortcut_setup_tokens); i++)
+    fprintf(file, "%s\n", getSetupLine(shortcut_setup_tokens, "", i));
 
-  // player setup
   for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
   {
     char prefix[30];
 
-    sprintf(prefix, "%s%d", TOKEN_STR_PLAYER_PREFIX, pnr + 1);
+    sprintf(prefix, "%s%d", TOKEN_STR_PLAYER_PREFIX, pnr + 1);
+    fprintf(file, "\n");
+
+    setup_input = setup.input[pnr];
+    for (i = 0; i < ARRAY_SIZE(player_setup_tokens); i++)
+      fprintf(file, "%s\n", getSetupLine(player_setup_tokens, prefix, i));
+  }
+
+  fprintf(file, "\n");
+  for (i = 0; i < ARRAY_SIZE(system_setup_tokens); i++)
+    fprintf(file, "%s\n", getSetupLine(system_setup_tokens, "", i));
+
+  // (internal setup values not saved to user setup file)
+
+  fprintf(file, "\n");
+  for (i = 0; i < ARRAY_SIZE(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++)
+    fprintf(file, "%s\n", getSetupLine(options_setup_tokens, "", i));
+
+  fclose(file);
+
+  SetFilePermissions(filename, PERMS_PRIVATE);
+}
+
+void SaveSetup_AutoSetup(void)
+{
+  char *filename = getPath2(getSetupDir(), AUTOSETUP_FILENAME);
+  FILE *file;
+  int i;
+
+  InitUserDataDirectory();
+
+  if (!(file = fopen(filename, MODE_WRITE)))
+  {
+    Warn("cannot write auto setup file '%s'", filename);
+
+    free(filename);
+
+    return;
+  }
+
+  fprintFileHeader(file, AUTOSETUP_FILENAME);
+
+  for (i = 0; i < ARRAY_SIZE(auto_setup_tokens); i++)
+    fprintf(file, "%s\n", getSetupLine(auto_setup_tokens, "", i));
+
+  fclose(file);
+
+  SetFilePermissions(filename, PERMS_PRIVATE);
+
+  free(filename);
+}
+
+void SaveSetup_ServerSetup(void)
+{
+  char *filename = getPath2(getSetupDir(), 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);
+  FILE *file;
+  int i;
+
+  InitUserDataDirectory();
+
+  if (!(file = fopen(filename, MODE_WRITE)))
+  {
+    Warn("cannot write editor cascade state file '%s'", filename);
 
-    sii = setup.input[pnr];
-    for (i = 0; i < NUM_PLAYER_SETUP_TOKENS; i++)
-    {
-      char full_token[100];
+    free(filename);
 
-      sprintf(full_token, "%s%s", prefix, player_setup_tokens[i].text);
-      setSetupInfoFromTokenText(setup_file_hash, player_setup_tokens, i,
-                               full_token);
-    }
-    setup.input[pnr] = sii;
+    return;
   }
 
-  // system setup
-  syi = setup.system;
-  for (i = 0; i < NUM_SYSTEM_SETUP_TOKENS; i++)
-    setSetupInfoFromTokenInfo(setup_file_hash, system_setup_tokens, i);
-  setup.system = syi;
+  fprintFileHeader(file, EDITORCASCADE_FILENAME);
 
-  // internal setup
-  sxi = setup.internal;
-  for (i = 0; i < NUM_INTERNAL_SETUP_TOKENS; i++)
-    setSetupInfoFromTokenInfo(setup_file_hash, internal_setup_tokens, i);
-  setup.internal = sxi;
+  for (i = 0; i < ARRAY_SIZE(editor_cascade_setup_tokens); i++)
+    fprintf(file, "%s\n", getSetupLine(editor_cascade_setup_tokens, "", i));
 
-  // debug setup
-  sdi = setup.debug;
-  for (i = 0; i < NUM_DEBUG_SETUP_TOKENS; i++)
-    setSetupInfoFromTokenInfo(setup_file_hash, debug_setup_tokens, i);
-  setup.debug = sdi;
+  fclose(file);
 
-  // options setup
-  soi = setup.options;
-  for (i = 0; i < NUM_OPTIONS_SETUP_TOKENS; i++)
-    setSetupInfoFromTokenInfo(setup_file_hash, options_setup_tokens, i);
-  setup.options = soi;
+  SetFilePermissions(filename, PERMS_PRIVATE);
 
-  setHideRelatedSetupEntries();
+  free(filename);
 }
 
-static void decodeSetupFileHash_AutoSetup(SetupFileHash *setup_file_hash)
+void SaveSetup(void)
 {
-  int i;
+  SaveSetup_Default();
+  SaveSetup_AutoSetup();
+  SaveSetup_ServerSetup();
+  SaveSetup_EditorCascade();
+}
+
+static void SaveSetup_WriteGameControllerMappings(SetupFileHash *mappings_hash,
+                                                 char *filename)
+{
+  FILE *file;
+
+  if (!(file = fopen(filename, MODE_WRITE)))
+  {
+    Warn("cannot write game controller mappings file '%s'", filename);
 
-  if (!setup_file_hash)
     return;
+  }
 
-  // auto setup
-  sasi = setup.auto_setup;
-  for (i = 0; i < NUM_AUTO_SETUP_TOKENS; i++)
-    setSetupInfo(auto_setup_tokens, i,
-                getHashEntry(setup_file_hash,
-                             auto_setup_tokens[i].text));
-  setup.auto_setup = sasi;
+  BEGIN_HASH_ITERATION(mappings_hash, itr)
+  {
+    fprintf(file, "%s\n", HASH_ITERATION_VALUE(itr));
+  }
+  END_HASH_ITERATION(mappings_hash, itr)
+
+  fclose(file);
 }
 
-static void decodeSetupFileHash_EditorCascade(SetupFileHash *setup_file_hash)
+void SaveSetup_AddGameControllerMapping(char *mapping)
 {
-  int i;
+  char *filename = getPath2(getSetupDir(), GAMECONTROLLER_BASENAME);
+  SetupFileHash *mappings_hash = newSetupFileHash();
 
-  if (!setup_file_hash)
-    return;
+  InitUserDataDirectory();
 
-  // editor cascade setup
-  seci = setup.editor_cascade;
-  for (i = 0; i < NUM_EDITOR_CASCADE_SETUP_TOKENS; i++)
-    setSetupInfo(editor_cascade_setup_tokens, i,
-                getHashEntry(setup_file_hash,
-                             editor_cascade_setup_tokens[i].text));
-  setup.editor_cascade = seci;
+  // load existing personal game controller mappings
+  LoadSetup_ReadGameControllerMappings(mappings_hash, filename);
+
+  // add new mapping to personal game controller mappings
+  addGameControllerMappingToHash(mappings_hash, mapping);
+
+  // save updated personal game controller mappings
+  SaveSetup_WriteGameControllerMappings(mappings_hash, filename);
+
+  freeSetupFileHash(mappings_hash);
+  free(filename);
 }
 
-void LoadSetupFromFilename(char *filename)
+void LoadCustomElementDescriptions(void)
 {
-  SetupFileHash *setup_file_hash = loadSetupFileHash(filename);
+  char *filename = getCustomArtworkConfigFilename(ARTWORK_TYPE_GRAPHICS);
+  SetupFileHash *setup_file_hash;
+  int i;
 
-  if (setup_file_hash)
+  for (i = 0; i < NUM_FILE_ELEMENTS; i++)
   {
-    decodeSetupFileHash(setup_file_hash);
-
-    freeSetupFileHash(setup_file_hash);
+    if (element_info[i].custom_description != NULL)
+    {
+      free(element_info[i].custom_description);
+      element_info[i].custom_description = NULL;
+    }
   }
-  else
+
+  if ((setup_file_hash = loadSetupFileHash(filename)) == NULL)
+    return;
+
+  for (i = 0; i < NUM_FILE_ELEMENTS; i++)
   {
-    Error(ERR_DEBUG, "using default setup values");
+    char *token = getStringCat2(element_info[i].token_name, ".name");
+    char *value = getHashEntry(setup_file_hash, token);
+
+    if (value != NULL)
+      element_info[i].custom_description = getStringCopy(value);
+
+    free(token);
   }
+
+  freeSetupFileHash(setup_file_hash);
 }
 
-static void LoadSetup_SpecialPostProcessing(void)
+static int getElementFromToken(char *token)
 {
-  char *player_name_new;
+  char *value = getHashEntry(element_token_hash, token);
 
-  // needed to work around problems with fixed length strings
-  player_name_new = get_corrected_login_name(setup.player_name);
-  free(setup.player_name);
-  setup.player_name = player_name_new;
+  if (value != NULL)
+    return atoi(value);
 
-  // "scroll_delay: on(3) / off(0)" was replaced by scroll delay value
-  if (setup.scroll_delay == FALSE)
-  {
-    setup.scroll_delay_value = MIN_SCROLL_DELAY;
-    setup.scroll_delay = TRUE;                 // now always "on"
-  }
+  Warn("unknown element token '%s'", token);
 
-  // make sure that scroll delay value stays inside valid range
-  setup.scroll_delay_value =
-    MIN(MAX(MIN_SCROLL_DELAY, setup.scroll_delay_value), MAX_SCROLL_DELAY);
+  return EL_UNDEFINED;
 }
 
-void LoadSetup(void)
+void FreeGlobalAnimEventInfo(void)
 {
-  char *filename;
-
-  // always start with reliable default values
-  setSetupInfoToDefaults(&setup);
+  struct GlobalAnimEventInfo *gaei = &global_anim_event_info;
 
-  // try to load setup values from default setup file
-  filename = getDefaultSetupFilename();
+  if (gaei->event_list == NULL)
+    return;
 
-  if (fileExists(filename))
-    LoadSetupFromFilename(filename);
+  int i;
 
-  // try to load setup values from user setup file
-  filename = getSetupFilename();
+  for (i = 0; i < gaei->num_event_lists; i++)
+  {
+    checked_free(gaei->event_list[i]->event_value);
+    checked_free(gaei->event_list[i]);
+  }
 
-  LoadSetupFromFilename(filename);
+  checked_free(gaei->event_list);
 
-  LoadSetup_SpecialPostProcessing();
+  gaei->event_list = NULL;
+  gaei->num_event_lists = 0;
 }
 
-void LoadSetup_AutoSetup(void)
+static int AddGlobalAnimEventList(void)
 {
-  char *filename = getPath2(getSetupDir(), AUTOSETUP_FILENAME);
-  SetupFileHash *setup_file_hash = NULL;
+  struct GlobalAnimEventInfo *gaei = &global_anim_event_info;
+  int list_pos = gaei->num_event_lists++;
 
-  // always start with reliable default values
-  setSetupInfoToDefaults_AutoSetup(&setup);
+  gaei->event_list = checked_realloc(gaei->event_list, gaei->num_event_lists *
+                                    sizeof(struct GlobalAnimEventListInfo *));
 
-  setup_file_hash = loadSetupFileHash(filename);
+  gaei->event_list[list_pos] =
+    checked_calloc(sizeof(struct GlobalAnimEventListInfo));
 
-  if (setup_file_hash)
-  {
-    decodeSetupFileHash_AutoSetup(setup_file_hash);
+  struct GlobalAnimEventListInfo *gaeli = gaei->event_list[list_pos];
 
-    freeSetupFileHash(setup_file_hash);
-  }
+  gaeli->event_value = NULL;
+  gaeli->num_event_values = 0;
 
-  free(filename);
+  return list_pos;
 }
 
-void LoadSetup_EditorCascade(void)
+static int AddGlobalAnimEventValue(int list_pos, int event_value)
 {
-  char *filename = getPath2(getSetupDir(), EDITORCASCADE_FILENAME);
-  SetupFileHash *setup_file_hash = NULL;
+  // do not add empty global animation events
+  if (event_value == ANIM_EVENT_NONE)
+    return list_pos;
 
-  // always start with reliable default values
-  setSetupInfoToDefaults_EditorCascade(&setup);
+  // if list position is undefined, create new list
+  if (list_pos == ANIM_EVENT_UNDEFINED)
+    list_pos = AddGlobalAnimEventList();
 
-  setup_file_hash = loadSetupFileHash(filename);
+  struct GlobalAnimEventInfo *gaei = &global_anim_event_info;
+  struct GlobalAnimEventListInfo *gaeli = gaei->event_list[list_pos];
+  int value_pos = gaeli->num_event_values++;
 
-  if (setup_file_hash)
-  {
-    decodeSetupFileHash_EditorCascade(setup_file_hash);
+  gaeli->event_value = checked_realloc(gaeli->event_value,
+                                      gaeli->num_event_values * sizeof(int *));
 
-    freeSetupFileHash(setup_file_hash);
-  }
+  gaeli->event_value[value_pos] = event_value;
 
-  free(filename);
+  return list_pos;
 }
 
-static void addGameControllerMappingToHash(SetupFileHash *mappings_hash,
-                                          char *mapping_line)
+int GetGlobalAnimEventValue(int list_pos, int value_pos)
 {
-  char mapping_guid[MAX_LINE_LEN];
-  char *mapping_start, *mapping_end;
+  if (list_pos == ANIM_EVENT_UNDEFINED)
+    return ANIM_EVENT_NONE;
 
-  // get GUID from game controller mapping line: copy complete line
-  strncpy(mapping_guid, mapping_line, MAX_LINE_LEN - 1);
-  mapping_guid[MAX_LINE_LEN - 1] = '\0';
+  struct GlobalAnimEventInfo *gaei = &global_anim_event_info;
+  struct GlobalAnimEventListInfo *gaeli = gaei->event_list[list_pos];
 
-  // get GUID from game controller mapping line: cut after GUID part
-  mapping_start = strchr(mapping_guid, ',');
-  if (mapping_start != NULL)
-    *mapping_start = '\0';
+  return gaeli->event_value[value_pos];
+}
 
-  // cut newline from game controller mapping line
-  mapping_end = strchr(mapping_line, '\n');
-  if (mapping_end != NULL)
-    *mapping_end = '\0';
+int GetGlobalAnimEventValueCount(int list_pos)
+{
+  if (list_pos == ANIM_EVENT_UNDEFINED)
+    return 0;
 
-  // add mapping entry to game controller mappings hash
-  setHashEntry(mappings_hash, mapping_guid, mapping_line);
+  struct GlobalAnimEventInfo *gaei = &global_anim_event_info;
+  struct GlobalAnimEventListInfo *gaeli = gaei->event_list[list_pos];
+
+  return gaeli->num_event_values;
 }
 
-static void LoadSetup_ReadGameControllerMappings(SetupFileHash *mappings_hash,
-                                                char *filename)
+// This function checks if a string <s> of the format "string1, string2, ..."
+// exactly contains a string <s_contained>.
+
+static boolean string_has_parameter(char *s, char *s_contained)
 {
-  FILE *file;
+  char *substring;
 
-  if (!(file = fopen(filename, MODE_READ)))
+  if (s == NULL || s_contained == NULL)
+    return FALSE;
+
+  if (strlen(s_contained) > strlen(s))
+    return FALSE;
+
+  if (strncmp(s, s_contained, strlen(s_contained)) == 0)
   {
-    Error(ERR_WARN, "cannot read game controller mappings file '%s'", filename);
+    char next_char = s[strlen(s_contained)];
 
-    return;
+    // check if next character is delimiter or whitespace
+    if (next_char == ',' || next_char == '\0' ||
+       next_char == ' ' || next_char == '\t')
+      return TRUE;
   }
 
-  while (!feof(file))
-  {
-    char line[MAX_LINE_LEN];
+  // check if string contains another parameter string after a comma
+  substring = strchr(s, ',');
+  if (substring == NULL)       // string does not contain a comma
+    return FALSE;
 
-    if (!fgets(line, MAX_LINE_LEN, file))
-      break;
+  // advance string pointer to next character after the comma
+  substring++;
 
-    addGameControllerMappingToHash(mappings_hash, line);
-  }
+  // skip potential whitespaces after the comma
+  while (*substring == ' ' || *substring == '\t')
+    substring++;
 
-  fclose(file);
+  return string_has_parameter(substring, s_contained);
 }
 
-void SaveSetup(void)
+static int get_anim_parameter_value_ce(char *s)
 {
-  char *filename = getSetupFilename();
-  FILE *file;
-  int i, pnr;
-
-  InitUserDataDirectory();
-
-  if (!(file = fopen(filename, MODE_WRITE)))
-  {
-    Error(ERR_WARN, "cannot write setup file '%s'", filename);
-    return;
-  }
+  char *s_ptr = s;
+  char *pattern_1 = "ce_change:custom_";
+  char *pattern_2 = ".page_";
+  int pattern_1_len = strlen(pattern_1);
+  char *matching_char = strstr(s_ptr, pattern_1);
+  int result = ANIM_EVENT_NONE;
 
-  fprintFileHeader(file, SETUP_FILENAME);
+  if (matching_char == NULL)
+    return ANIM_EVENT_NONE;
 
-  // global setup
-  si = setup;
-  for (i = 0; i < NUM_GLOBAL_SETUP_TOKENS; i++)
-  {
-    // just to make things nicer :)
-    if (i == SETUP_TOKEN_PLAYER_NAME + 1 ||
-       i == SETUP_TOKEN_GRAPHICS_SET ||
-       i == SETUP_TOKEN_VOLUME_SIMPLE ||
-       i == SETUP_TOKEN_NETWORK_MODE ||
-       i == SETUP_TOKEN_TOUCH_CONTROL_TYPE ||
-       i == SETUP_TOKEN_TOUCH_GRID_XSIZE_0 ||
-       i == SETUP_TOKEN_TOUCH_GRID_XSIZE_1)
-      fprintf(file, "\n");
+  result = ANIM_EVENT_CE_CHANGE;
 
-    fprintf(file, "%s\n", getSetupLine(global_setup_tokens, "", i));
-  }
+  s_ptr = matching_char + pattern_1_len;
 
-  // virtual buttons setup
-  for (i = 0; i < 2; i++)
+  // check for custom element number ("custom_X", "custom_XX" or "custom_XXX")
+  if (*s_ptr >= '0' && *s_ptr <= '9')
   {
-    int grid_xsize = setup.touch.grid_xsize[i];
-    int grid_ysize = setup.touch.grid_ysize[i];
-    int x, y;
+    int gic_ce_nr = (*s_ptr++ - '0');
 
-    fprintf(file, "\n");
-
-    for (y = 0; y < grid_ysize; y++)
+    if (*s_ptr >= '0' && *s_ptr <= '9')
     {
-      char token_string[MAX_LINE_LEN];
-      char value_string[MAX_LINE_LEN];
-
-      sprintf(token_string, "touch.virtual_buttons.%d.%02d", i, y);
+      gic_ce_nr = 10 * gic_ce_nr + (*s_ptr++ - '0');
 
-      for (x = 0; x < grid_xsize; x++)
-      {
-       char c = setup.touch.grid_button[i][x][y];
+      if (*s_ptr >= '0' && *s_ptr <= '9')
+       gic_ce_nr = 10 * gic_ce_nr + (*s_ptr++ - '0');
+    }
 
-       value_string[x] = (c == CHAR_GRID_BUTTON_NONE ? '.' : c);
-      }
+    if (gic_ce_nr < 1 || gic_ce_nr > NUM_CUSTOM_ELEMENTS)
+      return ANIM_EVENT_NONE;
 
-      value_string[grid_xsize] = '\0';
+    // custom element stored as 0 to 255
+    gic_ce_nr--;
 
-      fprintf(file, "%s\n", getFormattedSetupEntry(token_string, value_string));
-    }
+    result |= gic_ce_nr << ANIM_EVENT_CE_BIT;
   }
+  else
+  {
+    // invalid custom element number specified
 
-  // editor setup
-  sei = setup.editor;
-  fprintf(file, "\n");
-  for (i = 0; i < NUM_EDITOR_SETUP_TOKENS; i++)
-    fprintf(file, "%s\n", getSetupLine(editor_setup_tokens, "", i));
-
-  // shortcut setup
-  ssi = setup.shortcut;
-  fprintf(file, "\n");
-  for (i = 0; i < NUM_SHORTCUT_SETUP_TOKENS; i++)
-    fprintf(file, "%s\n", getSetupLine(shortcut_setup_tokens, "", i));
+    return ANIM_EVENT_NONE;
+  }
 
-  // player setup
-  for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
+  // check for change page number ("page_X" or "page_XX") (optional)
+  if (strPrefix(s_ptr, pattern_2))
   {
-    char prefix[30];
+    s_ptr += strlen(pattern_2);
 
-    sprintf(prefix, "%s%d", TOKEN_STR_PLAYER_PREFIX, pnr + 1);
-    fprintf(file, "\n");
+    if (*s_ptr >= '0' && *s_ptr <= '9')
+    {
+      int gic_page_nr = (*s_ptr++ - '0');
 
-    sii = setup.input[pnr];
-    for (i = 0; i < NUM_PLAYER_SETUP_TOKENS; i++)
-      fprintf(file, "%s\n", getSetupLine(player_setup_tokens, prefix, i));
-  }
+      if (*s_ptr >= '0' && *s_ptr <= '9')
+       gic_page_nr = 10 * gic_page_nr + (*s_ptr++ - '0');
 
-  // system setup
-  syi = setup.system;
-  fprintf(file, "\n");
-  for (i = 0; i < NUM_SYSTEM_SETUP_TOKENS; i++)
-    fprintf(file, "%s\n", getSetupLine(system_setup_tokens, "", i));
+      if (gic_page_nr < 1 || gic_page_nr > MAX_CHANGE_PAGES)
+       return ANIM_EVENT_NONE;
 
-  // internal setup
-  // (internal setup values not saved to user setup file)
+      // change page stored as 1 to 32 (0 means "all change pages")
 
-  // debug setup
-  sdi = setup.debug;
-  fprintf(file, "\n");
-  for (i = 0; i < NUM_DEBUG_SETUP_TOKENS; i++)
-    fprintf(file, "%s\n", getSetupLine(debug_setup_tokens, "", i));
+      result |= gic_page_nr << ANIM_EVENT_PAGE_BIT;
+    }
+    else
+    {
+      // invalid animation part number specified
 
-  // options setup
-  soi = setup.options;
-  fprintf(file, "\n");
-  for (i = 0; i < NUM_OPTIONS_SETUP_TOKENS; i++)
-    fprintf(file, "%s\n", getSetupLine(options_setup_tokens, "", i));
+      return ANIM_EVENT_NONE;
+    }
+  }
 
-  fclose(file);
+  // discard result if next character is neither delimiter nor whitespace
+  if (!(*s_ptr == ',' || *s_ptr == '\0' ||
+       *s_ptr == ' ' || *s_ptr == '\t'))
+    return ANIM_EVENT_NONE;
 
-  SetFilePermissions(filename, PERMS_PRIVATE);
+  return result;
 }
 
-void SaveSetup_AutoSetup(void)
+static int get_anim_parameter_value(char *s)
 {
-  char *filename = getPath2(getSetupDir(), AUTOSETUP_FILENAME);
-  FILE *file;
+  int event_value[] =
+  {
+    ANIM_EVENT_CLICK,
+    ANIM_EVENT_INIT,
+    ANIM_EVENT_START,
+    ANIM_EVENT_END,
+    ANIM_EVENT_POST
+  };
+  char *pattern_1[] =
+  {
+    "click:anim_",
+    "init:anim_",
+    "start:anim_",
+    "end:anim_",
+    "post:anim_"
+  };
+  char *pattern_2 = ".part_";
+  char *matching_char = NULL;
+  char *s_ptr = s;
+  int pattern_1_len = 0;
+  int result = ANIM_EVENT_NONE;
   int i;
 
-  InitUserDataDirectory();
+  result = get_anim_parameter_value_ce(s);
 
-  if (!(file = fopen(filename, MODE_WRITE)))
+  if (result != ANIM_EVENT_NONE)
+    return result;
+
+  for (i = 0; i < ARRAY_SIZE(event_value); i++)
   {
-    Error(ERR_WARN, "cannot write auto setup file '%s'", filename);
-    free(filename);
-    return;
+    matching_char = strstr(s_ptr, pattern_1[i]);
+    pattern_1_len = strlen(pattern_1[i]);
+    result = event_value[i];
+
+    if (matching_char != NULL)
+      break;
   }
 
-  fprintFileHeader(file, AUTOSETUP_FILENAME);
+  if (matching_char == NULL)
+    return ANIM_EVENT_NONE;
 
-  sasi = setup.auto_setup;
-  for (i = 0; i < NUM_AUTO_SETUP_TOKENS; i++)
-    fprintf(file, "%s\n", getSetupLine(auto_setup_tokens, "", i));
+  s_ptr = matching_char + pattern_1_len;
 
-  fclose(file);
+  // check for main animation number ("anim_X" or "anim_XX")
+  if (*s_ptr >= '0' && *s_ptr <= '9')
+  {
+    int gic_anim_nr = (*s_ptr++ - '0');
 
-  SetFilePermissions(filename, PERMS_PRIVATE);
+    if (*s_ptr >= '0' && *s_ptr <= '9')
+      gic_anim_nr = 10 * gic_anim_nr + (*s_ptr++ - '0');
 
-  free(filename);
-}
+    if (gic_anim_nr < 1 || gic_anim_nr > MAX_GLOBAL_ANIMS)
+      return ANIM_EVENT_NONE;
 
-void SaveSetup_EditorCascade(void)
-{
-  char *filename = getPath2(getSetupDir(), EDITORCASCADE_FILENAME);
-  FILE *file;
-  int i;
+    result |= gic_anim_nr << ANIM_EVENT_ANIM_BIT;
+  }
+  else
+  {
+    // invalid main animation number specified
 
-  InitUserDataDirectory();
+    return ANIM_EVENT_NONE;
+  }
 
-  if (!(file = fopen(filename, MODE_WRITE)))
+  // check for animation part number ("part_X" or "part_XX") (optional)
+  if (strPrefix(s_ptr, pattern_2))
   {
-    Error(ERR_WARN, "cannot write editor cascade state file '%s'", filename);
-    free(filename);
-    return;
-  }
+    s_ptr += strlen(pattern_2);
 
-  fprintFileHeader(file, EDITORCASCADE_FILENAME);
+    if (*s_ptr >= '0' && *s_ptr <= '9')
+    {
+      int gic_part_nr = (*s_ptr++ - '0');
 
-  seci = setup.editor_cascade;
-  for (i = 0; i < NUM_EDITOR_CASCADE_SETUP_TOKENS; i++)
-    fprintf(file, "%s\n", getSetupLine(editor_cascade_setup_tokens, "", i));
+      if (*s_ptr >= '0' && *s_ptr <= '9')
+       gic_part_nr = 10 * gic_part_nr + (*s_ptr++ - '0');
 
-  fclose(file);
+      if (gic_part_nr < 1 || gic_part_nr > MAX_GLOBAL_ANIM_PARTS)
+       return ANIM_EVENT_NONE;
 
-  SetFilePermissions(filename, PERMS_PRIVATE);
+      result |= gic_part_nr << ANIM_EVENT_PART_BIT;
+    }
+    else
+    {
+      // invalid animation part number specified
 
-  free(filename);
+      return ANIM_EVENT_NONE;
+    }
+  }
+
+  // discard result if next character is neither delimiter nor whitespace
+  if (!(*s_ptr == ',' || *s_ptr == '\0' ||
+       *s_ptr == ' ' || *s_ptr == '\t'))
+    return ANIM_EVENT_NONE;
+
+  return result;
 }
 
-static void SaveSetup_WriteGameControllerMappings(SetupFileHash *mappings_hash,
-                                                 char *filename)
+static int get_anim_parameter_values(char *s)
 {
-  FILE *file;
+  int list_pos = ANIM_EVENT_UNDEFINED;
+  int event_value = ANIM_EVENT_DEFAULT;
 
-  if (!(file = fopen(filename, MODE_WRITE)))
-  {
-    Error(ERR_WARN, "cannot write game controller mappings file '%s'",filename);
+  if (string_has_parameter(s, "any"))
+    event_value |= ANIM_EVENT_ANY;
 
-    return;
-  }
+  if (string_has_parameter(s, "click:self") ||
+      string_has_parameter(s, "click") ||
+      string_has_parameter(s, "self"))
+    event_value |= ANIM_EVENT_SELF;
 
-  BEGIN_HASH_ITERATION(mappings_hash, itr)
+  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);
+
+  while (s != NULL)
   {
-    fprintf(file, "%s\n", HASH_ITERATION_VALUE(itr));
+    // add optional "click:anim_X" or "click:anim_X.part_X" parameter
+    event_value = get_anim_parameter_value(s);
+
+    // if animation event found, add it to global animation event list
+    if (event_value != ANIM_EVENT_NONE)
+      list_pos = AddGlobalAnimEventValue(list_pos, event_value);
+
+    // continue with next part of the string, starting with next comma
+    s = strchr(s + 1, ',');
   }
-  END_HASH_ITERATION(mappings_hash, itr)
 
-  fclose(file);
+  return list_pos;
 }
 
-void SaveSetup_AddGameControllerMapping(char *mapping)
+static int get_anim_action_parameter_value(char *token)
 {
-  char *filename = getPath2(getSetupDir(), GAMECONTROLLER_BASENAME);
-  SetupFileHash *mappings_hash = newSetupFileHash();
+  // check most common default case first to massively speed things up
+  if (strEqual(token, ARG_UNDEFINED))
+    return ANIM_EVENT_ACTION_NONE;
 
-  InitUserDataDirectory();
+  int result = getImageIDFromToken(token);
 
-  // load existing personal game controller mappings
-  LoadSetup_ReadGameControllerMappings(mappings_hash, filename);
+  if (result == -1)
+  {
+    char *gfx_token = getStringCat2("gfx.", token);
 
-  // add new mapping to personal game controller mappings
-  addGameControllerMappingToHash(mappings_hash, mapping);
+    result = getImageIDFromToken(gfx_token);
 
-  // save updated personal game controller mappings
-  SaveSetup_WriteGameControllerMappings(mappings_hash, filename);
+    checked_free(gfx_token);
+  }
 
-  freeSetupFileHash(mappings_hash);
-  free(filename);
-}
+  if (result == -1)
+  {
+    Key key = getKeyFromX11KeyName(token);
 
-void LoadCustomElementDescriptions(void)
-{
-  char *filename = getCustomArtworkConfigFilename(ARTWORK_TYPE_GRAPHICS);
-  SetupFileHash *setup_file_hash;
-  int i;
+    if (key != KSYM_UNDEFINED)
+      result = -(int)key;
+  }
 
-  for (i = 0; i < NUM_FILE_ELEMENTS; i++)
+  if (result == -1)
   {
-    if (element_info[i].custom_description != NULL)
+    if (isURL(token))
     {
-      free(element_info[i].custom_description);
-      element_info[i].custom_description = NULL;
+      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 ((setup_file_hash = loadSetupFileHash(filename)) == NULL)
-    return;
+  if (result == -1)
+    result = ANIM_EVENT_ACTION_NONE;
 
-  for (i = 0; i < NUM_FILE_ELEMENTS; i++)
+  return result;
+}
+
+int get_parameter_value(char *value_raw, char *suffix, int type)
+{
+  char *value = getStringToLower(value_raw);
+  int result = 0;      // probably a save default value
+
+  if (strEqual(suffix, ".direction"))
   {
-    char *token = getStringCat2(element_info[i].token_name, ".name");
-    char *value = getHashEntry(setup_file_hash, token);
+    result = (strEqual(value, "left")  ? MV_LEFT :
+             strEqual(value, "right") ? MV_RIGHT :
+             strEqual(value, "up")    ? MV_UP :
+             strEqual(value, "down")  ? MV_DOWN : MV_NONE);
+  }
+  else if (strEqual(suffix, ".position"))
+  {
+    result = (strEqual(value, "left")   ? POS_LEFT :
+             strEqual(value, "right")  ? POS_RIGHT :
+             strEqual(value, "top")    ? POS_TOP :
+             strEqual(value, "upper")  ? POS_UPPER :
+             strEqual(value, "middle") ? POS_MIDDLE :
+             strEqual(value, "lower")  ? POS_LOWER :
+             strEqual(value, "bottom") ? POS_BOTTOM :
+             strEqual(value, "any")    ? POS_ANY :
+             strEqual(value, "ce")     ? POS_CE :
+             strEqual(value, "last")   ? POS_LAST : POS_UNDEFINED);
+  }
+  else if (strEqual(suffix, ".align"))
+  {
+    result = (strEqual(value, "left")   ? ALIGN_LEFT :
+             strEqual(value, "right")  ? ALIGN_RIGHT :
+             strEqual(value, "center") ? ALIGN_CENTER :
+             strEqual(value, "middle") ? ALIGN_CENTER : ALIGN_DEFAULT);
+  }
+  else if (strEqual(suffix, ".valign"))
+  {
+    result = (strEqual(value, "top")    ? VALIGN_TOP :
+             strEqual(value, "bottom") ? VALIGN_BOTTOM :
+             strEqual(value, "middle") ? VALIGN_MIDDLE :
+             strEqual(value, "center") ? VALIGN_MIDDLE : VALIGN_DEFAULT);
+  }
+  else if (strEqual(suffix, ".anim_mode"))
+  {
+    result = (string_has_parameter(value, "none")      ? ANIM_NONE :
+             string_has_parameter(value, "loop")       ? ANIM_LOOP :
+             string_has_parameter(value, "linear")     ? ANIM_LINEAR :
+             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 :
+             string_has_parameter(value, "horizontal") ? ANIM_HORIZONTAL :
+             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 (value != NULL)
-      element_info[i].custom_description = getStringCopy(value);
+    if (string_has_parameter(value, "once"))
+      result |= ANIM_ONCE;
 
-    free(token);
+    if (string_has_parameter(value, "reverse"))
+      result |= ANIM_REVERSE;
+
+    if (string_has_parameter(value, "opaque_player"))
+      result |= ANIM_OPAQUE_PLAYER;
+
+    if (string_has_parameter(value, "static_panel"))
+      result |= ANIM_STATIC_PANEL;
+  }
+  else if (strEqual(suffix, ".init_event") ||
+          strEqual(suffix, ".anim_event"))
+  {
+    result = get_anim_parameter_values(value);
+  }
+  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);
   }
+  else if (strEqual(suffix, ".class"))
+  {
+    result = (strEqual(value, ARG_UNDEFINED) ? ARG_UNDEFINED_VALUE :
+             get_hash_from_key(value));
+  }
+  else if (strEqual(suffix, ".style"))
+  {
+    result = STYLE_DEFAULT;
 
-  freeSetupFileHash(setup_file_hash);
-}
+    if (string_has_parameter(value, "accurate_borders"))
+      result |= STYLE_ACCURATE_BORDERS;
 
-static int getElementFromToken(char *token)
-{
-  char *value = getHashEntry(element_token_hash, token);
+    if (string_has_parameter(value, "inner_corners"))
+      result |= STYLE_INNER_CORNERS;
 
-  if (value != NULL)
-    return atoi(value);
+    if (string_has_parameter(value, "reverse"))
+      result |= STYLE_REVERSE;
 
-  Error(ERR_WARN, "unknown element token '%s'", token);
+    if (string_has_parameter(value, "leftmost_position"))
+      result |= STYLE_LEFTMOST_POSITION;
 
-  return EL_UNDEFINED;
+    if (string_has_parameter(value, "block_clicks"))
+      result |= STYLE_BLOCK;
+
+    if (string_has_parameter(value, "passthrough_clicks"))
+      result |= STYLE_PASSTHROUGH;
+
+    if (string_has_parameter(value, "multiple_actions"))
+      result |= STYLE_MULTIPLE_ACTIONS;
+
+    if (string_has_parameter(value, "consume_ce_event"))
+      result |= STYLE_CONSUME_CE_EVENT;
+  }
+  else if (strEqual(suffix, ".fade_mode"))
+  {
+    result = (string_has_parameter(value, "none")      ? FADE_MODE_NONE :
+             string_has_parameter(value, "fade")       ? FADE_MODE_FADE :
+             string_has_parameter(value, "crossfade")  ? FADE_MODE_CROSSFADE :
+             string_has_parameter(value, "melt")       ? FADE_MODE_MELT :
+             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);
+  }
+  else         // generic parameter of type integer or boolean
+  {
+    result = (strEqual(value, ARG_UNDEFINED) ? ARG_UNDEFINED_VALUE :
+             type == TYPE_INTEGER ? get_integer_from_string(value) :
+             type == TYPE_BOOLEAN ? get_boolean_from_string(value) :
+             ARG_UNDEFINED_VALUE);
+  }
+
+  free(value);
+
+  return result;
 }
 
 static int get_token_parameter_value(char *token, char *value_raw)
@@ -9703,14 +11969,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 =
@@ -9718,6 +11988,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;
@@ -9734,10 +12010,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 =
@@ -9746,27 +12026,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")
@@ -9795,48 +12084,279 @@ static void InitMenuDesignSettings_SpecialPreProcessing(void)
     menu.next_screen[i]  = menu.next_screen[GFX_SPECIAL_ARG_DEFAULT];
   }
 
-  // special case: initialize "ARG_DEFAULT" values in static default config
-  // (eg, init "viewport.door_1.MAIN.xyz" from "viewport.door_1.xyz")
-  for (i = 0; i < NUM_SPECIAL_GFX_ARGS; i++)
-  {
-    viewport.window[i]    = viewport.window[GFX_SPECIAL_ARG_DEFAULT];
-    viewport.playfield[i] = viewport.playfield[GFX_SPECIAL_ARG_DEFAULT];
-    viewport.door_1[i]    = viewport.door_1[GFX_SPECIAL_ARG_DEFAULT];
+  // special case: initialize "ARG_DEFAULT" values in static default config
+  // (eg, init "viewport.door_1.MAIN.xyz" from "viewport.door_1.xyz")
+  for (i = 0; i < NUM_SPECIAL_GFX_ARGS; i++)
+  {
+    viewport.window[i]    = viewport.window[GFX_SPECIAL_ARG_DEFAULT];
+    viewport.playfield[i] = viewport.playfield[GFX_SPECIAL_ARG_DEFAULT];
+    viewport.door_1[i]    = viewport.door_1[GFX_SPECIAL_ARG_DEFAULT];
+
+    if (i == GFX_SPECIAL_ARG_EDITOR)   // editor values already initialized
+      continue;
+
+    viewport.door_2[i] = viewport.door_2[GFX_SPECIAL_ARG_DEFAULT];
+  }
+}
+
+static void InitMenuDesignSettings_SpecialPostProcessing(void)
+{
+  static struct
+  {
+    struct XY *dst, *src;
+  }
+  game_buttons_xy[] =
+  {
+    { &game.button.save,       &game.button.stop       },
+    { &game.button.pause2,     &game.button.pause      },
+    { &game.button.load,       &game.button.play       },
+    { &game.button.undo,       &game.button.stop       },
+    { &game.button.redo,       &game.button.play       },
+
+    { NULL,                    NULL                    }
+  };
+  int i, j;
+
+  // special case: initialize later added SETUP list size from LEVELS value
+  if (menu.list_size[GAME_MODE_SETUP] == -1)
+    menu.list_size[GAME_MODE_SETUP] = menu.list_size[GAME_MODE_LEVELS];
+
+  // set default position for snapshot buttons to stop/pause/play buttons
+  for (i = 0; game_buttons_xy[i].dst != NULL; i++)
+    if ((*game_buttons_xy[i].dst).x == -1 &&
+       (*game_buttons_xy[i].dst).y == -1)
+      *game_buttons_xy[i].dst = *game_buttons_xy[i].src;
+
+  // --------------------------------------------------------------------------
+  // dynamic viewports (including playfield margins, borders and alignments)
+  // --------------------------------------------------------------------------
+
+  // dynamic viewports currently only supported for landscape mode
+  int display_width  = MAX(video.display_width, video.display_height);
+  int display_height = MIN(video.display_width, video.display_height);
+
+  for (i = 0; i < NUM_SPECIAL_GFX_ARGS; i++)
+  {
+    struct RectWithBorder *vp_window    = &viewport.window[i];
+    struct RectWithBorder *vp_playfield = &viewport.playfield[i];
+    struct RectWithBorder *vp_door_1    = &viewport.door_1[i];
+    struct RectWithBorder *vp_door_2    = &viewport.door_2[i];
+    boolean dynamic_window_width     = (vp_window->min_width     != -1);
+    boolean dynamic_window_height    = (vp_window->min_height    != -1);
+    boolean dynamic_playfield_width  = (vp_playfield->min_width  != -1);
+    boolean dynamic_playfield_height = (vp_playfield->min_height != -1);
+
+    // adjust window size if min/max width/height is specified
+
+    if (vp_window->min_width != -1)
+    {
+      int window_width = display_width;
+
+      // when using static window height, use aspect ratio of display
+      if (vp_window->min_height == -1)
+       window_width = vp_window->height * display_width / display_height;
+
+      vp_window->width = MAX(vp_window->min_width, window_width);
+    }
+
+    if (vp_window->min_height != -1)
+    {
+      int window_height = display_height;
+
+      // when using static window width, use aspect ratio of display
+      if (vp_window->min_width == -1)
+       window_height = vp_window->width * display_height / display_width;
+
+      vp_window->height = MAX(vp_window->min_height, window_height);
+    }
+
+    if (vp_window->max_width != -1)
+      vp_window->width = MIN(vp_window->width, vp_window->max_width);
+
+    if (vp_window->max_height != -1)
+      vp_window->height = MIN(vp_window->height, vp_window->max_height);
+
+    int playfield_width  = vp_window->width;
+    int playfield_height = vp_window->height;
+
+    // adjust playfield size and position according to specified margins
+
+    playfield_width  -= vp_playfield->margin_left;
+    playfield_width  -= vp_playfield->margin_right;
+
+    playfield_height -= vp_playfield->margin_top;
+    playfield_height -= vp_playfield->margin_bottom;
+
+    // adjust playfield size if min/max width/height is specified
+
+    if (vp_playfield->min_width != -1)
+      vp_playfield->width = MAX(vp_playfield->min_width, playfield_width);
+
+    if (vp_playfield->min_height != -1)
+      vp_playfield->height = MAX(vp_playfield->min_height, playfield_height);
+
+    if (vp_playfield->max_width != -1)
+      vp_playfield->width = MIN(vp_playfield->width, vp_playfield->max_width);
+
+    if (vp_playfield->max_height != -1)
+      vp_playfield->height = MIN(vp_playfield->height, vp_playfield->max_height);
+
+    // adjust playfield position according to specified alignment
+
+    if (vp_playfield->align == ALIGN_LEFT || vp_playfield->x > 0)
+      vp_playfield->x = ALIGNED_VP_XPOS(vp_playfield);
+    else if (vp_playfield->align == ALIGN_CENTER)
+      vp_playfield->x = playfield_width / 2 - vp_playfield->width / 2;
+    else if (vp_playfield->align == ALIGN_RIGHT)
+      vp_playfield->x += playfield_width - vp_playfield->width;
+
+    if (vp_playfield->valign == VALIGN_TOP || vp_playfield->y > 0)
+      vp_playfield->y = ALIGNED_VP_YPOS(vp_playfield);
+    else if (vp_playfield->valign == VALIGN_MIDDLE)
+      vp_playfield->y = playfield_height / 2 - vp_playfield->height / 2;
+    else if (vp_playfield->valign == VALIGN_BOTTOM)
+      vp_playfield->y += playfield_height - vp_playfield->height;
+
+    vp_playfield->x += vp_playfield->margin_left;
+    vp_playfield->y += vp_playfield->margin_top;
+
+    // adjust individual playfield borders if only default border is specified
+
+    if (vp_playfield->border_left == -1)
+      vp_playfield->border_left = vp_playfield->border_size;
+    if (vp_playfield->border_right == -1)
+      vp_playfield->border_right = vp_playfield->border_size;
+    if (vp_playfield->border_top == -1)
+      vp_playfield->border_top = vp_playfield->border_size;
+    if (vp_playfield->border_bottom == -1)
+      vp_playfield->border_bottom = vp_playfield->border_size;
+
+    // set dynamic playfield borders if borders are specified as undefined
+    // (but only if window size was dynamic and playfield size was static)
+
+    if (dynamic_window_width && !dynamic_playfield_width)
+    {
+      if (vp_playfield->border_left == -1)
+      {
+       vp_playfield->border_left = (vp_playfield->x -
+                                    vp_playfield->margin_left);
+       vp_playfield->x     -= vp_playfield->border_left;
+       vp_playfield->width += vp_playfield->border_left;
+      }
+
+      if (vp_playfield->border_right == -1)
+      {
+       vp_playfield->border_right = (vp_window->width -
+                                     vp_playfield->x -
+                                     vp_playfield->width -
+                                     vp_playfield->margin_right);
+       vp_playfield->width += vp_playfield->border_right;
+      }
+    }
+
+    if (dynamic_window_height && !dynamic_playfield_height)
+    {
+      if (vp_playfield->border_top == -1)
+      {
+       vp_playfield->border_top = (vp_playfield->y -
+                                   vp_playfield->margin_top);
+       vp_playfield->y      -= vp_playfield->border_top;
+       vp_playfield->height += vp_playfield->border_top;
+      }
+
+      if (vp_playfield->border_bottom == -1)
+      {
+       vp_playfield->border_bottom = (vp_window->height -
+                                      vp_playfield->y -
+                                      vp_playfield->height -
+                                      vp_playfield->margin_bottom);
+       vp_playfield->height += vp_playfield->border_bottom;
+      }
+    }
+
+    // adjust playfield size to be a multiple of a defined alignment tile size
+
+    int align_size = vp_playfield->align_size;
+    int playfield_xtiles = vp_playfield->width  / align_size;
+    int playfield_ytiles = vp_playfield->height / align_size;
+    int playfield_width_corrected  = playfield_xtiles * align_size;
+    int playfield_height_corrected = playfield_ytiles * align_size;
+    boolean is_playfield_mode = (i == GFX_SPECIAL_ARG_PLAYING ||
+                                i == GFX_SPECIAL_ARG_EDITOR);
+
+    if (is_playfield_mode &&
+       dynamic_playfield_width &&
+       vp_playfield->width != playfield_width_corrected)
+    {
+      int playfield_xdiff = vp_playfield->width - playfield_width_corrected;
+
+      vp_playfield->width = playfield_width_corrected;
+
+      if (vp_playfield->align == ALIGN_LEFT)
+      {
+       vp_playfield->border_left += playfield_xdiff;
+      }
+      else if (vp_playfield->align == ALIGN_RIGHT)
+      {
+       vp_playfield->border_right += playfield_xdiff;
+      }
+      else if (vp_playfield->align == ALIGN_CENTER)
+      {
+       int border_left_diff  = playfield_xdiff / 2;
+       int border_right_diff = playfield_xdiff - border_left_diff;
+
+       vp_playfield->border_left  += border_left_diff;
+       vp_playfield->border_right += border_right_diff;
+      }
+    }
 
-    if (i == GFX_SPECIAL_ARG_EDITOR)   // editor values already initialized
-      continue;
+    if (is_playfield_mode &&
+       dynamic_playfield_height &&
+       vp_playfield->height != playfield_height_corrected)
+    {
+      int playfield_ydiff = vp_playfield->height - playfield_height_corrected;
 
-    viewport.door_2[i] = viewport.door_2[GFX_SPECIAL_ARG_DEFAULT];
-  }
-}
+      vp_playfield->height = playfield_height_corrected;
 
-static void InitMenuDesignSettings_SpecialPostProcessing(void)
-{
-  static struct
-  {
-    struct XY *dst, *src;
-  }
-  game_buttons_xy[] =
-  {
-    { &game.button.save,       &game.button.stop       },
-    { &game.button.pause2,     &game.button.pause      },
-    { &game.button.load,       &game.button.play       },
-    { &game.button.undo,       &game.button.stop       },
-    { &game.button.redo,       &game.button.play       },
+      if (vp_playfield->valign == VALIGN_TOP)
+      {
+       vp_playfield->border_top += playfield_ydiff;
+      }
+      else if (vp_playfield->align == VALIGN_BOTTOM)
+      {
+       vp_playfield->border_right += playfield_ydiff;
+      }
+      else if (vp_playfield->align == VALIGN_MIDDLE)
+      {
+       int border_top_diff    = playfield_ydiff / 2;
+       int border_bottom_diff = playfield_ydiff - border_top_diff;
 
-    { NULL,                    NULL                    }
-  };
-  int i;
+       vp_playfield->border_top    += border_top_diff;
+       vp_playfield->border_bottom += border_bottom_diff;
+      }
+    }
 
-  // special case: initialize later added SETUP list size from LEVELS value
-  if (menu.list_size[GAME_MODE_SETUP] == -1)
-    menu.list_size[GAME_MODE_SETUP] = menu.list_size[GAME_MODE_LEVELS];
+    // adjust door positions according to specified alignment
 
-  // set default position for snapshot buttons to stop/pause/play buttons
-  for (i = 0; game_buttons_xy[i].dst != NULL; i++)
-    if ((*game_buttons_xy[i].dst).x == -1 &&
-       (*game_buttons_xy[i].dst).y == -1)
-      *game_buttons_xy[i].dst = *game_buttons_xy[i].src;
+    for (j = 0; j < 2; j++)
+    {
+      struct RectWithBorder *vp_door = (j == 0 ? vp_door_1 : vp_door_2);
+
+      if (vp_door->align == ALIGN_LEFT || vp_door->x > 0)
+       vp_door->x = ALIGNED_VP_XPOS(vp_door);
+      else if (vp_door->align == ALIGN_CENTER)
+       vp_door->x = vp_window->width / 2 - vp_door->width / 2;
+      else if (vp_door->align == ALIGN_RIGHT)
+       vp_door->x += vp_window->width - vp_door->width;
+
+      if (vp_door->valign == VALIGN_TOP || vp_door->y > 0)
+       vp_door->y = ALIGNED_VP_YPOS(vp_door);
+      else if (vp_door->valign == VALIGN_MIDDLE)
+       vp_door->y = vp_window->height / 2 - vp_door->height / 2;
+      else if (vp_door->valign == VALIGN_BOTTOM)
+       vp_door->y += vp_window->height - vp_door->height;
+    }
+  }
 }
 
 static void InitMenuDesignSettings_SpecialPostProcessing_AfterGraphics(void)
@@ -9878,6 +12398,79 @@ static void InitMenuDesignSettings_SpecialPostProcessing_AfterGraphics(void)
       *editor_buttons_xy[i].dst = *editor_buttons_xy[i].src;
     }
   }
+
+  // adjust editor palette rows and columns if specified to be dynamic
+
+  if (editor.palette.cols == -1)
+  {
+    int vp_width = viewport.playfield[GFX_SPECIAL_ARG_EDITOR].width;
+    int bt_width = graphic_info[IMG_EDITOR_PALETTE_BUTTON].width;
+    int sc_width = graphic_info[IMG_EDITOR_PALETTE_SCROLLBAR].width;
+
+    editor.palette.cols = (vp_width - sc_width) / bt_width;
+
+    if (editor.palette.x == -1)
+    {
+      int palette_width = editor.palette.cols * bt_width + sc_width;
+
+      editor.palette.x = (vp_width - palette_width) / 2;
+    }
+  }
+
+  if (editor.palette.rows == -1)
+  {
+    int vp_height = viewport.playfield[GFX_SPECIAL_ARG_EDITOR].height;
+    int bt_height = graphic_info[IMG_EDITOR_PALETTE_BUTTON].height;
+    int tx_height = getFontHeight(FONT_TEXT_2);
+
+    editor.palette.rows = (vp_height - tx_height) / bt_height;
+
+    if (editor.palette.y == -1)
+    {
+      int palette_height = editor.palette.rows * bt_height + tx_height;
+
+      editor.palette.y = (vp_height - palette_height) / 2;
+    }
+  }
+}
+
+static void InitMenuDesignSettings_PreviewPlayers_Ext(SetupFileHash *hash,
+                                                      boolean initialize)
+{
+  // special case: check if network and preview player positions are redefined,
+  // to compare this later against the main menu level preview being redefined
+  struct TokenIntPtrInfo menu_config_players[] =
+  {
+    { "main.network_players.x",        &menu.main.network_players.redefined    },
+    { "main.network_players.y",        &menu.main.network_players.redefined    },
+    { "main.preview_players.x",        &menu.main.preview_players.redefined    },
+    { "main.preview_players.y",        &menu.main.preview_players.redefined    },
+    { "preview.x",             &preview.redefined                      },
+    { "preview.y",             &preview.redefined                      }
+  };
+  int i;
+
+  if (initialize)
+  {
+    for (i = 0; i < ARRAY_SIZE(menu_config_players); i++)
+      *menu_config_players[i].value = FALSE;
+  }
+  else
+  {
+    for (i = 0; i < ARRAY_SIZE(menu_config_players); i++)
+      if (getHashEntry(hash, menu_config_players[i].token) != NULL)
+        *menu_config_players[i].value = TRUE;
+  }
+}
+
+static void InitMenuDesignSettings_PreviewPlayers(void)
+{
+  InitMenuDesignSettings_PreviewPlayers_Ext(NULL, TRUE);
+}
+
+static void InitMenuDesignSettings_PreviewPlayers_FromHash(SetupFileHash *hash)
+{
+  InitMenuDesignSettings_PreviewPlayers_Ext(hash, FALSE);
 }
 
 static void LoadMenuDesignSettingsFromFilename(char *filename)
@@ -9890,6 +12483,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                    }
   };
@@ -9912,6 +12506,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                    }
   };
@@ -9987,36 +12582,43 @@ static void LoadMenuDesignSettingsFromFilename(char *filename)
   // (e.g., init "menu.draw_xoffset.INFO" from "menu.draw_xoffset")
   for (i = 0; i < NUM_SPECIAL_GFX_ARGS; i++)
   {
-    char *value_1 = getHashEntry(setup_file_hash, "menu.draw_xoffset");
-    char *value_2 = getHashEntry(setup_file_hash, "menu.draw_yoffset");
-    char *value_3 = getHashEntry(setup_file_hash, "menu.list_size");
+    struct TokenIntPtrInfo menu_config[] =
+    {
+      { "menu.draw_xoffset",   &menu.draw_xoffset[i]   },
+      { "menu.draw_yoffset",   &menu.draw_yoffset[i]   },
+      { "menu.list_size",      &menu.list_size[i]      }
+    };
+
+    for (j = 0; j < ARRAY_SIZE(menu_config); j++)
+    {
+      char *token = menu_config[j].token;
+      char *value = getHashEntry(setup_file_hash, token);
 
-    if (value_1 != NULL)
-      menu.draw_xoffset[i] = get_integer_from_string(value_1);
-    if (value_2 != NULL)
-      menu.draw_yoffset[i] = get_integer_from_string(value_2);
-    if (value_3 != NULL)
-      menu.list_size[i] = get_integer_from_string(value_3);
+      if (value != NULL)
+        *menu_config[j].value = get_integer_from_string(value);
+    }
   }
 
   // special case: initialize with default values that may be overwritten
   // (eg, init "menu.draw_xoffset.INFO[XXX]" from "menu.draw_xoffset.INFO")
   for (i = 0; i < NUM_SPECIAL_GFX_INFO_ARGS; i++)
   {
-    char *value_1 = getHashEntry(setup_file_hash, "menu.draw_xoffset.INFO");
-    char *value_2 = getHashEntry(setup_file_hash, "menu.draw_yoffset.INFO");
-
-    if (value_1 != NULL)
-      menu.draw_xoffset_info[i] = get_integer_from_string(value_1);
-    if (value_2 != NULL)
-      menu.draw_yoffset_info[i] = get_integer_from_string(value_2);
+    struct TokenIntPtrInfo menu_config[] =
+    {
+      { "menu.draw_xoffset.INFO",      &menu.draw_xoffset_info[i]      },
+      { "menu.draw_yoffset.INFO",      &menu.draw_yoffset_info[i]      },
+      { "menu.list_size.INFO",         &menu.list_size_info[i]         },
+      { "menu.list_entry_size.INFO",   &menu.list_entry_size_info[i]   },
+      { "menu.tile_size.INFO",         &menu.tile_size_info[i]         }
+    };
 
-    if (i == GFX_SPECIAL_ARG_INFO_ELEMENTS)
+    for (j = 0; j < ARRAY_SIZE(menu_config); j++)
     {
-      char *value_1 = getHashEntry(setup_file_hash, "menu.list_size.INFO");
+      char *token = menu_config[j].token;
+      char *value = getHashEntry(setup_file_hash, token);
 
-      if (value_1 != NULL)
-       menu.list_size_info[i] = get_integer_from_string(value_1);
+      if (value != NULL)
+        *menu_config[j].value = get_integer_from_string(value);
     }
   }
 
@@ -10024,179 +12626,132 @@ static void LoadMenuDesignSettingsFromFilename(char *filename)
   // (eg, init "menu.draw_xoffset.SETUP[XXX]" from "menu.draw_xoffset.SETUP")
   for (i = 0; i < NUM_SPECIAL_GFX_SETUP_ARGS; i++)
   {
-    char *value_1 = getHashEntry(setup_file_hash, "menu.draw_xoffset.SETUP");
-    char *value_2 = getHashEntry(setup_file_hash, "menu.draw_yoffset.SETUP");
+    struct TokenIntPtrInfo menu_config[] =
+    {
+      { "menu.draw_xoffset.SETUP",     &menu.draw_xoffset_setup[i]     },
+      { "menu.draw_yoffset.SETUP",     &menu.draw_yoffset_setup[i]     }
+    };
+
+    for (j = 0; j < ARRAY_SIZE(menu_config); j++)
+    {
+      char *token = menu_config[j].token;
+      char *value = getHashEntry(setup_file_hash, token);
 
-    if (value_1 != NULL)
-      menu.draw_xoffset_setup[i] = get_integer_from_string(value_1);
-    if (value_2 != NULL)
-      menu.draw_yoffset_setup[i] = get_integer_from_string(value_2);
+      if (value != NULL)
+        *menu_config[j].value = get_integer_from_string(value);
+    }
   }
 
   // special case: initialize with default values that may be overwritten
   // (eg, init "menu.line_spacing.INFO[XXX]" from "menu.line_spacing.INFO")
   for (i = 0; i < NUM_SPECIAL_GFX_INFO_ARGS; i++)
   {
-    char *value_1 = getHashEntry(setup_file_hash,"menu.left_spacing.INFO");
-    char *value_2 = getHashEntry(setup_file_hash,"menu.right_spacing.INFO");
-    char *value_3 = getHashEntry(setup_file_hash,"menu.top_spacing.INFO");
-    char *value_4 = getHashEntry(setup_file_hash,"menu.bottom_spacing.INFO");
-    char *value_5 = getHashEntry(setup_file_hash,"menu.paragraph_spacing.INFO");
-    char *value_6 = getHashEntry(setup_file_hash,"menu.headline1_spacing.INFO");
-    char *value_7 = getHashEntry(setup_file_hash,"menu.headline2_spacing.INFO");
-    char *value_8 = getHashEntry(setup_file_hash,"menu.line_spacing.INFO");
-    char *value_9 = getHashEntry(setup_file_hash,"menu.extra_spacing.INFO");
-
-    if (value_1 != NULL)
-      menu.left_spacing_info[i]      = get_integer_from_string(value_1);
-    if (value_2 != NULL)
-      menu.right_spacing_info[i]     = get_integer_from_string(value_2);
-    if (value_3 != NULL)
-      menu.top_spacing_info[i]       = get_integer_from_string(value_3);
-    if (value_4 != NULL)
-      menu.bottom_spacing_info[i]    = get_integer_from_string(value_4);
-    if (value_5 != NULL)
-      menu.paragraph_spacing_info[i] = get_integer_from_string(value_5);
-    if (value_6 != NULL)
-      menu.headline1_spacing_info[i] = get_integer_from_string(value_6);
-    if (value_7 != NULL)
-      menu.headline2_spacing_info[i] = get_integer_from_string(value_7);
-    if (value_8 != NULL)
-      menu.line_spacing_info[i]      = get_integer_from_string(value_8);
-    if (value_9 != NULL)
-      menu.extra_spacing_info[i]     = get_integer_from_string(value_9);
+    struct TokenIntPtrInfo menu_config[] =
+    {
+      { "menu.left_spacing.INFO",      &menu.left_spacing_info[i]      },
+      { "menu.middle_spacing.INFO",    &menu.middle_spacing_info[i]    },
+      { "menu.right_spacing.INFO",     &menu.right_spacing_info[i]     },
+      { "menu.top_spacing.INFO",       &menu.top_spacing_info[i]       },
+      { "menu.bottom_spacing.INFO",    &menu.bottom_spacing_info[i]    },
+      { "menu.paragraph_spacing.INFO", &menu.paragraph_spacing_info[i] },
+      { "menu.headline1_spacing.INFO", &menu.headline1_spacing_info[i] },
+      { "menu.headline2_spacing.INFO", &menu.headline2_spacing_info[i] },
+      { "menu.line_spacing.INFO",      &menu.line_spacing_info[i]      },
+      { "menu.extra_spacing.INFO",     &menu.extra_spacing_info[i]     },
+    };
+
+    for (j = 0; j < ARRAY_SIZE(menu_config); j++)
+    {
+      char *token = menu_config[j].token;
+      char *value = getHashEntry(setup_file_hash, token);
+
+      if (value != NULL)
+        *menu_config[j].value = get_integer_from_string(value);
+    }
   }
 
   // special case: initialize with default values that may be overwritten
   // (eg, init "menu.enter_screen.SCORES.xyz" from "menu.enter_screen.xyz")
   for (i = 0; i < NUM_SPECIAL_GFX_ARGS; i++)
   {
-    char *token_1 = "menu.enter_screen.fade_mode";
-    char *token_2 = "menu.enter_screen.fade_delay";
-    char *token_3 = "menu.enter_screen.post_delay";
-    char *token_4 = "menu.leave_screen.fade_mode";
-    char *token_5 = "menu.leave_screen.fade_delay";
-    char *token_6 = "menu.leave_screen.post_delay";
-    char *token_7 = "menu.next_screen.fade_mode";
-    char *token_8 = "menu.next_screen.fade_delay";
-    char *token_9 = "menu.next_screen.post_delay";
-    char *value_1 = getHashEntry(setup_file_hash, token_1);
-    char *value_2 = getHashEntry(setup_file_hash, token_2);
-    char *value_3 = getHashEntry(setup_file_hash, token_3);
-    char *value_4 = getHashEntry(setup_file_hash, token_4);
-    char *value_5 = getHashEntry(setup_file_hash, token_5);
-    char *value_6 = getHashEntry(setup_file_hash, token_6);
-    char *value_7 = getHashEntry(setup_file_hash, token_7);
-    char *value_8 = getHashEntry(setup_file_hash, token_8);
-    char *value_9 = getHashEntry(setup_file_hash, token_9);
-
-    if (value_1 != NULL)
-      menu.enter_screen[i].fade_mode = get_token_parameter_value(token_1,
-                                                                value_1);
-    if (value_2 != NULL)
-      menu.enter_screen[i].fade_delay = get_token_parameter_value(token_2,
-                                                                 value_2);
-    if (value_3 != NULL)
-      menu.enter_screen[i].post_delay = get_token_parameter_value(token_3,
-                                                                 value_3);
-    if (value_4 != NULL)
-      menu.leave_screen[i].fade_mode = get_token_parameter_value(token_4,
-                                                                value_4);
-    if (value_5 != NULL)
-      menu.leave_screen[i].fade_delay = get_token_parameter_value(token_5,
-                                                                 value_5);
-    if (value_6 != NULL)
-      menu.leave_screen[i].post_delay = get_token_parameter_value(token_6,
-                                                                 value_6);
-    if (value_7 != NULL)
-      menu.next_screen[i].fade_mode = get_token_parameter_value(token_7,
-                                                               value_7);
-    if (value_8 != NULL)
-      menu.next_screen[i].fade_delay = get_token_parameter_value(token_8,
-                                                                value_8);
-    if (value_9 != NULL)
-      menu.next_screen[i].post_delay = get_token_parameter_value(token_9,
-                                                                value_9);
+    struct TokenIntPtrInfo menu_config[] =
+    {
+      { "menu.enter_screen.fade_mode", &menu.enter_screen[i].fade_mode  },
+      { "menu.enter_screen.fade_delay",        &menu.enter_screen[i].fade_delay },
+      { "menu.enter_screen.post_delay",        &menu.enter_screen[i].post_delay },
+      { "menu.leave_screen.fade_mode", &menu.leave_screen[i].fade_mode  },
+      { "menu.leave_screen.fade_delay",        &menu.leave_screen[i].fade_delay },
+      { "menu.leave_screen.post_delay",        &menu.leave_screen[i].post_delay },
+      { "menu.next_screen.fade_mode",  &menu.next_screen[i].fade_mode   },
+      { "menu.next_screen.fade_delay", &menu.next_screen[i].fade_delay  },
+      { "menu.next_screen.post_delay", &menu.next_screen[i].post_delay  }
+    };
+
+    for (j = 0; j < ARRAY_SIZE(menu_config); j++)
+    {
+      char *token = menu_config[j].token;
+      char *value = getHashEntry(setup_file_hash, token);
+
+      if (value != NULL)
+        *menu_config[j].value = get_token_parameter_value(token, value);
+    }
   }
 
   // special case: initialize with default values that may be overwritten
   // (eg, init "viewport.door_1.MAIN.xyz" from "viewport.door_1.xyz")
   for (i = 0; i < NUM_SPECIAL_GFX_ARGS; i++)
   {
-    char *token_w1 = "viewport.window.width";
-    char *token_w2 = "viewport.window.height";
-    char *token_01 = "viewport.playfield.x";
-    char *token_02 = "viewport.playfield.y";
-    char *token_03 = "viewport.playfield.width";
-    char *token_04 = "viewport.playfield.height";
-    char *token_05 = "viewport.playfield.border_size";
-    char *token_06 = "viewport.door_1.x";
-    char *token_07 = "viewport.door_1.y";
-    char *token_08 = "viewport.door_1.width";
-    char *token_09 = "viewport.door_1.height";
-    char *token_10 = "viewport.door_1.border_size";
-    char *token_11 = "viewport.door_2.x";
-    char *token_12 = "viewport.door_2.y";
-    char *token_13 = "viewport.door_2.width";
-    char *token_14 = "viewport.door_2.height";
-    char *token_15 = "viewport.door_2.border_size";
-    char *value_w1 = getHashEntry(setup_file_hash, token_w1);
-    char *value_w2 = getHashEntry(setup_file_hash, token_w2);
-    char *value_01 = getHashEntry(setup_file_hash, token_01);
-    char *value_02 = getHashEntry(setup_file_hash, token_02);
-    char *value_03 = getHashEntry(setup_file_hash, token_03);
-    char *value_04 = getHashEntry(setup_file_hash, token_04);
-    char *value_05 = getHashEntry(setup_file_hash, token_05);
-    char *value_06 = getHashEntry(setup_file_hash, token_06);
-    char *value_07 = getHashEntry(setup_file_hash, token_07);
-    char *value_08 = getHashEntry(setup_file_hash, token_08);
-    char *value_09 = getHashEntry(setup_file_hash, token_09);
-    char *value_10 = getHashEntry(setup_file_hash, token_10);
-    char *value_11 = getHashEntry(setup_file_hash, token_11);
-    char *value_12 = getHashEntry(setup_file_hash, token_12);
-    char *value_13 = getHashEntry(setup_file_hash, token_13);
-    char *value_14 = getHashEntry(setup_file_hash, token_14);
-    char *value_15 = getHashEntry(setup_file_hash, token_15);
-
-    if (value_w1 != NULL)
-      viewport.window[i].width = get_token_parameter_value(token_w1, value_w1);
-    if (value_w2 != NULL)
-      viewport.window[i].height = get_token_parameter_value(token_w2, value_w2);
-    if (value_01 != NULL)
-      viewport.playfield[i].x = get_token_parameter_value(token_01, value_01);
-    if (value_02 != NULL)
-      viewport.playfield[i].y = get_token_parameter_value(token_02, value_02);
-    if (value_03 != NULL)
-      viewport.playfield[i].width = get_token_parameter_value(token_03,
-                                                             value_03);
-    if (value_04 != NULL)
-      viewport.playfield[i].height = get_token_parameter_value(token_04,
-                                                              value_04);
-    if (value_05 != NULL)
-      viewport.playfield[i].border_size = get_token_parameter_value(token_05,
-                                                                   value_05);
-    if (value_06 != NULL)
-      viewport.door_1[i].x = get_token_parameter_value(token_06, value_06);
-    if (value_07 != NULL)
-      viewport.door_1[i].y = get_token_parameter_value(token_07, value_07);
-    if (value_08 != NULL)
-      viewport.door_1[i].width = get_token_parameter_value(token_08, value_08);
-    if (value_09 != NULL)
-      viewport.door_1[i].height = get_token_parameter_value(token_09, value_09);
-    if (value_10 != NULL)
-      viewport.door_1[i].border_size = get_token_parameter_value(token_10,
-                                                                value_10);
-    if (value_11 != NULL)
-      viewport.door_2[i].x = get_token_parameter_value(token_11, value_11);
-    if (value_12 != NULL)
-      viewport.door_2[i].y = get_token_parameter_value(token_12, value_12);
-    if (value_13 != NULL)
-      viewport.door_2[i].width = get_token_parameter_value(token_13, value_13);
-    if (value_14 != NULL)
-      viewport.door_2[i].height = get_token_parameter_value(token_14, value_14);
-    if (value_15 != NULL)
-      viewport.door_1[i].border_size = get_token_parameter_value(token_15,
-                                                                value_15);
+    struct
+    {
+      char *token_prefix;
+      struct RectWithBorder *struct_ptr;
+    }
+    vp_struct[] =
+    {
+      { "viewport.window",     &viewport.window[i]     },
+      { "viewport.playfield",  &viewport.playfield[i]  },
+      { "viewport.door_1",     &viewport.door_1[i]     },
+      { "viewport.door_2",     &viewport.door_2[i]     }
+    };
+
+    for (j = 0; j < ARRAY_SIZE(vp_struct); j++)
+    {
+      struct TokenIntPtrInfo vp_config[] =
+      {
+        { ".x",                        &vp_struct[j].struct_ptr->x             },
+        { ".y",                        &vp_struct[j].struct_ptr->y             },
+        { ".width",            &vp_struct[j].struct_ptr->width         },
+        { ".height",           &vp_struct[j].struct_ptr->height        },
+        { ".min_width",                &vp_struct[j].struct_ptr->min_width     },
+        { ".min_height",       &vp_struct[j].struct_ptr->min_height    },
+        { ".max_width",                &vp_struct[j].struct_ptr->max_width     },
+        { ".max_height",       &vp_struct[j].struct_ptr->max_height    },
+        { ".margin_left",      &vp_struct[j].struct_ptr->margin_left   },
+        { ".margin_right",     &vp_struct[j].struct_ptr->margin_right  },
+        { ".margin_top",       &vp_struct[j].struct_ptr->margin_top    },
+        { ".margin_bottom",    &vp_struct[j].struct_ptr->margin_bottom },
+        { ".border_left",      &vp_struct[j].struct_ptr->border_left   },
+        { ".border_right",     &vp_struct[j].struct_ptr->border_right  },
+        { ".border_top",       &vp_struct[j].struct_ptr->border_top    },
+        { ".border_bottom",    &vp_struct[j].struct_ptr->border_bottom },
+        { ".border_size",      &vp_struct[j].struct_ptr->border_size   },
+        { ".align_size",       &vp_struct[j].struct_ptr->align_size    },
+        { ".align",            &vp_struct[j].struct_ptr->align         },
+        { ".valign",           &vp_struct[j].struct_ptr->valign        }
+      };
+
+      for (k = 0; k < ARRAY_SIZE(vp_config); k++)
+      {
+        char *token = getStringCat2(vp_struct[j].token_prefix,
+                                    vp_config[k].token);
+        char *value = getHashEntry(setup_file_hash, token);
+
+        if (value != NULL)
+          *vp_config[k].value = get_token_parameter_value(token, value);
+
+        free(token);
+      }
+    }
   }
 
   // special case: initialize with default values that may be overwritten
@@ -10260,15 +12815,10 @@ static void LoadMenuDesignSettingsFromFilename(char *filename)
   }
 
   // read (and overwrite with) values that may be specified in config file
-  for (i = 0; image_config_vars[i].token != NULL; i++)
-  {
-    char *value = getHashEntry(setup_file_hash, image_config_vars[i].token);
+  InitMenuDesignSettings_FromHash(setup_file_hash, TRUE);
 
-    // (ignore definitions set to "[DEFAULT]" which are already initialized)
-    if (value != NULL && !strEqual(value, ARG_DEFAULT))
-      *image_config_vars[i].value =
-       get_token_parameter_value(image_config_vars[i].token, value);
-  }
+  // special case: check if network and preview player positions are redefined
+  InitMenuDesignSettings_PreviewPlayers_FromHash(setup_file_hash);
 
   freeSetupFileHash(setup_file_hash);
 }
@@ -10279,6 +12829,7 @@ void LoadMenuDesignSettings(void)
 
   InitMenuDesignSettings_Static();
   InitMenuDesignSettings_SpecialPreProcessing();
+  InitMenuDesignSettings_PreviewPlayers();
 
   if (!GFX_OVERRIDE_ARTWORK(ARTWORK_TYPE_GRAPHICS))
   {
@@ -10357,19 +12908,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;
@@ -10379,8 +12930,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
 }
 
@@ -10401,11 +12952,13 @@ static struct MusicFileInfo *get_music_file_info_ext(char *basename, int music,
     { "artist_header", &tmp_music_file_info.artist_header      },
     { "album_header",  &tmp_music_file_info.album_header       },
     { "year_header",   &tmp_music_file_info.year_header        },
+    { "played_header", &tmp_music_file_info.played_header      },
 
     { "title",         &tmp_music_file_info.title              },
     { "artist",                &tmp_music_file_info.artist             },
     { "album",         &tmp_music_file_info.album              },
     { "year",          &tmp_music_file_info.year               },
+    { "played",                &tmp_music_file_info.played             },
 
     { NULL,            NULL                                    },
   };
@@ -10501,14 +13054,12 @@ static boolean sound_info_listed(struct MusicFileInfo *list, char *basename)
 
 void LoadMusicInfo(void)
 {
-  char *music_directory = getCustomMusicDirectory();
+  int num_music_noconf = getMusicListSize_NoConf();
   int num_music = getMusicListSize();
-  int num_music_noconf = 0;
   int num_sounds = getSoundListSize();
-  Directory *dir;
-  DirectoryEntry *dir_entry;
   struct FileInfo *music, *sound;
   struct MusicFileInfo *next, **new;
+
   int i;
 
   while (music_file_info != NULL)
@@ -10521,11 +13072,13 @@ void LoadMusicInfo(void)
     checked_free(music_file_info->artist_header);
     checked_free(music_file_info->album_header);
     checked_free(music_file_info->year_header);
+    checked_free(music_file_info->played_header);
 
     checked_free(music_file_info->title);
     checked_free(music_file_info->artist);
     checked_free(music_file_info->album);
     checked_free(music_file_info->year);
+    checked_free(music_file_info->played);
 
     free(music_file_info);
 
@@ -10534,75 +13087,68 @@ void LoadMusicInfo(void)
 
   new = &music_file_info;
 
-  for (i = 0; i < num_music; i++)
+  // get (configured or unconfigured) music file info for all levels
+  for (i = leveldir_current->first_level;
+       i <= leveldir_current->last_level; i++)
   {
-    music = getMusicListEntry(i);
+    int music_nr;
 
-    if (music->filename == NULL)
-      continue;
+    if (levelset.music[i] != MUS_UNDEFINED)
+    {
+      // get music file info for configured level music
+      music_nr = levelset.music[i];
+    }
+    else if (num_music_noconf > 0)
+    {
+      // get music file info for unconfigured level music
+      int level_pos = i - leveldir_current->first_level;
 
-    if (strEqual(music->filename, UNDEFINED_FILENAME))
+      music_nr = MAP_NOCONF_MUSIC(level_pos % num_music_noconf);
+    }
+    else
+    {
       continue;
+    }
 
-    // a configured file may be not recognized as music
-    if (!FileIsMusic(music->filename))
+    char *basename = getMusicInfoEntryFilename(music_nr);
+
+    if (basename == NULL)
       continue;
 
-    if (!music_info_listed(music_file_info, music->filename))
+    if (!music_info_listed(music_file_info, basename))
     {
-      *new = get_music_file_info(music->filename, i);
+      *new = get_music_file_info(basename, music_nr);
 
       if (*new != NULL)
        new = &(*new)->next;
     }
   }
 
-  if ((dir = openDirectory(music_directory)) == NULL)
-  {
-    Error(ERR_WARN, "cannot read music directory '%s'", music_directory);
-    return;
-  }
-
-  while ((dir_entry = readDirectory(dir)) != NULL)     // loop all entries
+  // get music file info for all remaining configured music files
+  for (i = 0; i < num_music; i++)
   {
-    char *basename = dir_entry->basename;
-    boolean music_already_used = FALSE;
-    int i;
-
-    // skip all music files that are configured in music config file
-    for (i = 0; i < num_music; i++)
-    {
-      music = getMusicListEntry(i);
-
-      if (music->filename == NULL)
-       continue;
+    music = getMusicListEntry(i);
 
-      if (strEqual(basename, music->filename))
-      {
-       music_already_used = TRUE;
-       break;
-      }
-    }
+    if (music->filename == NULL)
+      continue;
 
-    if (music_already_used)
+    if (strEqual(music->filename, UNDEFINED_FILENAME))
       continue;
 
-    if (!FileIsMusic(dir_entry->filename))
+    // a configured file may be not recognized as music
+    if (!FileIsMusic(music->filename))
       continue;
 
-    if (!music_info_listed(music_file_info, basename))
+    if (!music_info_listed(music_file_info, music->filename))
     {
-      *new = get_music_file_info(basename, MAP_NOCONF_MUSIC(num_music_noconf));
+      *new = get_music_file_info(music->filename, i);
 
       if (*new != NULL)
        new = &(*new)->next;
     }
-
-    num_music_noconf++;
   }
 
-  closeDirectory(dir);
-
+  // get sound file info for all configured sound files
   for (i = 0; i < num_sounds; i++)
   {
     sound = getSoundListEntry(i);
@@ -10624,6 +13170,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,
@@ -10647,18 +13205,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)
@@ -10862,12 +13420,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
 }
 
@@ -10899,8 +13457,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
@@ -10926,8 +13484,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;
 
@@ -10970,6 +13527,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);
 
@@ -11027,7 +13589,6 @@ void ConvertLevels(void)
 
 void CreateLevelSketchImages(void)
 {
-#if defined(TARGET_SDL)
   Bitmap *bitmap1;
   Bitmap *bitmap2;
   int i;
@@ -11039,51 +13600,186 @@ void CreateLevelSketchImages(void)
 
   for (i = 0; i < NUM_FILE_ELEMENTS; i++)
   {
-    Bitmap *src_bitmap;
-    int src_x, src_y;
     int element = getMappedElement(i);
-    int graphic = el2edimg(element);
     char basename1[16];
     char basename2[16];
     char *filename1;
     char *filename2;
 
-    sprintf(basename1, "%03d.bmp", i);
-    sprintf(basename2, "%03ds.bmp", i);
+    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);
 
-    getFixedGraphicSource(graphic, 0, &src_bitmap, &src_x, &src_y);
-    BlitBitmap(src_bitmap, bitmap1, src_x, src_y, TILEX, TILEY,
-              0, 0);
+    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);
 
-    getMiniGraphicSource(graphic, &src_bitmap, &src_x, &src_y);
-    BlitBitmap(src_bitmap, bitmap2, src_x, src_y, MINI_TILEX, MINI_TILEY, 0, 0);
+    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);
 
+    // create corresponding SQL statements (for normal and small images)
+    if (i < 1000)
+    {
+      printf("insert into phpbb_words values (NULL, '`%03d', '<IMG class=\"levelsketch\" src=\"/I/%04d.png\"/>');\n", i, i);
+      printf("insert into phpbb_words values (NULL, '¸%03d', '<IMG class=\"levelsketch\" src=\"/I/%04ds.png\"/>');\n", i, i);
+    }
+
+    printf("insert into phpbb_words values (NULL, '`%04d', '<IMG class=\"levelsketch\" src=\"/I/%04d.png\"/>');\n", i, i);
+    printf("insert into phpbb_words values (NULL, '¸%04d', '<IMG class=\"levelsketch\" src=\"/I/%04ds.png\"/>');\n", i, i);
+
+    // optional: create content for forum level sketch demonstration post
     if (options.debug)
-      printf("%03d `%03d%c", i, i, (i % 10 < 9 ? ' ' : '\n'));
+      fprintf(stderr, "%03d `%03d%c", i, i, (i % 10 < 9 ? ' ' : '\n'));
   }
 
   FreeBitmap(bitmap1);
   FreeBitmap(bitmap2);
 
   if (options.debug)
-    printf("\n");
+    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);
-#endif
 }
 
 
@@ -11093,7 +13789,6 @@ void CreateLevelSketchImages(void)
 
 void CreateCustomElementImages(char *directory)
 {
-#if defined(TARGET_SDL)
   char *src_basename = "RocksCE-template.ilbm";
   char *dst_basename = "RocksCE.bmp";
   char *src_filename = getPath2(directory, src_basename);
@@ -11104,7 +13799,7 @@ void CreateCustomElementImages(char *directory)
   int yoffset_ge = (TILEY * NUM_CUSTOM_ELEMENTS / 16);
   int i;
 
-  SDLInitVideoDisplay();
+  InitVideoDefaults();
 
   ReCreateBitmap(&backbuffer, video.width, video.height);
 
@@ -11180,10 +13875,9 @@ 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);
 
   CloseAllAndExit(0);
-#endif
 }