rnd-20040103-1-src
[rocksndiamonds.git] / src / files.c
index 3c26c22fd2f41cdcf9417e4349ba91b179a363d2..ae43afc879aae27806d27a55d4a4b34bc530df21 100644 (file)
@@ -13,6 +13,8 @@
 
 #include <ctype.h>
 #include <sys/stat.h>
+#include <dirent.h>
+#include <math.h>
 
 #include "libgame/libgame.h"
 
@@ -34,8 +36,6 @@
 #define LEVEL_CHUNK_CNT3_UNUSED        10      /* unused CNT3 chunk bytes    */
 #define LEVEL_CPART_CUS3_SIZE  134     /* size of CUS3 chunk part    */
 #define LEVEL_CPART_CUS3_UNUSED        15      /* unused CUS3 bytes / part   */
-#define LEVEL_CPART_CUS4_SIZE  ???     /* size of CUS4 chunk part    */
-#define LEVEL_CPART_CUS4_UNUSED        ???     /* unused CUS4 bytes / part   */
 #define TAPE_HEADER_SIZE       20      /* size of tape file header   */
 #define TAPE_HEADER_UNUSED     3       /* unused tape header bytes   */
 
 #define TAPE_COOKIE_TMPL       "ROCKSNDIAMONDS_TAPE_FILE_VERSION_x.x"
 #define SCORE_COOKIE           "ROCKSNDIAMONDS_SCORE_FILE_VERSION_1.2"
 
+/* values for level file type identifier */
+#define LEVEL_FILE_TYPE_UNKNOWN                0
+#define LEVEL_FILE_TYPE_RND            1
+#define LEVEL_FILE_TYPE_EM             2
+
+#define LEVEL_FILE_TYPE_RND_PACKED     (10 + LEVEL_FILE_TYPE_RND)
+#define LEVEL_FILE_TYPE_EM_PACKED      (10 + LEVEL_FILE_TYPE_EM)
+
+#define IS_SINGLE_LEVEL_FILE(x)                (x < 10)
+#define IS_PACKED_LEVEL_FILE(x)                (x > 10)
+
 
 /* ========================================================================= */
 /* level file functions                                                      */
@@ -80,7 +91,7 @@ void setElementChangeInfoToDefaults(struct ElementChangeInfo *change)
 
   change->delay_fixed = 0;
   change->delay_random = 0;
-  change->delay_frames = -1;   /* later set to reliable default value */
+  change->delay_frames = 1;
 
   change->trigger_element = EL_EMPTY_SPACE;
 
@@ -88,11 +99,11 @@ void setElementChangeInfoToDefaults(struct ElementChangeInfo *change)
   change->use_content = FALSE;
   change->only_complete = FALSE;
   change->use_random_change = FALSE;
-  change->random = 0;
+  change->random = 100;
   change->power = CP_NON_DESTRUCTIVE;
 
-  for(x=0; x<3; x++)
-    for(y=0; y<3; y++)
+  for (x = 0; x < 3; x++)
+    for (y = 0; y < 3; y++)
       change->content[x][y] = EL_EMPTY_SPACE;
 
   change->direct_action = 0;
@@ -117,8 +128,8 @@ static void setLevelInfoToDefaults(struct LevelInfo *level)
   level->fieldx = STD_LEV_FIELDX;
   level->fieldy = STD_LEV_FIELDY;
 
-  for(x=0; x<MAX_LEV_FIELDX; x++)
-    for(y=0; y<MAX_LEV_FIELDY; y++)
+  for (x = 0; x < MAX_LEV_FIELDX; x++)
+    for (y = 0; y < MAX_LEV_FIELDY; y++)
       level->field[x][y] = EL_SAND;
 
   level->time = 100;
@@ -130,50 +141,50 @@ static void setLevelInfoToDefaults(struct LevelInfo *level)
   level->time_timegate = 10;
   level->amoeba_content = EL_DIAMOND;
   level->double_speed = FALSE;
-  level->gravity = FALSE;
+  level->initial_gravity = FALSE;
   level->em_slippery_gems = FALSE;
 
   level->use_custom_template = FALSE;
 
-  for(i=0; i<MAX_LEVEL_NAME_LEN; i++)
+  for (i = 0; i < MAX_LEVEL_NAME_LEN; i++)
     level->name[i] = '\0';
-  for(i=0; i<MAX_LEVEL_AUTHOR_LEN; i++)
+  for (i = 0; i < MAX_LEVEL_AUTHOR_LEN; i++)
     level->author[i] = '\0';
 
   strcpy(level->name, NAMELESS_LEVEL_NAME);
   strcpy(level->author, ANONYMOUS_NAME);
 
-  for (i=0; i<4; i++)
+  for (i = 0; i < 4; i++)
   {
     level->envelope_text[i][0] = '\0';
     level->envelope_xsize[i] = MAX_ENVELOPE_XSIZE;
     level->envelope_ysize[i] = MAX_ENVELOPE_YSIZE;
   }
 
-  for(i=0; i<LEVEL_SCORE_ELEMENTS; i++)
+  for (i = 0; i < LEVEL_SCORE_ELEMENTS; i++)
     level->score[i] = 10;
 
   level->num_yamyam_contents = STD_ELEMENT_CONTENTS;
-  for(i=0; i<MAX_ELEMENT_CONTENTS; i++)
-    for(x=0; x<3; x++)
-      for(y=0; y<3; y++)
+  for (i = 0; i < MAX_ELEMENT_CONTENTS; i++)
+    for (x = 0; x < 3; x++)
+      for (y = 0; y < 3; y++)
        level->yamyam_content[i][x][y] =
          (i < STD_ELEMENT_CONTENTS ? EL_ROCK : EL_EMPTY);
 
   level->field[0][0] = EL_PLAYER_1;
   level->field[STD_LEV_FIELDX - 1][STD_LEV_FIELDY - 1] = EL_EXIT_CLOSED;
 
-  for (i=0; i < MAX_NUM_ELEMENTS; i++)
+  for (i = 0; i < MAX_NUM_ELEMENTS; i++)
   {
     setElementChangePages(&element_info[i], 1);
     setElementChangeInfoToDefaults(element_info[i].change);
   }
 
-  for (i=0; i < NUM_CUSTOM_ELEMENTS; i++)
+  for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
   {
     int element = EL_CUSTOM_START + i;
 
-    for(j=0; j < MAX_ELEMENT_NAME_LEN + 1; j++)
+    for (j = 0; j < MAX_ELEMENT_NAME_LEN + 1; j++)
       element_info[element].description[j] = '\0';
     if (element_info[element].custom_description != NULL)
       strncpy(element_info[element].description,
@@ -188,8 +199,8 @@ static void setLevelInfoToDefaults(struct LevelInfo *level)
     element_info[element].collect_score = 10;          /* special default */
     element_info[element].collect_count = 1;           /* special default */
 
-    element_info[element].push_delay_fixed = 2;                /* special default */
-    element_info[element].push_delay_random = 8;       /* special default */
+    element_info[element].push_delay_fixed = -1;       /* initialize later */
+    element_info[element].push_delay_random = -1;      /* initialize later */
     element_info[element].move_delay_fixed = 0;
     element_info[element].move_delay_random = 0;
 
@@ -199,8 +210,8 @@ static void setLevelInfoToDefaults(struct LevelInfo *level)
 
     element_info[element].slippery_type = SLIPPERY_ANY_RANDOM;
 
-    for(x=0; x<3; x++)
-      for(y=0; y<3; y++)
+    for (x = 0; x < 3; x++)
+      for (y = 0; y < 3; y++)
        element_info[element].content[x][y] = EL_EMPTY_SPACE;
 
     element_info[element].access_type = 0;
@@ -217,7 +228,7 @@ static void setLevelInfoToDefaults(struct LevelInfo *level)
     element_info[element].current_change_page = 0;
 
     /* start with no properties at all */
-    for (j=0; j < NUM_EP_BITFIELDS; j++)
+    for (j = 0; j < NUM_EP_BITFIELDS; j++)
       Properties[element][j] = EP_BITMASK_DEFAULT;
 
     element_info[element].modified_settings = FALSE;
@@ -244,12 +255,12 @@ static void setLevelInfoToDefaults(struct LevelInfo *level)
        strcpy(level->author, PROGRAM_AUTHOR_STRING);
        break;
 
-      case LEVELCLASS_CONTRIBUTION:
-       strncpy(level->author, leveldir_current->name,MAX_LEVEL_AUTHOR_LEN);
+      case LEVELCLASS_CONTRIB:
+       strncpy(level->author, leveldir_current->name, MAX_LEVEL_AUTHOR_LEN);
        level->author[MAX_LEVEL_AUTHOR_LEN] = '\0';
        break;
 
-      case LEVELCLASS_USER:
+      case LEVELCLASS_PRIVATE:
        strncpy(level->author, getRealName(), MAX_LEVEL_AUTHOR_LEN);
        level->author[MAX_LEVEL_AUTHOR_LEN] = '\0';
        break;
@@ -268,13 +279,109 @@ static void ActivateLevelTemplate()
      level data, while all other variables do not change. */
 }
 
-boolean LevelFileExists(int level_nr)
+static char *getLevelFilenameFromBasename(char *basename)
+{
+  static char *filename = NULL;
+
+  checked_free(filename);
+
+  filename = getPath2(getCurrentLevelDir(), basename);
+
+  return filename;
+}
+
+static char *getSingleLevelBasename(int nr, int type)
+{
+  static char basename[MAX_FILENAME_LEN];
+
+  switch (type)
+  {
+    case LEVEL_FILE_TYPE_RND:
+      if (nr < 0)
+       sprintf(basename, "template.%s", LEVELFILE_EXTENSION);
+      else
+       sprintf(basename, "%03d.%s", nr, LEVELFILE_EXTENSION);
+      break;
+
+    case LEVEL_FILE_TYPE_EM:
+      sprintf(basename, "%d", nr);
+      break;
+
+    default:
+      strcpy(basename, UNDEFINED_FILENAME);
+      break;
+  }
+
+  return basename;
+}
+
+static char *getPackedLevelBasename(int type)
+{
+  static char basename[MAX_FILENAME_LEN];
+
+  switch (type)
+  {
+    default:
+      strcpy(basename, UNDEFINED_FILENAME);
+      break;
+  }
+
+  return basename;
+}
+
+static char *getSingleLevelFilename(int nr, int type)
 {
-  char *filename = getLevelFilename(level_nr);
+  return getLevelFilenameFromBasename(getSingleLevelBasename(nr, type));
+}
+
+static char *getPackedLevelFilename(int type)
+{
+  return getLevelFilenameFromBasename(getPackedLevelBasename(type));
+}
+
+char *getDefaultLevelFilename(int nr)
+{
+  return getSingleLevelFilename(nr, LEVEL_FILE_TYPE_RND);
+}
+
+static struct LevelFileInfo *getLevelFileInfo(int nr)
+{
+  static struct LevelFileInfo level_file_info;
+
+  level_file_info.nr = nr;
+
+  /* special case: level template */
+  if (nr < 0)
+  {
+    level_file_info.type = LEVEL_FILE_TYPE_RND;
+    level_file_info.filename = getDefaultLevelFilename(nr);
+
+    return &level_file_info;
+  }
+
+  /* 1st try: check for native Rocks'n'Diamonds level file */
+  level_file_info.type = LEVEL_FILE_TYPE_RND;
+  level_file_info.filename = getSingleLevelFilename(nr, level_file_info.type);
+  if (fileExists(level_file_info.filename))
+    return &level_file_info;
+
+  /* 2nd try: check for classic Emerald Mine level file */
+  level_file_info.type = LEVEL_FILE_TYPE_EM;
+  level_file_info.filename = getSingleLevelFilename(nr, level_file_info.type);
+  if (fileExists(level_file_info.filename))
+    return &level_file_info;
 
-  return (access(filename, F_OK) == 0);
+  /* no known level file found -- use default values */
+  level_file_info.type = LEVEL_FILE_TYPE_RND;
+  level_file_info.filename = getSingleLevelFilename(nr, level_file_info.type);
+
+  return &level_file_info;
 }
 
+/* ------------------------------------------------------------------------- */
+/* functions for loading R'n'D level                                         */
+/* ------------------------------------------------------------------------- */
+
 static int checkLevelElement(int element)
 {
   /* map some (historic, now obsolete) elements */
@@ -356,17 +463,17 @@ static int LoadLevel_HEAD(FILE *file, int chunk_size, struct LevelInfo *level)
   level->time          = getFile16BitBE(file);
   level->gems_needed   = getFile16BitBE(file);
 
-  for(i=0; i<MAX_LEVEL_NAME_LEN; i++)
+  for (i = 0; i < MAX_LEVEL_NAME_LEN; i++)
     level->name[i] = getFile8Bit(file);
   level->name[MAX_LEVEL_NAME_LEN] = 0;
 
-  for(i=0; i<LEVEL_SCORE_ELEMENTS; i++)
+  for (i = 0; i < LEVEL_SCORE_ELEMENTS; i++)
     level->score[i] = getFile8Bit(file);
 
   level->num_yamyam_contents = STD_ELEMENT_CONTENTS;
-  for(i=0; i<STD_ELEMENT_CONTENTS; i++)
-    for(y=0; y<3; y++)
-      for(x=0; x<3; x++)
+  for (i = 0; i < STD_ELEMENT_CONTENTS; i++)
+    for (y = 0; y < 3; y++)
+      for (x = 0; x < 3; x++)
        level->yamyam_content[i][x][y] = checkLevelElement(getFile8Bit(file));
 
   level->amoeba_speed          = getFile8Bit(file);
@@ -374,7 +481,7 @@ static int LoadLevel_HEAD(FILE *file, int chunk_size, struct LevelInfo *level)
   level->time_wheel            = getFile8Bit(file);
   level->amoeba_content                = checkLevelElement(getFile8Bit(file));
   level->double_speed          = (getFile8Bit(file) == 1 ? TRUE : FALSE);
-  level->gravity               = (getFile8Bit(file) == 1 ? TRUE : FALSE);
+  level->initial_gravity       = (getFile8Bit(file) == 1 ? TRUE : FALSE);
   level->encoding_16bit_field  = (getFile8Bit(file) == 1 ? TRUE : FALSE);
   level->em_slippery_gems      = (getFile8Bit(file) == 1 ? TRUE : FALSE);
 
@@ -389,7 +496,7 @@ static int LoadLevel_AUTH(FILE *file, int chunk_size, struct LevelInfo *level)
 {
   int i;
 
-  for(i=0; i<MAX_LEVEL_AUTHOR_LEN; i++)
+  for (i = 0; i < MAX_LEVEL_AUTHOR_LEN; i++)
     level->author[i] = getFile8Bit(file);
   level->author[MAX_LEVEL_NAME_LEN] = 0;
 
@@ -415,8 +522,8 @@ static int LoadLevel_BODY(FILE *file, int chunk_size, struct LevelInfo *level)
     return chunk_size_expected;
   }
 
-  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++)
       level->field[x][y] =
        checkLevelElement(level->encoding_16bit_field ? getFile16BitBE(file) :
                          getFile8Bit(file));
@@ -454,9 +561,9 @@ static int LoadLevel_CONT(FILE *file, int chunk_size, struct LevelInfo *level)
       level->num_yamyam_contents > MAX_ELEMENT_CONTENTS)
     level->num_yamyam_contents = STD_ELEMENT_CONTENTS;
 
-  for(i=0; i<MAX_ELEMENT_CONTENTS; i++)
-    for(y=0; y<3; y++)
-      for(x=0; x<3; x++)
+  for (i = 0; i < MAX_ELEMENT_CONTENTS; i++)
+    for (y = 0; y < 3; y++)
+      for (x = 0; x < 3; x++)
        level->yamyam_content[i][x][y] =
          checkLevelElement(level->encoding_16bit_field ?
                            getFile16BitBE(file) : getFile8Bit(file));
@@ -477,9 +584,9 @@ static int LoadLevel_CNT2(FILE *file, int chunk_size, struct LevelInfo *level)
 
   ReadUnusedBytesFromFile(file, LEVEL_CHUNK_CNT2_UNUSED);
 
-  for(i=0; i<MAX_ELEMENT_CONTENTS; i++)
-    for(y=0; y<3; y++)
-      for(x=0; x<3; x++)
+  for (i = 0; i < MAX_ELEMENT_CONTENTS; i++)
+    for (y = 0; y < 3; y++)
+      for (x = 0; x < 3; x++)
        content_array[i][x][y] = checkLevelElement(getFile16BitBE(file));
 
   /* correct invalid number of content fields -- should never happen */
@@ -490,9 +597,9 @@ static int LoadLevel_CNT2(FILE *file, int chunk_size, struct LevelInfo *level)
   {
     level->num_yamyam_contents = num_contents;
 
-    for(i=0; i<num_contents; i++)
-      for(y=0; y<3; y++)
-       for(x=0; x<3; x++)
+    for (i = 0; i < num_contents; i++)
+      for (y = 0; y < 3; y++)
+       for (x = 0; x < 3; x++)
          level->yamyam_content[i][x][y] = content_array[i][x][y];
   }
   else if (element == EL_BD_AMOEBA)
@@ -536,7 +643,7 @@ static int LoadLevel_CNT3(FILE *file, int chunk_size, struct LevelInfo *level)
     return chunk_size_expected;
   }
 
-  for(i=0; i < envelope_len; i++)
+  for (i = 0; i < envelope_len; i++)
     level->envelope_text[envelope_nr][i] = getFile8Bit(file);
 
   return chunk_size;
@@ -554,7 +661,7 @@ static int LoadLevel_CUS1(FILE *file, int chunk_size, struct LevelInfo *level)
     return chunk_size_expected;
   }
 
-  for (i=0; i < num_changed_custom_elements; i++)
+  for (i = 0; i < num_changed_custom_elements; i++)
   {
     int element = getFile16BitBE(file);
     int properties = getFile32BitBE(file);
@@ -580,7 +687,7 @@ static int LoadLevel_CUS2(FILE *file, int chunk_size, struct LevelInfo *level)
     return chunk_size_expected;
   }
 
-  for (i=0; i < num_changed_custom_elements; i++)
+  for (i = 0; i < num_changed_custom_elements; i++)
   {
     int element = getFile16BitBE(file);
     int custom_target_element = getFile16BitBE(file);
@@ -606,7 +713,7 @@ static int LoadLevel_CUS3(FILE *file, int chunk_size, struct LevelInfo *level)
     return chunk_size_expected;
   }
 
-  for (i=0; i < num_changed_custom_elements; i++)
+  for (i = 0; i < num_changed_custom_elements; i++)
   {
     int element = getFile16BitBE(file);
 
@@ -614,10 +721,10 @@ static int LoadLevel_CUS3(FILE *file, int chunk_size, struct LevelInfo *level)
     {
       Error(ERR_WARN, "invalid custom element number %d", element);
 
-      element = EL_DEFAULT;    /* dummy element used for artwork config */
+      element = EL_DUMMY;
     }
 
-    for(j=0; j<MAX_ELEMENT_NAME_LEN; j++)
+    for (j = 0; j < MAX_ELEMENT_NAME_LEN; j++)
       element_info[element].description[j] = getFile8Bit(file);
     element_info[element].description[MAX_ELEMENT_NAME_LEN] = 0;
 
@@ -642,8 +749,8 @@ static int LoadLevel_CUS3(FILE *file, int chunk_size, struct LevelInfo *level)
     element_info[element].move_direction_initial = getFile8Bit(file);
     element_info[element].move_stepsize = getFile8Bit(file);
 
-    for(y=0; y<3; y++)
-      for(x=0; x<3; x++)
+    for (y = 0; y < 3; y++)
+      for (x = 0; x < 3; x++)
        element_info[element].content[x][y] =
          checkLevelElement(getFile16BitBE(file));
 
@@ -667,8 +774,8 @@ static int LoadLevel_CUS3(FILE *file, int chunk_size, struct LevelInfo *level)
     element_info[element].change->random = getFile8Bit(file);
     element_info[element].change->power = getFile8Bit(file);
 
-    for(y=0; y<3; y++)
-      for(x=0; x<3; x++)
+    for (y = 0; y < 3; y++)
+      for (x = 0; x < 3; x++)
        element_info[element].change->content[x][y] =
          checkLevelElement(getFile16BitBE(file));
 
@@ -697,12 +804,12 @@ static int LoadLevel_CUS4(FILE *file, int chunk_size, struct LevelInfo *level)
   {
     Error(ERR_WARN, "invalid custom element number %d", element);
 
-    element = EL_DEFAULT;      /* dummy element used for artwork config */
+    element = EL_DUMMY;
   }
 
   ei = &element_info[element];
 
-  for(i=0; i < MAX_ELEMENT_NAME_LEN; i++)
+  for (i = 0; i < MAX_ELEMENT_NAME_LEN; i++)
     ei->description[i] = getFile8Bit(file);
   ei->description[MAX_ELEMENT_NAME_LEN] = 0;
 
@@ -740,8 +847,8 @@ static int LoadLevel_CUS4(FILE *file, int chunk_size, struct LevelInfo *level)
 
   ei->slippery_type = getFile8Bit(file);
 
-  for(y=0; y<3; y++)
-    for(x=0; x<3; x++)
+  for (y = 0; y < 3; y++)
+    for (x = 0; x < 3; x++)
       ei->content[x][y] = checkLevelElement(getFile16BitBE(file));
 
   /* some free bytes for future custom property values and padding */
@@ -751,7 +858,7 @@ static int LoadLevel_CUS4(FILE *file, int chunk_size, struct LevelInfo *level)
 
   setElementChangePages(ei, ei->num_change_pages);
 
-  for (i=0; i < ei->num_change_pages; i++)
+  for (i = 0; i < ei->num_change_pages; i++)
   {
     struct ElementChangeInfo *change = &ei->change_page[i];
 
@@ -776,14 +883,19 @@ static int LoadLevel_CUS4(FILE *file, int chunk_size, struct LevelInfo *level)
     change->random = getFile8Bit(file);
     change->power = getFile8Bit(file);
 
-    for(y=0; y<3; y++)
-      for(x=0; x<3; x++)
+    for (y = 0; y < 3; y++)
+      for (x = 0; x < 3; x++)
        change->content[x][y] = checkLevelElement(getFile16BitBE(file));
 
     change->can_change = getFile8Bit(file);
 
+    change->sides = getFile8Bit(file);
+
+    if (change->sides == CH_SIDE_NONE) /* correct empty sides field */
+      change->sides = CH_SIDE_ANY;
+
     /* some free bytes for future change property values and padding */
-    ReadUnusedBytesFromFile(file, 9);
+    ReadUnusedBytesFromFile(file, 8);
   }
 
   /* mark this custom element as modified */
@@ -792,8 +904,10 @@ static int LoadLevel_CUS4(FILE *file, int chunk_size, struct LevelInfo *level)
   return chunk_size;
 }
 
-void LoadLevelFromFilename(struct LevelInfo *level, char *filename)
+static void LoadLevelFromFileInfo_RND(struct LevelInfo *level,
+                                     struct LevelFileInfo *level_file_info)
 {
+  char *filename = level_file_info->filename;
   char cookie[MAX_LINE_LEN];
   char chunk_name[CHUNK_ID_LEN + 1];
   int chunk_size;
@@ -807,7 +921,7 @@ void LoadLevelFromFilename(struct LevelInfo *level, char *filename)
     level->no_level_file = TRUE;
 
     if (level != &level_template)
-      Error(ERR_WARN, "cannot read level '%s' - creating new level", filename);
+      Error(ERR_WARN, "cannot read level '%s' - using empty level", filename);
 
     return;
   }
@@ -922,23 +1036,368 @@ void LoadLevelFromFilename(struct LevelInfo *level, char *filename)
   fclose(file);
 }
 
-#if 1
+/* ------------------------------------------------------------------------- */
+/* functions for loading EM level                                            */
+/* ------------------------------------------------------------------------- */
+
+static int map_em_element_yam(int element)
+{
+  switch (element)
+  {
+    case 0x00: return EL_EMPTY;
+    case 0x01: return EL_EMERALD;
+    case 0x02: return EL_DIAMOND;
+    case 0x03: return EL_ROCK;
+    case 0x04: return EL_ROBOT;
+    case 0x05: return EL_SPACESHIP_UP;
+    case 0x06: return EL_BOMB;
+    case 0x07: return EL_BUG_UP;
+    case 0x08: return EL_AMOEBA_DROP;
+    case 0x09: return EL_NUT;
+    case 0x0a: return EL_YAMYAM;
+    case 0x0b: return EL_QUICKSAND_FULL;
+    case 0x0c: return EL_SAND;
+    case 0x0d: return EL_WALL_SLIPPERY;
+    case 0x0e: return EL_STEELWALL;
+    case 0x0f: return EL_WALL;
+    case 0x10: return EL_EM_KEY_1;
+    case 0x11: return EL_EM_KEY_2;
+    case 0x12: return EL_EM_KEY_4;
+    case 0x13: return EL_EM_KEY_3;
+    case 0x14: return EL_MAGIC_WALL;
+    case 0x15: return EL_ROBOT_WHEEL;
+    case 0x16: return EL_DYNAMITE;
+
+    case 0x17: return EL_EM_KEY_1;                     /* EMC */
+    case 0x18: return EL_BUG_UP;                       /* EMC */
+    case 0x1a: return EL_DIAMOND;                      /* EMC */
+    case 0x1b: return EL_EMERALD;                      /* EMC */
+    case 0x25: return EL_NUT;                          /* EMC */
+    case 0x80: return EL_EMPTY;                        /* EMC */
+    case 0x85: return EL_EM_KEY_1;                     /* EMC */
+    case 0x86: return EL_EM_KEY_2;                     /* EMC */
+    case 0x87: return EL_EM_KEY_4;                     /* EMC */
+    case 0x88: return EL_EM_KEY_3;                     /* EMC */
+    case 0x94: return EL_QUICKSAND_EMPTY;              /* EMC */
+    case 0x9a: return EL_AMOEBA_WET;                   /* EMC */
+    case 0xaf: return EL_DYNAMITE;                     /* EMC */
+    case 0xbd: return EL_SAND;                         /* EMC */
+
+    default:
+      Error(ERR_WARN, "invalid level element %d", element);
+      return EL_CHAR_QUESTION;
+  }
+}
+
+static int map_em_element_field(int element)
+{
+  if (element >= 0xc8 && element <= 0xe1)
+    return EL_CHAR_A + (element - 0xc8);
+  else if (element >= 0xe2 && element <= 0xeb)
+    return EL_CHAR_0 + (element - 0xe2);
+
+  switch (element)
+  {
+    case 0x00: return EL_ROCK;
+    case 0x02: return EL_DIAMOND;
+    case 0x03: return EL_DIAMOND;
+    case 0x04: return EL_ROBOT;
+    case 0x05: return EL_ROBOT;                        /* EMC */
+    case 0x08: return EL_SPACESHIP_UP;
+    case 0x09: return EL_SPACESHIP_RIGHT;
+    case 0x0a: return EL_SPACESHIP_DOWN;
+    case 0x0b: return EL_SPACESHIP_LEFT;
+    case 0x0c: return EL_SPACESHIP_UP;
+    case 0x0d: return EL_SPACESHIP_RIGHT;
+    case 0x0e: return EL_SPACESHIP_DOWN;
+    case 0x0f: return EL_SPACESHIP_LEFT;
+    case 0x10: return EL_BOMB;
+    case 0x12: return EL_EMERALD;
+    case 0x13: return EL_EMERALD;
+    case 0x14: return EL_BUG_UP;
+    case 0x15: return EL_BUG_RIGHT;
+    case 0x16: return EL_BUG_DOWN;
+    case 0x17: return EL_BUG_LEFT;
+    case 0x18: return EL_BUG_UP;
+    case 0x19: return EL_BUG_RIGHT;
+    case 0x1a: return EL_BUG_DOWN;
+    case 0x1b: return EL_BUG_LEFT;
+    case 0x1c: return EL_AMOEBA_DROP;
+    case 0x20: return EL_ROCK;
+    case 0x24: return EL_MAGIC_WALL;
+    case 0x25: return EL_NUT;
+
+      /* looks like magic wheel, but is _always_ activated */
+    case 0x28: return EL_ROBOT_WHEEL;                  /* EMC */
+
+    case 0x29: return EL_YAMYAM;
+    case 0x2a: return EL_YAMYAM;
+    case 0x2b: return EL_YAMYAM;                       /* EMC */
+    case 0x2c: return EL_YAMYAM;                       /* EMC */
+    case 0x2d: return EL_QUICKSAND_FULL;
+    case 0x39: return EL_EXPANDABLE_WALL_HORIZONTAL;   /* EMC */
+    case 0x3a: return EL_EXPANDABLE_WALL_VERTICAL;     /* EMC */
+    case 0x3b: return EL_DYNAMITE_ACTIVE;
+    case 0x3c: return EL_DYNAMITE_ACTIVE;
+    case 0x3d: return EL_DYNAMITE_ACTIVE;
+    case 0x3e: return EL_DYNAMITE_ACTIVE;
+    case 0x3f: return EL_ACID_POOL_BOTTOM;
+    case 0x40: return EL_EXIT_OPEN;
+    case 0x41: return EL_EXIT_OPEN;
+    case 0x42: return EL_EXIT_OPEN;
+    case 0x43: return EL_BALLOON;
+    case 0x4e: return EL_INVISIBLE_WALL;
+    case 0x65: return EL_ACID;                         /* EMC */
+    case 0x73: return EL_SAND;                         /* EMC */
+    case 0x74: return EL_STEELWALL;
+    case 0x7b: return EL_ACID;
+    case 0x80: return EL_EMPTY;
+    case 0x81: return EL_WALL_SLIPPERY;
+    case 0x82: return EL_SAND;
+    case 0x83: return EL_STEELWALL;
+    case 0x84: return EL_WALL;
+    case 0x85: return EL_EM_KEY_1;
+    case 0x86: return EL_EM_KEY_2;
+    case 0x87: return EL_EM_KEY_4;
+    case 0x88: return EL_EM_KEY_3;
+    case 0x89: return EL_EM_GATE_1;
+    case 0x8a: return EL_EM_GATE_2;
+    case 0x8b: return EL_EM_GATE_4;
+    case 0x8c: return EL_EM_GATE_3;
+    case 0x8d: return EL_INVISIBLE_WALL;               /* EMC */
+    case 0x8e: return EL_EM_GATE_1_GRAY;
+    case 0x8f: return EL_EM_GATE_2_GRAY;
+    case 0x90: return EL_EM_GATE_4_GRAY;
+    case 0x91: return EL_EM_GATE_3_GRAY;
+    case 0x92: return EL_MAGIC_WALL;
+    case 0x94: return EL_QUICKSAND_EMPTY;
+    case 0x95: return EL_ACID_POOL_TOPLEFT;
+    case 0x96: return EL_ACID_POOL_TOPRIGHT;
+    case 0x97: return EL_ACID_POOL_BOTTOMLEFT;
+    case 0x98: return EL_ACID_POOL_BOTTOMRIGHT;
+    case 0x99: return EL_ACID;
+    case 0x9a: return EL_AMOEBA_DEAD;
+    case 0x9b: return EL_AMOEBA_DEAD;
+    case 0x9c: return EL_AMOEBA_DEAD;
+    case 0x9d: return EL_AMOEBA_DEAD;
+    case 0x9e: return EL_EXIT_CLOSED;
+    case 0x9f: return EL_CHAR_LESS;                    /* EMC */
+    case 0x93: return EL_ROBOT_WHEEL;
+
+      /* looks like normal dust, but behaves like wall */
+    case 0xa0: return EL_WALL;                         /* EMC */
+
+    case 0xa8: return EL_EMC_WALL_1;                   /* EMC */
+    case 0xa9: return EL_EMC_WALL_2;                   /* EMC */
+    case 0xaa: return EL_EMC_WALL_3;                   /* EMC */
+    case 0xab: return EL_EMC_WALL_7;                   /* EMC */
+    case 0xae: return EL_CHAR_MINUS;                   /* EMC */
+    case 0xaf: return EL_DYNAMITE;
+    case 0xb0: return EL_EMC_STEELWALL_1;              /* EMC */
+    case 0xb1: return EL_EMC_WALL_8;                   /* EMC */
+
+      /* (exact steel wall) */
+    case 0xb3: return EL_STEELWALL;                    /* EMC */
+
+    case 0xb4: return EL_WALL_SLIPPERY;                /* EMC */
+    case 0xb5: return EL_EMC_WALL_6;                   /* EMC */
+    case 0xb6: return EL_EMC_WALL_5;                   /* EMC */
+    case 0xb7: return EL_EMC_WALL_4;                   /* EMC */
+    case 0xb8: return EL_BALLOON_SWITCH_ANY;           /* EMC */
+    case 0xb9: return EL_BALLOON_SWITCH_RIGHT;         /* EMC */
+    case 0xba: return EL_BALLOON_SWITCH_DOWN;          /* EMC */
+    case 0xbb: return EL_BALLOON_SWITCH_LEFT;          /* EMC */
+    case 0xbc: return EL_BALLOON_SWITCH_UP;            /* EMC */
+    case 0xbd: return EL_SAND;                         /* EMC */
+    case 0xec: return EL_CHAR_PERIOD;
+    case 0xed: return EL_CHAR_EXCLAM;
+    case 0xee: return EL_CHAR_COLON;
+    case 0xef: return EL_CHAR_QUESTION;
+    case 0xf0: return EL_CHAR_GREATER;
+    case 0xf1: return EL_CHAR_COPYRIGHT;
+    case 0xfe: return EL_PLAYER_1;
+    case 0xff: return EL_PLAYER_2;
+
+    default:
+      Error(ERR_WARN, "invalid level element %d", element);
+      return EL_CHAR_QUESTION;
+  }
+}
+
+static void LoadLevelFromFileInfo_EM(struct LevelInfo *level,
+                                    struct LevelFileInfo *level_file_info)
+{
+  char *filename = level_file_info->filename;
+  FILE *file;
+  unsigned char body[40][64];
+  unsigned char *leveldata = &body[0][0];
+  unsigned char *header = &leveldata[2048];
+  unsigned char code0 = 0x65;
+  unsigned char code1 = 0x11;
+  boolean level_is_crypted = FALSE;
+  int nr = level_file_info->nr;
+  int jx, jy;
+  int i, x, y;
+
+  /* always start with reliable default values */
+  setLevelInfoToDefaults(level);
+
+  if (!(file = fopen(filename, MODE_READ)))
+  {
+    level->no_level_file = TRUE;
+
+    Error(ERR_WARN, "cannot read level '%s' - using empty level", filename);
+
+    return;
+  }
+
+  for(i = 0; i < 2106; i++)
+    leveldata[i] = fgetc(file);
+
+  fclose(file);
+
+  /* check if level data is crypted by testing against known starting bytes
+     of the few existing crypted level files (from Emerald Mine 1 + 2) */
+
+  if ((leveldata[0] == 0xf1 ||
+       leveldata[0] == 0xf5) && leveldata[2] == 0xe7 && leveldata[3] == 0xee)
+  {
+    level_is_crypted = TRUE;
+
+    if (leveldata[0] == 0xf5)  /* error in crypted Emerald Mine 2 levels */
+      leveldata[0] = 0xf1;
+  }
+
+  if (level_is_crypted)                /* decode crypted level data */
+  {
+    for(i = 0; i < 2106; i++)
+    {
+      leveldata[i] ^= code0;
+      leveldata[i] -= code1;
+
+      code0  = (code0 + 7) & 0xff;
+    }
+  }
+
+  level->fieldx        = 64;
+  level->fieldy        = 32;
+
+  level->time          = header[46] * 10;
+  level->gems_needed   = header[47];
+
+  /* The original Emerald Mine levels have their level number stored
+     at the second byte of the level file...
+     Do not trust this information at other level files, e.g. EMC,
+     but correct it anyway (normally the first row is completely
+     steel wall, so the correction does not hurt anyway). */
+
+  if (leveldata[1] == nr)
+    leveldata[1] = leveldata[2];       /* correct level number field */
+
+  sprintf(level->name, "Level %d", nr);
+
+  level->score[SC_EMERALD]     = header[36];
+  level->score[SC_DIAMOND]     = header[37];
+  level->score[SC_ROBOT]       = header[38];
+  level->score[SC_SPACESHIP]   = header[39];
+  level->score[SC_BUG]         = header[40];
+  level->score[SC_YAMYAM]      = header[41];
+  level->score[SC_NUT]         = header[42];
+  level->score[SC_DYNAMITE]    = header[43];
+  level->score[SC_TIME_BONUS]  = header[44];
+
+  level->num_yamyam_contents = 4;
+
+  for(i = 0; i < level->num_yamyam_contents; i++)
+    for(y = 0; y < 3; y++)
+      for(x = 0; x < 3; x++)
+       level->yamyam_content[i][x][y] =
+         map_em_element_yam(header[i * 9 + y * 3 + x]);
+
+  level->amoeba_speed          = (header[52] * 256 + header[53]) % 256;
+  level->time_magic_wall       = (header[54] * 256 + header[55]) * 16 / 100;
+  level->time_wheel            = (header[56] * 256 + header[57]) * 16 / 100;
+  level->amoeba_content                = EL_DIAMOND;
+
+  for (y = 0; y < level->fieldy; y++) for (x = 0; x < level->fieldx; x++)
+  {
+    int new_element = map_em_element_field(body[y][x]);
+
+    if (new_element == EL_AMOEBA_DEAD && level->amoeba_speed)
+      new_element = EL_AMOEBA_WET;
+
+    level->field[x][y] = new_element;
+  }
+
+  jx = (header[48] * 256 + header[49]) % 64;
+  jy = (header[48] * 256 + header[49]) / 64;
+  level->field[jx][jy] = EL_PLAYER_1;
+
+  jx = (header[50] * 256 + header[51]) % 64;
+  jy = (header[50] * 256 + header[51]) / 64;
+  level->field[jx][jy] = EL_PLAYER_2;
+}
+
+void LoadLevelFromFileInfo(struct LevelInfo *level,
+                          struct LevelFileInfo *level_file_info)
+{
+  switch (level_file_info->type)
+  {
+    case LEVEL_FILE_TYPE_RND:
+      LoadLevelFromFileInfo_RND(level, level_file_info);
+      break;
+
+    case LEVEL_FILE_TYPE_EM:
+      LoadLevelFromFileInfo_EM(level, level_file_info);
+      break;
+
+    default:
+      LoadLevelFromFileInfo_RND(level, level_file_info);
+      break;
+  }
+}
+
+void LoadLevelFromFilename(struct LevelInfo *level, char *filename)
+{
+  static struct LevelFileInfo level_file_info;
+
+  level_file_info.nr = 0;                      /* unknown */
+  level_file_info.type = LEVEL_FILE_TYPE_RND;  /* no others supported yet */
+  level_file_info.filename = filename;
+
+  LoadLevelFromFileInfo(level, &level_file_info);
+}
 
 static void LoadLevel_InitVersion(struct LevelInfo *level, char *filename)
 {
   if (leveldir_current == NULL)                /* only when dumping level */
     return;
 
+#if 0
+  printf("::: sort_priority: %d\n", leveldir_current->sort_priority);
+#endif
+
   /* determine correct game engine version of current level */
-  if (IS_LEVELCLASS_CONTRIBUTION(leveldir_current) ||
-      IS_LEVELCLASS_USER(leveldir_current))
+#if 1
+  if (!leveldir_current->latest_engine)
+#else
+  if (IS_LEVELCLASS_CONTRIB(leveldir_current) ||
+      IS_LEVELCLASS_PRIVATE(leveldir_current) ||
+      IS_LEVELCLASS_UNDEFINED(leveldir_current))
+#endif
   {
 #if 0
     printf("\n::: This level is private or contributed: '%s'\n", filename);
 #endif
 
-    /* For user contributed and private levels, use the version of
-       the game engine the levels were created for.
+#if 0
+    printf("\n::: Use the stored game engine version for this level\n");
+#endif
+
+    /* For all levels which are not forced to use the latest game engine
+       version (normally user contributed, private and undefined levels),
+       use the version of the game engine the levels were created for.
+
        Since 2.0.1, the game engine version is now directly stored
        in the level file (chunk "VERS"), so there is no need anymore
        to set the game version from the file version (except for old,
@@ -948,7 +1407,7 @@ static void LoadLevel_InitVersion(struct LevelInfo *level, char *filename)
     /* do some special adjustments to support older level versions */
     if (level->file_version == FILE_VERSION_1_0)
     {
-      Error(ERR_WARN, "level file '%s'has version number 1.0", filename);
+      Error(ERR_WARN, "level file '%s' has version number 1.0", filename);
       Error(ERR_WARN, "using high speed movement for player");
 
       /* player was faster than monsters in (pre-)1.0 levels */
@@ -956,7 +1415,7 @@ static void LoadLevel_InitVersion(struct LevelInfo *level, char *filename)
     }
 
     /* Default behaviour for EM style gems was "slippery" only in 2.0.1 */
-    if (level->game_version == VERSION_IDENT(2,0,1))
+    if (level->game_version == VERSION_IDENT(2,0,1,0))
       level->em_slippery_gems = TRUE;
   }
   else
@@ -966,12 +1425,22 @@ static void LoadLevel_InitVersion(struct LevelInfo *level, char *filename)
           leveldir_current->sort_priority, filename);
 #endif
 
-    /* Always use the latest version of the game engine for all but
-       user contributed and private levels; this allows for actual
-       corrections in the game engine to take effect for existing,
-       converted levels (from "classic" or other existing games) to
-       make the game emulation more accurate, while (hopefully) not
-       breaking existing levels created from other players. */
+#if 0
+    printf("\n::: Use latest game engine version for this level.\n");
+#endif
+
+    /* For all levels which are forced to use the latest game engine version
+       (normally all but user contributed, private and undefined levels), set
+       the game engine version to the actual version; this allows for actual
+       corrections in the game engine to take effect for existing, converted
+       levels (from "classic" or other existing games) to make the emulation
+       of the corresponding game more accurate, while (hopefully) not breaking
+       existing levels created from other players. */
+
+#if 0
+    printf("::: changing engine from %d to %d\n",
+          level->game_version, GAME_VERSION_ACTUAL);
+#endif
 
     level->game_version = GAME_VERSION_ACTUAL;
 
@@ -985,6 +1454,10 @@ static void LoadLevel_InitVersion(struct LevelInfo *level, char *filename)
     if (level->file_version < FILE_VERSION_2_0)
       level->em_slippery_gems = TRUE;
   }
+
+#if 0
+  printf("::: => %d\n", level->game_version);
+#endif
 }
 
 static void LoadLevel_InitElements(struct LevelInfo *level, char *filename)
@@ -993,14 +1466,14 @@ static void LoadLevel_InitElements(struct LevelInfo *level, char *filename)
 
   /* map custom element change events that have changed in newer versions
      (these following values were accidentally changed in version 3.0.1) */
-  if (level->game_version <= VERSION_IDENT(3,0,0))
+  if (level->game_version <= VERSION_IDENT(3,0,0,0))
   {
-    for (i=0; i < NUM_CUSTOM_ELEMENTS; i++)
+    for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
     {
       int element = EL_CUSTOM_START + i;
 
       /* order of checking and copying events to be mapped is important */
-      for (j=CE_BY_OTHER_ACTION; j >= CE_BY_PLAYER; j--)
+      for (j = CE_BY_OTHER_ACTION; j >= CE_BY_PLAYER_OBSOLETE; j--)
       {
        if (HAS_CHANGE_EVENT(element, j - 2))
        {
@@ -1010,7 +1483,7 @@ static void LoadLevel_InitElements(struct LevelInfo *level, char *filename)
       }
 
       /* order of checking and copying events to be mapped is important */
-      for (j=CE_OTHER_GETS_COLLECTED; j >= CE_COLLISION; j--)
+      for (j = CE_OTHER_GETS_COLLECTED; j >= CE_HITTING_SOMETHING; j--)
       {
        if (HAS_CHANGE_EVENT(element, j - 1))
        {
@@ -1022,24 +1495,24 @@ static void LoadLevel_InitElements(struct LevelInfo *level, char *filename)
   }
 
   /* some custom element change events get mapped since version 3.0.3 */
-  for (i=0; i < NUM_CUSTOM_ELEMENTS; i++)
+  for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
   {
     int element = EL_CUSTOM_START + i;
 
-    if (HAS_CHANGE_EVENT(element, CE_BY_PLAYER) ||
-       HAS_CHANGE_EVENT(element, CE_BY_COLLISION))
+    if (HAS_CHANGE_EVENT(element, CE_BY_PLAYER_OBSOLETE) ||
+       HAS_CHANGE_EVENT(element, CE_BY_COLLISION_OBSOLETE))
     {
-      SET_CHANGE_EVENT(element, CE_BY_PLAYER, FALSE);
-      SET_CHANGE_EVENT(element, CE_BY_COLLISION, FALSE);
+      SET_CHANGE_EVENT(element, CE_BY_PLAYER_OBSOLETE, FALSE);
+      SET_CHANGE_EVENT(element, CE_BY_COLLISION_OBSOLETE, FALSE);
 
       SET_CHANGE_EVENT(element, CE_BY_DIRECT_ACTION, TRUE);
     }
   }
 
   /* initialize "can_change" field for old levels with only one change page */
-  if (level->game_version <= VERSION_IDENT(3,0,2))
+  if (level->game_version <= VERSION_IDENT(3,0,2,0))
   {
-    for (i=0; i < NUM_CUSTOM_ELEMENTS; i++)
+    for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
     {
       int element = EL_CUSTOM_START + i;
 
@@ -1048,6 +1521,31 @@ static void LoadLevel_InitElements(struct LevelInfo *level, char *filename)
     }
   }
 
+#if 0
+  /* set default push delay values (corrected since version 3.0.7-1) */
+  if (level->game_version < VERSION_IDENT(3,0,7,1))
+  {
+    game.default_push_delay_fixed = 2;
+    game.default_push_delay_random = 8;
+  }
+  else
+  {
+    game.default_push_delay_fixed = 8;
+    game.default_push_delay_random = 8;
+  }
+
+  /* set uninitialized push delay values of custom elements in older levels */
+  for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
+  {
+    int element = EL_CUSTOM_START + i;
+
+    if (element_info[element].push_delay_fixed == -1)
+      element_info[element].push_delay_fixed = game.default_push_delay_fixed;
+    if (element_info[element].push_delay_random == -1)
+      element_info[element].push_delay_random = game.default_push_delay_random;
+  }
+#endif
+
   /* initialize element properties for level editor etc. */
   InitElementPropertiesEngine(level->game_version);
 }
@@ -1057,13 +1555,13 @@ static void LoadLevel_InitPlayfield(struct LevelInfo *level, char *filename)
   int x, y;
 
   /* map elements that have changed in newer versions */
-  for(y=0; y<level->fieldy; y++)
+  for (y = 0; y < level->fieldy; y++)
   {
-    for(x=0; x<level->fieldx; x++)
+    for (x = 0; x < level->fieldx; x++)
     {
       int element = level->field[x][y];
 
-      if (level->game_version <= VERSION_IDENT(2,2,0))
+      if (level->game_version <= VERSION_IDENT(2,2,0,0))
       {
        /* map game font elements */
        element = (element == EL_CHAR('[')  ? EL_CHAR_AUMLAUT :
@@ -1072,7 +1570,7 @@ static void LoadLevel_InitPlayfield(struct LevelInfo *level, char *filename)
                   element == EL_CHAR('^')  ? EL_CHAR_COPYRIGHT : element);
       }
 
-      if (level->game_version < VERSION_IDENT(3,0,0))
+      if (level->game_version < VERSION_IDENT(3,0,0,0))
       {
        /* map Supaplex gravity tube elements */
        element = (element == EL_SP_GRAVITY_PORT_LEFT  ? EL_SP_PORT_LEFT  :
@@ -1087,8 +1585,8 @@ static void LoadLevel_InitPlayfield(struct LevelInfo *level, char *filename)
   }
 
   /* copy elements to runtime playfield array */
-  for(x=0; x<MAX_LEV_FIELDX; x++)
-    for(y=0; y<MAX_LEV_FIELDY; y++)
+  for (x = 0; x < MAX_LEV_FIELDX; x++)
+    for (y = 0; y < MAX_LEV_FIELDY; y++)
       Feld[x][y] = level->field[x][y];
 
   /* initialize level size variables for faster access */
@@ -1099,217 +1597,75 @@ static void LoadLevel_InitPlayfield(struct LevelInfo *level, char *filename)
   SetBorderElement();
 }
 
-#else
-
-static void LoadLevel_InitLevel(struct LevelInfo *level, char *filename)
+void LoadLevelTemplate(int nr)
 {
-  int i, j, x, y;
+#if 1
+  struct LevelFileInfo *level_file_info = getLevelFileInfo(nr);
+  char *filename = level_file_info->filename;
 
-  if (leveldir_current == NULL)                /* only when dumping level */
-    return;
+  LoadLevelFromFileInfo(&level, level_file_info);
+#else
+  char *filename = getDefaultLevelFilename(nr);
 
-  /* determine correct game engine version of current level */
-  if (IS_LEVELCLASS_CONTRIBUTION(leveldir_current) ||
-      IS_LEVELCLASS_USER(leveldir_current))
-  {
-#if 0
-    printf("\n::: This level is private or contributed: '%s'\n", filename);
+  LoadLevelFromFilename_RND(&level_template, filename);
 #endif
 
-    /* For user contributed and private levels, use the version of
-       the game engine the levels were created for.
-       Since 2.0.1, the game engine version is now directly stored
-       in the level file (chunk "VERS"), so there is no need anymore
-       to set the game version from the file version (except for old,
-       pre-2.0 levels, where the game version is still taken from the
-       file format version used to store the level -- see above). */
+  LoadLevel_InitVersion(&level, filename);
+  LoadLevel_InitElements(&level, filename);
 
-    /* do some special adjustments to support older level versions */
-    if (level->file_version == FILE_VERSION_1_0)
-    {
-      Error(ERR_WARN, "level file '%s'has version number 1.0", filename);
-      Error(ERR_WARN, "using high speed movement for player");
+  ActivateLevelTemplate();
+}
 
-      /* player was faster than monsters in (pre-)1.0 levels */
-      level->double_speed = TRUE;
-    }
+void LoadLevel(int nr)
+{
+#if 1
+  struct LevelFileInfo *level_file_info = getLevelFileInfo(nr);
+  char *filename = level_file_info->filename;
 
-    /* Default behaviour for EM style gems was "slippery" only in 2.0.1 */
-    if (level->game_version == VERSION_IDENT(2,0,1))
-      level->em_slippery_gems = TRUE;
-  }
-  else
-  {
-#if 0
-    printf("\n::: ALWAYS USE LATEST ENGINE FOR THIS LEVEL: [%d] '%s'\n",
-          leveldir_current->sort_priority, filename);
+  LoadLevelFromFileInfo(&level, level_file_info);
+#else
+  char *filename = getLevelFilename(nr);
+
+  LoadLevelFromFilename_RND(&level, filename);
 #endif
 
-    /* Always use the latest version of the game engine for all but
-       user contributed and private levels; this allows for actual
-       corrections in the game engine to take effect for existing,
-       converted levels (from "classic" or other existing games) to
-       make the game emulation more accurate, while (hopefully) not
-       breaking existing levels created from other players. */
+  if (level.use_custom_template)
+    LoadLevelTemplate(-1);
 
-    level->game_version = GAME_VERSION_ACTUAL;
+#if 1
+  LoadLevel_InitVersion(&level, filename);
+  LoadLevel_InitElements(&level, filename);
+  LoadLevel_InitPlayfield(&level, filename);
+#else
+  LoadLevel_InitLevel(&level, filename);
+#endif
+}
 
-    /* Set special EM style gems behaviour: EM style gems slip down from
-       normal, steel and growing wall. As this is a more fundamental change,
-       it seems better to set the default behaviour to "off" (as it is more
-       natural) and make it configurable in the level editor (as a property
-       of gem style elements). Already existing converted levels (neither
-       private nor contributed levels) are changed to the new behaviour. */
+static void SaveLevel_VERS(FILE *file, struct LevelInfo *level)
+{
+  putFileVersion(file, level->file_version);
+  putFileVersion(file, level->game_version);
+}
 
-    if (level->file_version < FILE_VERSION_2_0)
-      level->em_slippery_gems = TRUE;
-  }
+static void SaveLevel_HEAD(FILE *file, struct LevelInfo *level)
+{
+  int i, x, y;
 
-  /* map elements that have changed in newer versions */
-  for(y=0; y<level->fieldy; y++)
-  {
-    for(x=0; x<level->fieldx; x++)
-    {
-      int element = level->field[x][y];
+  putFile8Bit(file, level->fieldx);
+  putFile8Bit(file, level->fieldy);
 
-      if (level->game_version <= VERSION_IDENT(2,2,0))
-      {
-       /* map game font elements */
-       element = (element == EL_CHAR('[')  ? EL_CHAR_AUMLAUT :
-                  element == EL_CHAR('\\') ? EL_CHAR_OUMLAUT :
-                  element == EL_CHAR(']')  ? EL_CHAR_UUMLAUT :
-                  element == EL_CHAR('^')  ? EL_CHAR_COPYRIGHT : element);
-      }
+  putFile16BitBE(file, level->time);
+  putFile16BitBE(file, level->gems_needed);
 
-      if (level->game_version < VERSION_IDENT(3,0,0))
-      {
-       /* map Supaplex gravity tube elements */
-       element = (element == EL_SP_GRAVITY_PORT_LEFT  ? EL_SP_PORT_LEFT  :
-                  element == EL_SP_GRAVITY_PORT_RIGHT ? EL_SP_PORT_RIGHT :
-                  element == EL_SP_GRAVITY_PORT_UP    ? EL_SP_PORT_UP    :
-                  element == EL_SP_GRAVITY_PORT_DOWN  ? EL_SP_PORT_DOWN  :
-                  element);
-      }
-
-      level->field[x][y] = element;
-    }
-  }
-
-  /* map custom element change events that have changed in newer versions
-     (these following values have accidentally changed in version 3.0.1) */
-  if (level->game_version <= VERSION_IDENT(3,0,0))
-  {
-    for (i=0; i < NUM_CUSTOM_ELEMENTS; i++)
-    {
-      int element = EL_CUSTOM_START + i;
-
-      /* order of checking events to be mapped is important */
-      for (j=CE_BY_OTHER; j >= CE_BY_PLAYER; j--)
-      {
-       if (HAS_CHANGE_EVENT(element, j - 2))
-       {
-         SET_CHANGE_EVENT(element, j - 2, FALSE);
-         SET_CHANGE_EVENT(element, j, TRUE);
-       }
-      }
-
-      /* order of checking events to be mapped is important */
-      for (j=CE_OTHER_GETS_COLLECTED; j >= CE_COLLISION; j--)
-      {
-       if (HAS_CHANGE_EVENT(element, j - 1))
-       {
-         SET_CHANGE_EVENT(element, j - 1, FALSE);
-         SET_CHANGE_EVENT(element, j, TRUE);
-       }
-      }
-    }
-  }
-
-  /* initialize "can_change" field for old levels with only one change page */
-  if (level->game_version <= VERSION_IDENT(3,0,2))
-  {
-    for (i=0; i < NUM_CUSTOM_ELEMENTS; i++)
-    {
-      int element = EL_CUSTOM_START + i;
-
-      if (CAN_CHANGE(element))
-       element_info[element].change->can_change = TRUE;
-    }
-  }
-
-  /* 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];
-
-  /* initialize level size variables for faster access */
-  lev_fieldx = level->fieldx;
-  lev_fieldy = level->fieldy;
-
-  /* determine border element for this level */
-  SetBorderElement();
-
-  /* initialize element properties for level editor etc. */
-  InitElementPropertiesEngine(level->game_version);
-}
-
-#endif
-
-void LoadLevelTemplate(int level_nr)
-{
-  char *filename = getLevelFilename(level_nr);
-
-  LoadLevelFromFilename(&level_template, filename);
-
-  LoadLevel_InitVersion(&level, filename);
-  LoadLevel_InitElements(&level, filename);
-
-  ActivateLevelTemplate();
-}
-
-void LoadLevel(int level_nr)
-{
-  char *filename = getLevelFilename(level_nr);
-
-  LoadLevelFromFilename(&level, filename);
-
-  if (level.use_custom_template)
-    LoadLevelTemplate(-1);
-
-#if 1
-  LoadLevel_InitVersion(&level, filename);
-  LoadLevel_InitElements(&level, filename);
-  LoadLevel_InitPlayfield(&level, filename);
-#else
-  LoadLevel_InitLevel(&level, filename);
-#endif
-}
-
-static void SaveLevel_VERS(FILE *file, struct LevelInfo *level)
-{
-  putFileVersion(file, level->file_version);
-  putFileVersion(file, level->game_version);
-}
-
-static void SaveLevel_HEAD(FILE *file, struct LevelInfo *level)
-{
-  int i, x, y;
-
-  putFile8Bit(file, level->fieldx);
-  putFile8Bit(file, level->fieldy);
-
-  putFile16BitBE(file, level->time);
-  putFile16BitBE(file, level->gems_needed);
-
-  for(i=0; i<MAX_LEVEL_NAME_LEN; i++)
+  for (i = 0; i < MAX_LEVEL_NAME_LEN; i++)
     putFile8Bit(file, level->name[i]);
 
-  for(i=0; i<LEVEL_SCORE_ELEMENTS; i++)
+  for (i = 0; i < LEVEL_SCORE_ELEMENTS; i++)
     putFile8Bit(file, level->score[i]);
 
-  for(i=0; i<STD_ELEMENT_CONTENTS; i++)
-    for(y=0; y<3; y++)
-      for(x=0; x<3; x++)
+  for (i = 0; i < STD_ELEMENT_CONTENTS; i++)
+    for (y = 0; y < 3; y++)
+      for (x = 0; x < 3; x++)
        putFile8Bit(file, (level->encoding_16bit_yamyam ? EL_EMPTY :
                           level->yamyam_content[i][x][y]));
   putFile8Bit(file, level->amoeba_speed);
@@ -1318,7 +1674,7 @@ static void SaveLevel_HEAD(FILE *file, struct LevelInfo *level)
   putFile8Bit(file, (level->encoding_16bit_amoeba ? EL_EMPTY :
                     level->amoeba_content));
   putFile8Bit(file, (level->double_speed ? 1 : 0));
-  putFile8Bit(file, (level->gravity ? 1 : 0));
+  putFile8Bit(file, (level->initial_gravity ? 1 : 0));
   putFile8Bit(file, (level->encoding_16bit_field ? 1 : 0));
   putFile8Bit(file, (level->em_slippery_gems ? 1 : 0));
 
@@ -1331,7 +1687,7 @@ static void SaveLevel_AUTH(FILE *file, struct LevelInfo *level)
 {
   int i;
 
-  for(i=0; i<MAX_LEVEL_AUTHOR_LEN; i++)
+  for (i = 0; i < MAX_LEVEL_AUTHOR_LEN; i++)
     putFile8Bit(file, level->author[i]);
 }
 
@@ -1339,8 +1695,8 @@ static void SaveLevel_BODY(FILE *file, struct LevelInfo *level)
 {
   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)
        putFile16BitBE(file, level->field[x][y]);
       else
@@ -1357,9 +1713,9 @@ static void SaveLevel_CONT(FILE *file, struct LevelInfo *level)
   putFile8Bit(file, 0);
   putFile8Bit(file, 0);
 
-  for(i=0; i<MAX_ELEMENT_CONTENTS; i++)
-    for(y=0; y<3; y++)
-      for(x=0; x<3; x++)
+  for (i = 0; i < MAX_ELEMENT_CONTENTS; i++)
+    for (y = 0; y < 3; y++)
+      for (x = 0; x < 3; x++)
        if (level->encoding_16bit_field)
          putFile16BitBE(file, level->yamyam_content[i][x][y]);
        else
@@ -1379,9 +1735,9 @@ static void SaveLevel_CNT2(FILE *file, struct LevelInfo *level, int element)
     content_xsize = 3;
     content_ysize = 3;
 
-    for(i=0; i<MAX_ELEMENT_CONTENTS; i++)
-      for(y=0; y<3; y++)
-       for(x=0; x<3; x++)
+    for (i = 0; i < MAX_ELEMENT_CONTENTS; i++)
+      for (y = 0; y < 3; y++)
+       for (x = 0; x < 3; x++)
          content_array[i][x][y] = level->yamyam_content[i][x][y];
   }
   else if (element == EL_BD_AMOEBA)
@@ -1390,9 +1746,9 @@ static void SaveLevel_CNT2(FILE *file, struct LevelInfo *level, int element)
     content_xsize = 1;
     content_ysize = 1;
 
-    for(i=0; i<MAX_ELEMENT_CONTENTS; i++)
-      for(y=0; y<3; y++)
-       for(x=0; x<3; x++)
+    for (i = 0; i < MAX_ELEMENT_CONTENTS; i++)
+      for (y = 0; y < 3; y++)
+       for (x = 0; x < 3; x++)
          content_array[i][x][y] = EL_EMPTY;
     content_array[0][0][0] = level->amoeba_content;
   }
@@ -1412,9 +1768,9 @@ static void SaveLevel_CNT2(FILE *file, struct LevelInfo *level, int element)
 
   WriteUnusedBytesToFile(file, LEVEL_CHUNK_CNT2_UNUSED);
 
-  for(i=0; i<MAX_ELEMENT_CONTENTS; i++)
-    for(y=0; y<3; y++)
-      for(x=0; x<3; x++)
+  for (i = 0; i < MAX_ELEMENT_CONTENTS; i++)
+    for (y = 0; y < 3; y++)
+      for (x = 0; x < 3; x++)
        putFile16BitBE(file, content_array[i][x][y]);
 }
 
@@ -1431,7 +1787,7 @@ static void SaveLevel_CNT3(FILE *file, struct LevelInfo *level, int element)
 
   WriteUnusedBytesToFile(file, LEVEL_CHUNK_CNT3_UNUSED);
 
-  for(i=0; i < envelope_len; i++)
+  for (i = 0; i < envelope_len; i++)
     putFile8Bit(file, level->envelope_text[envelope_nr][i]);
 }
 
@@ -1443,7 +1799,7 @@ static void SaveLevel_CUS1(FILE *file, struct LevelInfo *level,
 
   putFile16BitBE(file, num_changed_custom_elements);
 
-  for (i=0; i < NUM_CUSTOM_ELEMENTS; i++)
+  for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
   {
     int element = EL_CUSTOM_START + i;
 
@@ -1472,7 +1828,7 @@ static void SaveLevel_CUS2(FILE *file, struct LevelInfo *level,
 
   putFile16BitBE(file, num_changed_custom_elements);
 
-  for (i=0; i < NUM_CUSTOM_ELEMENTS; i++)
+  for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
   {
     int element = EL_CUSTOM_START + i;
 
@@ -1501,7 +1857,7 @@ static void SaveLevel_CUS3(FILE *file, struct LevelInfo *level,
 
   putFile16BitBE(file, num_changed_custom_elements);
 
-  for (i=0; i < NUM_CUSTOM_ELEMENTS; i++)
+  for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
   {
     int element = EL_CUSTOM_START + i;
 
@@ -1511,7 +1867,7 @@ static void SaveLevel_CUS3(FILE *file, struct LevelInfo *level,
       {
        putFile16BitBE(file, element);
 
-       for(j=0; j<MAX_ELEMENT_NAME_LEN; j++)
+       for (j = 0; j < MAX_ELEMENT_NAME_LEN; j++)
          putFile8Bit(file, element_info[element].description[j]);
 
        putFile32BitBE(file, Properties[element][EP_BITFIELD_BASE]);
@@ -1534,8 +1890,8 @@ static void SaveLevel_CUS3(FILE *file, struct LevelInfo *level,
        putFile8Bit(file, element_info[element].move_direction_initial);
        putFile8Bit(file, element_info[element].move_stepsize);
 
-       for(y=0; y<3; y++)
-         for(x=0; x<3; x++)
+       for (y = 0; y < 3; y++)
+         for (x = 0; x < 3; x++)
            putFile16BitBE(file, element_info[element].content[x][y]);
 
        putFile32BitBE(file, element_info[element].change->events);
@@ -1556,8 +1912,8 @@ static void SaveLevel_CUS3(FILE *file, struct LevelInfo *level,
        putFile8Bit(file, element_info[element].change->random);
        putFile8Bit(file, element_info[element].change->power);
 
-       for(y=0; y<3; y++)
-         for(x=0; x<3; x++)
+       for (y = 0; y < 3; y++)
+         for (x = 0; x < 3; x++)
            putFile16BitBE(file, element_info[element].change->content[x][y]);
 
        putFile8Bit(file, element_info[element].slippery_type);
@@ -1582,7 +1938,7 @@ static void SaveLevel_CUS4(FILE *file, struct LevelInfo *level, int element)
 
   putFile16BitBE(file, element);
 
-  for(i=0; i < MAX_ELEMENT_NAME_LEN; i++)
+  for (i = 0; i < MAX_ELEMENT_NAME_LEN; i++)
     putFile8Bit(file, ei->description[i]);
 
   putFile32BitBE(file, Properties[element][EP_BITFIELD_BASE]);
@@ -1612,8 +1968,8 @@ static void SaveLevel_CUS4(FILE *file, struct LevelInfo *level, int element)
 
   putFile8Bit(file, ei->slippery_type);
 
-  for(y=0; y<3; y++)
-    for(x=0; x<3; x++)
+  for (y = 0; y < 3; y++)
+    for (x = 0; x < 3; x++)
       putFile16BitBE(file, ei->content[x][y]);
 
   /* some free bytes for future custom property values and padding */
@@ -1621,7 +1977,7 @@ static void SaveLevel_CUS4(FILE *file, struct LevelInfo *level, int element)
 
   /* write change property values */
 
-  for (i=0; i < ei->num_change_pages; i++)
+  for (i = 0; i < ei->num_change_pages; i++)
   {
     struct ElementChangeInfo *change = &ei->change_page[i];
 
@@ -1643,14 +1999,16 @@ static void SaveLevel_CUS4(FILE *file, struct LevelInfo *level, int element)
     putFile8Bit(file, change->random);
     putFile8Bit(file, change->power);
 
-    for(y=0; y<3; y++)
-      for(x=0; x<3; x++)
+    for (y = 0; y < 3; y++)
+      for (x = 0; x < 3; x++)
        putFile16BitBE(file, change->content[x][y]);
 
     putFile8Bit(file, change->can_change);
 
+    putFile8Bit(file, change->sides);
+
     /* some free bytes for future change property values and padding */
-    WriteUnusedBytesToFile(file, 9);
+    WriteUnusedBytesToFile(file, 8);
   }
 }
 
@@ -1671,16 +2029,16 @@ static void SaveLevelFromFilename(struct LevelInfo *level, char *filename)
 
   /* check level field for 16-bit elements */
   level->encoding_16bit_field = FALSE;
-  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->field[x][y] > 255)
        level->encoding_16bit_field = TRUE;
 
   /* check yamyam content for 16-bit elements */
   level->encoding_16bit_yamyam = FALSE;
-  for(i=0; i<level->num_yamyam_contents; i++)
-    for(y=0; y<3; y++)
-      for(x=0; x<3; x++)
+  for (i = 0; i < level->num_yamyam_contents; i++)
+    for (y = 0; y < 3; y++)
+      for (x = 0; x < 3; x++)
        if (level->yamyam_content[i][x][y] > 255)
          level->encoding_16bit_yamyam = TRUE;
 
@@ -1722,7 +2080,7 @@ static void SaveLevelFromFilename(struct LevelInfo *level, char *filename)
   }
 
   /* check for envelope content */
-  for (i=0; i<4; i++)
+  for (i = 0; i < 4; i++)
   {
     if (strlen(level->envelope_text[i]) > 0)
     {
@@ -1736,7 +2094,7 @@ static void SaveLevelFromFilename(struct LevelInfo *level, char *filename)
   /* check for non-default custom elements (unless using template level) */
   if (!level->use_custom_template)
   {
-    for (i=0; i < NUM_CUSTOM_ELEMENTS; i++)
+    for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
     {
       int element = EL_CUSTOM_START + i;
 
@@ -1755,16 +2113,16 @@ static void SaveLevelFromFilename(struct LevelInfo *level, char *filename)
   SetFilePermissions(filename, PERMS_PRIVATE);
 }
 
-void SaveLevel(int level_nr)
+void SaveLevel(int nr)
 {
-  char *filename = getLevelFilename(level_nr);
+  char *filename = getDefaultLevelFilename(nr);
 
   SaveLevelFromFilename(&level, filename);
 }
 
 void SaveLevelTemplate()
 {
-  char *filename = getLevelFilename(-1);
+  char *filename = getDefaultLevelFilename(-1);
 
   SaveLevelFromFilename(&level, filename);
 }
@@ -1791,7 +2149,7 @@ void DumpLevel(struct LevelInfo *level)
   printf("\n");
   printf("Amoeba Speed: %d\n", level->amoeba_speed);
   printf("\n");
-  printf("Gravity:                %s\n", (level->gravity ? "yes" : "no"));
+  printf("Gravity:                %s\n", (level->initial_gravity ? "yes" : "no"));
   printf("Double Speed Movement:  %s\n", (level->double_speed ? "yes" : "no"));
   printf("EM style slippery gems: %s\n", (level->em_slippery_gems ? "yes" : "no"));
 
@@ -1812,7 +2170,7 @@ static void setTapeInfoToDefaults()
 
   /* default values (also for pre-1.2 tapes) with only the first player */
   tape.player_participates[0] = TRUE;
-  for(i=1; i<MAX_PLAYERS; i++)
+  for (i = 1; i < MAX_PLAYERS; i++)
     tape.player_participates[i] = FALSE;
 
   /* at least one (default: the first) player participates in every tape */
@@ -1851,7 +2209,7 @@ static int LoadTape_HEAD(FILE *file, int chunk_size, struct TapeInfo *tape)
 
     /* since version 1.2, tapes store which players participate in the tape */
     tape->num_participating_players = 0;
-    for(i=0; i<MAX_PLAYERS; i++)
+    for (i = 0; i < MAX_PLAYERS; i++)
     {
       tape->player_participates[i] = FALSE;
 
@@ -1884,7 +2242,7 @@ static int LoadTape_INFO(FILE *file, int chunk_size, struct TapeInfo *tape)
   tape->level_identifier =
     checked_realloc(tape->level_identifier, level_identifier_size);
 
-  for(i=0; i < level_identifier_size; i++)
+  for (i = 0; i < level_identifier_size; i++)
     tape->level_identifier[i] = getFile8Bit(file);
 
   tape->level_nr = getFile16BitBE(file);
@@ -1906,12 +2264,12 @@ static int LoadTape_BODY(FILE *file, int chunk_size, struct TapeInfo *tape)
     return chunk_size_expected;
   }
 
-  for(i=0; i<tape->length; i++)
+  for (i = 0; i < tape->length; i++)
   {
     if (i >= MAX_TAPELEN)
       break;
 
-    for(j=0; j<MAX_PLAYERS; j++)
+    for (j = 0; j < MAX_PLAYERS; j++)
     {
       tape->pos[i].action[j] = MV_NO_MOVING;
 
@@ -1930,7 +2288,7 @@ static int LoadTape_BODY(FILE *file, int chunk_size, struct TapeInfo *tape)
       byte action = tape->pos[i].action[0];
       int k, num_moves = 0;
 
-      for (k=0; k<4; k++)
+      for (k = 0; k<4; k++)
       {
        if (action & joy_dir[k])
        {
@@ -1959,7 +2317,7 @@ static int LoadTape_BODY(FILE *file, int chunk_size, struct TapeInfo *tape)
        tape->pos[i + 1].delay = 1;
 
        /* delay part */
-       for(j=0; j<MAX_PLAYERS; j++)
+       for (j = 0; j < MAX_PLAYERS; j++)
          tape->pos[i].action[j] = MV_NO_MOVING;
        tape->pos[i].delay--;
 
@@ -2096,14 +2454,21 @@ void LoadTapeFromFilename(char *filename)
   tape.length_seconds = GetTapeLength();
 
 #if 0
-  printf("tape game version: %d\n", tape.game_version);
-  printf("tape engine version: %d\n", tape.engine_version);
+  printf("::: tape game version: %d\n", tape.game_version);
+  printf("::: tape engine version: %d\n", tape.engine_version);
 #endif
 }
 
-void LoadTape(int level_nr)
+void LoadTape(int nr)
+{
+  char *filename = getTapeFilename(nr);
+
+  LoadTapeFromFilename(filename);
+}
+
+void LoadSolutionTape(int nr)
 {
-  char *filename = getTapeFilename(level_nr);
+  char *filename = getSolutionTapeFilename(nr);
 
   LoadTapeFromFilename(filename);
 }
@@ -2120,7 +2485,7 @@ static void SaveTape_HEAD(FILE *file, struct TapeInfo *tape)
   byte store_participating_players = 0;
 
   /* set bits for participating players for compact storage */
-  for(i=0; i<MAX_PLAYERS; i++)
+  for (i = 0; i < MAX_PLAYERS; i++)
     if (tape->player_participates[i])
       store_participating_players |= (1 << i);
 
@@ -2143,7 +2508,7 @@ static void SaveTape_INFO(FILE *file, struct TapeInfo *tape)
 
   putFile16BitBE(file, level_identifier_size);
 
-  for(i=0; i < level_identifier_size; i++)
+  for (i = 0; i < level_identifier_size; i++)
     putFile8Bit(file, tape->level_identifier[i]);
 
   putFile16BitBE(file, tape->level_nr);
@@ -2153,9 +2518,9 @@ static void SaveTape_BODY(FILE *file, struct TapeInfo *tape)
 {
   int i, j;
 
-  for(i=0; i<tape->length; i++)
+  for (i = 0; i < tape->length; i++)
   {
-    for(j=0; j<MAX_PLAYERS; j++)
+    for (j = 0; j < MAX_PLAYERS; j++)
       if (tape->player_participates[j])
        putFile8Bit(file, tape->pos[i].action[j]);
 
@@ -2163,9 +2528,9 @@ static void SaveTape_BODY(FILE *file, struct TapeInfo *tape)
   }
 }
 
-void SaveTape(int level_nr)
+void SaveTape(int nr)
 {
-  char *filename = getTapeFilename(level_nr);
+  char *filename = getTapeFilename(nr);
   FILE *file;
   boolean new_tape = TRUE;
   int num_participating_players = 0;
@@ -2193,7 +2558,7 @@ void SaveTape(int level_nr)
   tape.game_version = GAME_VERSION_ACTUAL;
 
   /* count number of participating players  */
-  for(i=0; i<MAX_PLAYERS; i++)
+  for (i = 0; i < MAX_PLAYERS; i++)
     if (tape.player_participates[i])
       num_participating_players++;
 
@@ -2241,14 +2606,14 @@ void DumpTape(struct TapeInfo *tape)
   printf("Level series identifier: '%s'\n", tape->level_identifier);
   printf_line("-", 79);
 
-  for(i=0; i<tape->length; i++)
+  for (i = 0; i < tape->length; i++)
   {
     if (i >= MAX_TAPELEN)
       break;
 
     printf("%03d: ", i);
 
-    for(j=0; j<MAX_PLAYERS; j++)
+    for (j = 0; j < MAX_PLAYERS; j++)
     {
       if (tape->player_participates[j])
       {
@@ -2276,17 +2641,17 @@ void DumpTape(struct TapeInfo *tape)
 /* score file functions                                                      */
 /* ========================================================================= */
 
-void LoadScore(int level_nr)
+void LoadScore(int nr)
 {
   int i;
-  char *filename = getScoreFilename(level_nr);
+  char *filename = getScoreFilename(nr);
   char cookie[MAX_LINE_LEN];
   char line[MAX_LINE_LEN];
   char *line_ptr;
   FILE *file;
 
   /* always start with reliable default values */
-  for(i=0; i<MAX_SCORE_ENTRIES; i++)
+  for (i = 0; i < MAX_SCORE_ENTRIES; i++)
   {
     strcpy(highscore[i].Name, EMPTY_PLAYER_NAME);
     highscore[i].Score = 0;
@@ -2307,7 +2672,7 @@ void LoadScore(int level_nr)
     return;
   }
 
-  for(i=0; i<MAX_SCORE_ENTRIES; i++)
+  for (i = 0; i < MAX_SCORE_ENTRIES; i++)
   {
     fscanf(file, "%d", &highscore[i].Score);
     fgets(line, MAX_LINE_LEN, file);
@@ -2329,23 +2694,23 @@ void LoadScore(int level_nr)
   fclose(file);
 }
 
-void SaveScore(int level_nr)
+void SaveScore(int nr)
 {
   int i;
-  char *filename = getScoreFilename(level_nr);
+  char *filename = getScoreFilename(nr);
   FILE *file;
 
   InitScoreDirectory(leveldir_current->filename);
 
   if (!(file = fopen(filename, MODE_WRITE)))
   {
-    Error(ERR_WARN, "cannot save score for level %d", level_nr);
+    Error(ERR_WARN, "cannot save score for level %d", nr);
     return;
   }
 
   fprintf(file, "%s\n\n", SCORE_COOKIE);
 
-  for(i=0; i<MAX_SCORE_ENTRIES; i++)
+  for (i = 0; i < MAX_SCORE_ENTRIES; i++)
     fprintf(file, "%d %s\n", highscore[i].Score, highscore[i].Name);
 
   fclose(file);
@@ -2398,8 +2763,9 @@ void SaveScore(int level_nr)
 #define SETUP_TOKEN_EDITOR_EL_CUSTOM           8
 #define SETUP_TOKEN_EDITOR_EL_CUSTOM_MORE      9
 #define SETUP_TOKEN_EDITOR_EL_HEADLINES                10
+#define SETUP_TOKEN_EDITOR_EL_USER_DEFINED     11
 
-#define NUM_EDITOR_SETUP_TOKENS                        11
+#define NUM_EDITOR_SETUP_TOKENS                        12
 
 /* shortcut setup */
 #define SETUP_TOKEN_SHORTCUT_SAVE_GAME         0
@@ -2418,13 +2784,13 @@ void SaveScore(int level_nr)
 #define SETUP_TOKEN_PLAYER_JOY_YMIDDLE         6
 #define SETUP_TOKEN_PLAYER_JOY_YLOWER          7
 #define SETUP_TOKEN_PLAYER_JOY_SNAP            8
-#define SETUP_TOKEN_PLAYER_JOY_BOMB            9
+#define SETUP_TOKEN_PLAYER_JOY_DROP            9
 #define SETUP_TOKEN_PLAYER_KEY_LEFT            10
 #define SETUP_TOKEN_PLAYER_KEY_RIGHT           11
 #define SETUP_TOKEN_PLAYER_KEY_UP              12
 #define SETUP_TOKEN_PLAYER_KEY_DOWN            13
 #define SETUP_TOKEN_PLAYER_KEY_SNAP            14
-#define SETUP_TOKEN_PLAYER_KEY_BOMB            15
+#define SETUP_TOKEN_PLAYER_KEY_DROP            15
 
 #define NUM_PLAYER_SETUP_TOKENS                        16
 
@@ -2486,6 +2852,7 @@ static struct TokenInfo editor_setup_tokens[] =
   { TYPE_SWITCH, &sei.el_custom,       "editor.el_custom"              },
   { TYPE_SWITCH, &sei.el_custom_more,  "editor.el_custom_more"         },
   { TYPE_SWITCH, &sei.el_headlines,    "editor.el_headlines"           },
+  { TYPE_SWITCH, &sei.el_user_defined, "editor.el_user_defined"        },
 };
 
 static struct TokenInfo shortcut_setup_tokens[] =
@@ -2506,13 +2873,13 @@ static struct TokenInfo player_setup_tokens[] =
   { 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.bomb,       ".joy.place_bomb"               },
+  { 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.bomb,       ".key.place_bomb"               }
+  { TYPE_KEY_X11, &sii.key.drop,       ".key.place_bomb"               }
 };
 
 static struct TokenInfo system_setup_tokens[] =
@@ -2584,12 +2951,13 @@ static void setSetupInfoToDefaults(struct SetupInfo *si)
   si->editor.el_custom_more = FALSE;
 
   si->editor.el_headlines = TRUE;
+  si->editor.el_user_defined = FALSE;
 
   si->shortcut.save_game = DEFAULT_KEY_SAVE_GAME;
   si->shortcut.load_game = DEFAULT_KEY_LOAD_GAME;
   si->shortcut.toggle_pause = DEFAULT_KEY_TOGGLE_PAUSE;
 
-  for (i=0; i<MAX_PLAYERS; i++)
+  for (i = 0; i < MAX_PLAYERS; i++)
   {
     si->input[i].use_joystick = FALSE;
     si->input[i].joy.device_name=getStringCopy(getDeviceNameFromJoystickNr(i));
@@ -2600,13 +2968,13 @@ static void setSetupInfoToDefaults(struct SetupInfo *si)
     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.bomb  = (i == 0 ? JOY_BUTTON_2 : 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.bomb  = (i == 0 ? DEFAULT_KEY_BOMB  : KSYM_UNDEFINED);
+    si->input[i].key.drop  = (i == 0 ? DEFAULT_KEY_DROP  : KSYM_UNDEFINED);
   }
 
   si->system.sdl_audiodriver = getStringCopy(ARG_DEFAULT);
@@ -2624,34 +2992,34 @@ static void decodeSetupFileHash(SetupFileHash *setup_file_hash)
 
   /* global setup */
   si = setup;
-  for (i=0; i<NUM_GLOBAL_SETUP_TOKENS; i++)
+  for (i = 0; i < NUM_GLOBAL_SETUP_TOKENS; i++)
     setSetupInfo(global_setup_tokens, i,
                 getHashEntry(setup_file_hash, global_setup_tokens[i].text));
   setup = si;
 
   /* editor setup */
   sei = setup.editor;
-  for (i=0; i<NUM_EDITOR_SETUP_TOKENS; i++)
+  for (i = 0; i < NUM_EDITOR_SETUP_TOKENS; i++)
     setSetupInfo(editor_setup_tokens, i,
                 getHashEntry(setup_file_hash,editor_setup_tokens[i].text));
   setup.editor = sei;
 
   /* shortcut setup */
   ssi = setup.shortcut;
-  for (i=0; i<NUM_SHORTCUT_SETUP_TOKENS; i++)
+  for (i = 0; i < NUM_SHORTCUT_SETUP_TOKENS; i++)
     setSetupInfo(shortcut_setup_tokens, i,
                 getHashEntry(setup_file_hash,shortcut_setup_tokens[i].text));
   setup.shortcut = ssi;
 
   /* player setup */
-  for (pnr=0; pnr<MAX_PLAYERS; pnr++)
+  for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
   {
     char prefix[30];
 
     sprintf(prefix, "%s%d", TOKEN_STR_PLAYER_PREFIX, pnr + 1);
 
     sii = setup.input[pnr];
-    for (i=0; i<NUM_PLAYER_SETUP_TOKENS; i++)
+    for (i = 0; i < NUM_PLAYER_SETUP_TOKENS; i++)
     {
       char full_token[100];
 
@@ -2664,14 +3032,14 @@ static void decodeSetupFileHash(SetupFileHash *setup_file_hash)
 
   /* system setup */
   syi = setup.system;
-  for (i=0; i<NUM_SYSTEM_SETUP_TOKENS; i++)
+  for (i = 0; i < NUM_SYSTEM_SETUP_TOKENS; i++)
     setSetupInfo(system_setup_tokens, i,
                 getHashEntry(setup_file_hash, system_setup_tokens[i].text));
   setup.system = syi;
 
   /* options setup */
   soi = setup.options;
-  for (i=0; i<NUM_OPTIONS_SETUP_TOKENS; i++)
+  for (i = 0; i < NUM_OPTIONS_SETUP_TOKENS; i++)
     setSetupInfo(options_setup_tokens, i,
                 getHashEntry(setup_file_hash, options_setup_tokens[i].text));
   setup.options = soi;
@@ -2727,7 +3095,7 @@ void SaveSetup()
 
   /* global setup */
   si = setup;
-  for (i=0; i<NUM_GLOBAL_SETUP_TOKENS; i++)
+  for (i = 0; i < NUM_GLOBAL_SETUP_TOKENS; i++)
   {
     /* just to make things nicer :) */
     if (i == SETUP_TOKEN_PLAYER_NAME + 1 ||
@@ -2740,17 +3108,17 @@ void SaveSetup()
   /* editor setup */
   sei = setup.editor;
   fprintf(file, "\n");
-  for (i=0; i<NUM_EDITOR_SETUP_TOKENS; i++)
+  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++)
+  for (i = 0; i < NUM_SHORTCUT_SETUP_TOKENS; i++)
     fprintf(file, "%s\n", getSetupLine(shortcut_setup_tokens, "", i));
 
   /* player setup */
-  for (pnr=0; pnr<MAX_PLAYERS; pnr++)
+  for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
   {
     char prefix[30];
 
@@ -2758,20 +3126,20 @@ void SaveSetup()
     fprintf(file, "\n");
 
     sii = setup.input[pnr];
-    for (i=0; i<NUM_PLAYER_SETUP_TOKENS; i++)
+    for (i = 0; i < NUM_PLAYER_SETUP_TOKENS; i++)
       fprintf(file, "%s\n", getSetupLine(player_setup_tokens, prefix, i));
   }
 
   /* system setup */
   syi = setup.system;
   fprintf(file, "\n");
-  for (i=0; i<NUM_SYSTEM_SETUP_TOKENS; i++)
+  for (i = 0; i < NUM_SYSTEM_SETUP_TOKENS; i++)
     fprintf(file, "%s\n", getSetupLine(system_setup_tokens, "", i));
 
   /* options setup */
   soi = setup.options;
   fprintf(file, "\n");
-  for (i=0; i<NUM_OPTIONS_SETUP_TOKENS; i++)
+  for (i = 0; i < NUM_OPTIONS_SETUP_TOKENS; i++)
     fprintf(file, "%s\n", getSetupLine(options_setup_tokens, "", i));
 
   fclose(file);
@@ -2785,7 +3153,7 @@ void LoadCustomElementDescriptions()
   SetupFileHash *setup_file_hash;
   int i;
 
-  for (i=0; i<NUM_FILE_ELEMENTS; i++)
+  for (i = 0; i < NUM_FILE_ELEMENTS; i++)
   {
     if (element_info[i].custom_description != NULL)
     {
@@ -2797,7 +3165,7 @@ void LoadCustomElementDescriptions()
   if ((setup_file_hash = loadSetupFileHash(filename)) == NULL)
     return;
 
-  for (i=0; i<NUM_FILE_ELEMENTS; i++)
+  for (i = 0; i < NUM_FILE_ELEMENTS; i++)
   {
     char *token = getStringCat2(element_info[i].token_name, ".name");
     char *value = getHashEntry(setup_file_hash, token);
@@ -2818,8 +3186,8 @@ void LoadSpecialMenuDesignSettings()
   int i, j;
 
   /* always start with reliable default values from default config */
-  for (i=0; image_config_vars[i].token != NULL; i++)
-    for (j=0; image_config[j].token != NULL; j++)
+  for (i = 0; image_config_vars[i].token != NULL; i++)
+    for (j = 0; image_config[j].token != NULL; j++)
       if (strcmp(image_config_vars[i].token, image_config[j].token) == 0)
        *image_config_vars[i].value =
          get_auto_parameter_value(image_config_vars[i].token,
@@ -2829,7 +3197,7 @@ void LoadSpecialMenuDesignSettings()
     return;
 
   /* special case: initialize with default values that may be overwritten */
-  for (i=0; i < NUM_SPECIAL_GFX_ARGS; i++)
+  for (i = 0; i < NUM_SPECIAL_GFX_ARGS; i++)
   {
     char *value_x = getHashEntry(setup_file_hash, "menu.draw_xoffset");
     char *value_y = getHashEntry(setup_file_hash, "menu.draw_yoffset");
@@ -2844,7 +3212,7 @@ void LoadSpecialMenuDesignSettings()
   }
 
   /* read (and overwrite with) values that may be specified in config file */
-  for (i=0; image_config_vars[i].token != NULL; i++)
+  for (i = 0; image_config_vars[i].token != NULL; i++)
   {
     char *value = getHashEntry(setup_file_hash, image_config_vars[i].token);
 
@@ -2855,3 +3223,625 @@ void LoadSpecialMenuDesignSettings()
 
   freeSetupFileHash(setup_file_hash);
 }
+
+void LoadUserDefinedEditorElementList(int **elements, int *num_elements)
+{
+  char *filename = getEditorSetupFilename();
+  SetupFileList *setup_file_list, *list;
+  SetupFileHash *element_hash;
+  int num_unknown_tokens = 0;
+  int i;
+
+  if ((setup_file_list = loadSetupFileList(filename)) == NULL)
+    return;
+
+  element_hash = newSetupFileHash();
+
+  for (i = 0; i < NUM_FILE_ELEMENTS; i++)
+    setHashEntry(element_hash, element_info[i].token_name, i_to_a(i));
+
+  /* determined size may be larger than needed (due to unknown elements) */
+  *num_elements = 0;
+  for (list = setup_file_list; list != NULL; list = list->next)
+    (*num_elements)++;
+
+  /* add space for up to 3 more elements for padding that may be needed */
+  *num_elements += 3;
+
+  *elements = checked_malloc(*num_elements * sizeof(int));
+
+  *num_elements = 0;
+  for (list = setup_file_list; list != NULL; list = list->next)
+  {
+    char *value = getHashEntry(element_hash, list->token);
+
+    if (value)
+    {
+      (*elements)[(*num_elements)++] = atoi(value);
+    }
+    else
+    {
+      if (num_unknown_tokens == 0)
+      {
+       Error(ERR_RETURN_LINE, "-");
+       Error(ERR_RETURN, "warning: unknown token(s) found in config file:");
+       Error(ERR_RETURN, "- config file: '%s'", filename);
+
+       num_unknown_tokens++;
+      }
+
+      Error(ERR_RETURN, "- token: '%s'", list->token);
+    }
+  }
+
+  if (num_unknown_tokens > 0)
+    Error(ERR_RETURN_LINE, "-");
+
+  while (*num_elements % 4)    /* pad with empty elements, if needed */
+    (*elements)[(*num_elements)++] = EL_EMPTY;
+
+  freeSetupFileList(setup_file_list);
+  freeSetupFileHash(element_hash);
+
+#if 0
+  /* TEST-ONLY */
+  for (i = 0; i < *num_elements; i++)
+    printf("editor: element '%s' [%d]\n",
+          element_info[(*elements)[i]].token_name, (*elements)[i]);
+#endif
+}
+
+static struct MusicFileInfo *get_music_file_info_ext(char *basename, int music,
+                                                    boolean is_sound)
+{
+  SetupFileHash *setup_file_hash = NULL;
+  struct MusicFileInfo tmp_music_file_info, *new_music_file_info;
+  char *filename_music, *filename_prefix, *filename_info;
+  struct
+  {
+    char *token;
+    char **value_ptr;
+  }
+  token_to_value_ptr[] =
+  {
+    { "title_header",  &tmp_music_file_info.title_header       },
+    { "artist_header", &tmp_music_file_info.artist_header      },
+    { "album_header",  &tmp_music_file_info.album_header       },
+    { "year_header",   &tmp_music_file_info.year_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               },
+
+    { NULL,            NULL                                    },
+  };
+  int i;
+
+  filename_music = (is_sound ? getCustomSoundFilename(basename) :
+                   getCustomMusicFilename(basename));
+
+  if (filename_music == NULL)
+    return NULL;
+
+  /* ---------- try to replace file extension ---------- */
+
+  filename_prefix = getStringCopy(filename_music);
+  if (strrchr(filename_prefix, '.') != NULL)
+    *strrchr(filename_prefix, '.') = '\0';
+  filename_info = getStringCat2(filename_prefix, ".txt");
+
+#if 0
+  printf("trying to load file '%s'...\n", filename_info);
+#endif
+
+  if (fileExists(filename_info))
+    setup_file_hash = loadSetupFileHash(filename_info);
+
+  free(filename_prefix);
+  free(filename_info);
+
+  if (setup_file_hash == NULL)
+  {
+    /* ---------- try to add file extension ---------- */
+
+    filename_prefix = getStringCopy(filename_music);
+    filename_info = getStringCat2(filename_prefix, ".txt");
+
+#if 0
+    printf("trying to load file '%s'...\n", filename_info);
+#endif
+
+    if (fileExists(filename_info))
+      setup_file_hash = loadSetupFileHash(filename_info);
+
+    free(filename_prefix);
+    free(filename_info);
+  }
+
+  if (setup_file_hash == NULL)
+    return NULL;
+
+  /* ---------- music file info found ---------- */
+
+  memset(&tmp_music_file_info, 0, sizeof(struct MusicFileInfo));
+
+  for (i = 0; token_to_value_ptr[i].token != NULL; i++)
+  {
+    char *value = getHashEntry(setup_file_hash, token_to_value_ptr[i].token);
+
+    *token_to_value_ptr[i].value_ptr =
+      getStringCopy(value != NULL && *value != '\0' ? value : UNKNOWN_NAME);
+  }
+
+  tmp_music_file_info.basename = getStringCopy(basename);
+  tmp_music_file_info.music = music;
+  tmp_music_file_info.is_sound = is_sound;
+
+  new_music_file_info = checked_malloc(sizeof(struct MusicFileInfo));
+  *new_music_file_info = tmp_music_file_info;
+
+  return new_music_file_info;
+}
+
+static struct MusicFileInfo *get_music_file_info(char *basename, int music)
+{
+  return get_music_file_info_ext(basename, music, FALSE);
+}
+
+static struct MusicFileInfo *get_sound_file_info(char *basename, int sound)
+{
+  return get_music_file_info_ext(basename, sound, TRUE);
+}
+
+static boolean music_info_listed_ext(struct MusicFileInfo *list,
+                                    char *basename, boolean is_sound)
+{
+  for (; list != NULL; list = list->next)
+    if (list->is_sound == is_sound && strcmp(list->basename, basename) == 0)
+      return TRUE;
+
+  return FALSE;
+}
+
+static boolean music_info_listed(struct MusicFileInfo *list, char *basename)
+{
+  return music_info_listed_ext(list, basename, FALSE);
+}
+
+static boolean sound_info_listed(struct MusicFileInfo *list, char *basename)
+{
+  return music_info_listed_ext(list, basename, TRUE);
+}
+
+void LoadMusicInfo()
+{
+  char *music_directory = getCustomMusicDirectory();
+  int num_music = getMusicListSize();
+  int num_music_noconf = 0;
+  int num_sounds = getSoundListSize();
+  DIR *dir;
+  struct dirent *dir_entry;
+  struct FileInfo *music, *sound;
+  struct MusicFileInfo *next, **new;
+  int i;
+
+  while (music_file_info != NULL)
+  {
+    next = music_file_info->next;
+
+    checked_free(music_file_info->basename);
+
+    checked_free(music_file_info->title_header);
+    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->title);
+    checked_free(music_file_info->artist);
+    checked_free(music_file_info->album);
+    checked_free(music_file_info->year);
+
+    free(music_file_info);
+
+    music_file_info = next;
+  }
+
+  new = &music_file_info;
+
+#if 0
+  printf("::: num_music == %d\n", num_music);
+#endif
+
+  for (i = 0; i < num_music; i++)
+  {
+    music = getMusicListEntry(i);
+
+#if 0
+    printf("::: %d [%08x]\n", i, music->filename);
+#endif
+
+    if (music->filename == NULL)
+      continue;
+
+    if (strcmp(music->filename, UNDEFINED_FILENAME) == 0)
+      continue;
+
+    /* a configured file may be not recognized as music */
+    if (!FileIsMusic(music->filename))
+      continue;
+
+#if 0
+    printf("::: -> '%s' (configured)\n", music->filename);
+#endif
+
+    if (!music_info_listed(music_file_info, music->filename))
+    {
+      *new = get_music_file_info(music->filename, i);
+      if (*new != NULL)
+       new = &(*new)->next;
+    }
+  }
+
+  if ((dir = opendir(music_directory)) == NULL)
+  {
+    Error(ERR_WARN, "cannot read music directory '%s'", music_directory);
+    return;
+  }
+
+  while ((dir_entry = readdir(dir)) != NULL)   /* loop until last dir entry */
+  {
+    char *basename = dir_entry->d_name;
+    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;
+
+      if (strcmp(basename, music->filename) == 0)
+      {
+       music_already_used = TRUE;
+       break;
+      }
+    }
+
+    if (music_already_used)
+      continue;
+
+    if (!FileIsMusic(basename))
+      continue;
+
+#if 0
+    printf("::: -> '%s' (found in directory)\n", basename);
+#endif
+
+    if (!music_info_listed(music_file_info, basename))
+    {
+      *new = get_music_file_info(basename, MAP_NOCONF_MUSIC(num_music_noconf));
+      if (*new != NULL)
+       new = &(*new)->next;
+    }
+
+    num_music_noconf++;
+  }
+
+  closedir(dir);
+
+  for (i = 0; i < num_sounds; i++)
+  {
+    sound = getSoundListEntry(i);
+
+    if (sound->filename == NULL)
+      continue;
+
+    if (strcmp(sound->filename, UNDEFINED_FILENAME) == 0)
+      continue;
+
+    /* a configured file may be not recognized as sound */
+    if (!FileIsSound(sound->filename))
+      continue;
+
+#if 0
+    printf("::: -> '%s' (configured)\n", sound->filename);
+#endif
+
+    if (!sound_info_listed(music_file_info, sound->filename))
+    {
+      *new = get_sound_file_info(sound->filename, i);
+      if (*new != NULL)
+       new = &(*new)->next;
+    }
+  }
+
+#if 0
+  /* TEST-ONLY */
+  for (next = music_file_info; next != NULL; next = next->next)
+    printf("::: title == '%s'\n", next->title);
+#endif
+}
+
+void add_helpanim_entry(int element, int action, int direction, int delay,
+                       int *num_list_entries)
+{
+  struct HelpAnimInfo *new_list_entry;
+  (*num_list_entries)++;
+
+  helpanim_info =
+    checked_realloc(helpanim_info,
+                   *num_list_entries * sizeof(struct HelpAnimInfo));
+  new_list_entry = &helpanim_info[*num_list_entries - 1];
+
+  new_list_entry->element = element;
+  new_list_entry->action = action;
+  new_list_entry->direction = direction;
+  new_list_entry->delay = delay;
+}
+
+void print_unknown_token(char *filename, char *token, int token_nr)
+{
+  if (token_nr == 0)
+  {
+    Error(ERR_RETURN_LINE, "-");
+    Error(ERR_RETURN, "warning: unknown token(s) found in config file:");
+    Error(ERR_RETURN, "- config file: '%s'", filename);
+  }
+
+  Error(ERR_RETURN, "- token: '%s'", token);
+}
+
+void print_unknown_token_end(int token_nr)
+{
+  if (token_nr > 0)
+    Error(ERR_RETURN_LINE, "-");
+}
+
+void LoadHelpAnimInfo()
+{
+  char *filename = getHelpAnimFilename();
+  SetupFileList *setup_file_list = NULL, *list;
+  SetupFileHash *element_hash, *action_hash, *direction_hash;
+  int num_list_entries = 0;
+  int num_unknown_tokens = 0;
+  int i;
+
+  if (fileExists(filename))
+    setup_file_list = loadSetupFileList(filename);
+
+  if (setup_file_list == NULL)
+  {
+    /* use reliable default values from static configuration */
+    SetupFileList *insert_ptr;
+
+    insert_ptr = setup_file_list =
+      newSetupFileList(helpanim_config[0].token,
+                      helpanim_config[0].value);
+
+    for (i = 1; helpanim_config[i].token; i++)
+      insert_ptr = addListEntry(insert_ptr,
+                               helpanim_config[i].token,
+                               helpanim_config[i].value);
+  }
+
+  element_hash   = newSetupFileHash();
+  action_hash    = newSetupFileHash();
+  direction_hash = newSetupFileHash();
+
+  for (i = 0; i < MAX_NUM_ELEMENTS; i++)
+    setHashEntry(element_hash, element_info[i].token_name, i_to_a(i));
+
+  for (i = 0; i < NUM_ACTIONS; i++)
+    setHashEntry(action_hash, element_action_info[i].suffix,
+                i_to_a(element_action_info[i].value));
+
+  /* do not store direction index (bit) here, but direction value! */
+  for (i = 0; i < NUM_DIRECTIONS; i++)
+    setHashEntry(direction_hash, element_direction_info[i].suffix,
+                i_to_a(1 << element_direction_info[i].value));
+
+  for (list = setup_file_list; list != NULL; list = list->next)
+  {
+    char *element_token, *action_token, *direction_token;
+    char *element_value, *action_value, *direction_value;
+    int delay = atoi(list->value);
+
+    if (strcmp(list->token, "end") == 0)
+    {
+      add_helpanim_entry(HELPANIM_LIST_NEXT, -1, -1, -1, &num_list_entries);
+
+      continue;
+    }
+
+    /* first try to break element into element/action/direction parts;
+       if this does not work, also accept combined "element[.act][.dir]"
+       elements (like "dynamite.active"), which are unique elements */
+
+    if (strchr(list->token, '.') == NULL)      /* token contains no '.' */
+    {
+      element_value = getHashEntry(element_hash, list->token);
+      if (element_value != NULL)       /* element found */
+       add_helpanim_entry(atoi(element_value), -1, -1, delay,
+                          &num_list_entries);
+      else
+      {
+       /* no further suffixes found -- this is not an element */
+       print_unknown_token(filename, list->token, num_unknown_tokens++);
+      }
+
+      continue;
+    }
+
+    /* token has format "<prefix>.<something>" */
+
+    action_token = strchr(list->token, '.');   /* suffix may be action ... */
+    direction_token = action_token;            /* ... or direction */
+
+    element_token = getStringCopy(list->token);
+    *strchr(element_token, '.') = '\0';
+
+    element_value = getHashEntry(element_hash, element_token);
+
+    if (element_value == NULL)         /* this is no element */
+    {
+      element_value = getHashEntry(element_hash, list->token);
+      if (element_value != NULL)       /* combined element found */
+       add_helpanim_entry(atoi(element_value), -1, -1, delay,
+                          &num_list_entries);
+      else
+       print_unknown_token(filename, list->token, num_unknown_tokens++);
+
+      free(element_token);
+
+      continue;
+    }
+
+    action_value = getHashEntry(action_hash, action_token);
+
+    if (action_value != NULL)          /* action found */
+    {
+      add_helpanim_entry(atoi(element_value), atoi(action_value), -1, delay,
+                   &num_list_entries);
+
+      free(element_token);
+
+      continue;
+    }
+
+    direction_value = getHashEntry(direction_hash, direction_token);
+
+    if (direction_value != NULL)       /* direction found */
+    {
+      add_helpanim_entry(atoi(element_value), -1, atoi(direction_value), delay,
+                        &num_list_entries);
+
+      free(element_token);
+
+      continue;
+    }
+
+    if (strchr(action_token + 1, '.') == NULL)
+    {
+      /* no further suffixes found -- this is not an action nor direction */
+
+      element_value = getHashEntry(element_hash, list->token);
+      if (element_value != NULL)       /* combined element found */
+       add_helpanim_entry(atoi(element_value), -1, -1, delay,
+                          &num_list_entries);
+      else
+       print_unknown_token(filename, list->token, num_unknown_tokens++);
+
+      free(element_token);
+
+      continue;
+    }
+
+    /* token has format "<prefix>.<suffix>.<something>" */
+
+    direction_token = strchr(action_token + 1, '.');
+
+    action_token = getStringCopy(action_token);
+    *strchr(action_token + 1, '.') = '\0';
+
+    action_value = getHashEntry(action_hash, action_token);
+
+    if (action_value == NULL)          /* this is no action */
+    {
+      element_value = getHashEntry(element_hash, list->token);
+      if (element_value != NULL)       /* combined element found */
+       add_helpanim_entry(atoi(element_value), -1, -1, delay,
+                          &num_list_entries);
+      else
+       print_unknown_token(filename, list->token, num_unknown_tokens++);
+
+      free(element_token);
+      free(action_token);
+
+      continue;
+    }
+
+    direction_value = getHashEntry(direction_hash, direction_token);
+
+    if (direction_value != NULL)       /* direction found */
+    {
+      add_helpanim_entry(atoi(element_value), atoi(action_value),
+                        atoi(direction_value), delay, &num_list_entries);
+
+      free(element_token);
+      free(action_token);
+
+      continue;
+    }
+
+    /* this is no direction */
+
+    element_value = getHashEntry(element_hash, list->token);
+    if (element_value != NULL)         /* combined element found */
+      add_helpanim_entry(atoi(element_value), -1, -1, delay,
+                        &num_list_entries);
+    else
+      print_unknown_token(filename, list->token, num_unknown_tokens++);
+
+    free(element_token);
+    free(action_token);
+  }
+
+  print_unknown_token_end(num_unknown_tokens);
+
+  add_helpanim_entry(HELPANIM_LIST_NEXT, -1, -1, -1, &num_list_entries);
+  add_helpanim_entry(HELPANIM_LIST_END,  -1, -1, -1, &num_list_entries);
+
+  freeSetupFileList(setup_file_list);
+  freeSetupFileHash(element_hash);
+  freeSetupFileHash(action_hash);
+  freeSetupFileHash(direction_hash);
+
+#if 0
+  /* TEST ONLY */
+  for (i = 0; i < num_list_entries; i++)
+    printf("::: %d, %d, %d => %d\n",
+          helpanim_info[i].element,
+          helpanim_info[i].action,
+          helpanim_info[i].direction,
+          helpanim_info[i].delay);
+#endif
+}
+
+void LoadHelpTextInfo()
+{
+  char *filename = getHelpTextFilename();
+  int i;
+
+  if (helptext_info != NULL)
+  {
+    freeSetupFileHash(helptext_info);
+    helptext_info = NULL;
+  }
+
+  if (fileExists(filename))
+    helptext_info = loadSetupFileHash(filename);
+
+  if (helptext_info == NULL)
+  {
+    /* use reliable default values from static configuration */
+    helptext_info = newSetupFileHash();
+
+    for (i = 0; helptext_config[i].token; i++)
+      setHashEntry(helptext_info,
+                  helptext_config[i].token,
+                  helptext_config[i].value);
+  }
+
+#if 0
+  /* TEST ONLY */
+  BEGIN_HASH_ITERATION(helptext_info, itr)
+  {
+    printf("::: '%s' => '%s'\n",
+          HASH_ITERATION_TOKEN(itr), HASH_ITERATION_VALUE(itr));
+  }
+  END_HASH_ITERATION(hash, itr)
+#endif
+}