rnd-20030620-1-src
[rocksndiamonds.git] / src / files.c
index fad45178abaf3bf6c61eb9567df2b2e97bdd5dc9..19f536d9f2498e18bdbe9ca11b36211757fb37ba 100644 (file)
@@ -17,6 +17,7 @@
 #include "libgame/libgame.h"
 
 #include "files.h"
+#include "init.h"
 #include "tools.h"
 #include "tape.h"
 
@@ -44,7 +45,7 @@
 
 static void setLevelInfoToDefaults()
 {
-  int i, x, y;
+  int i, j, x, y;
 
   level.file_version = FILE_VERSION_ACTUAL;
   level.game_version = GAME_VERSION_ACTUAL;
@@ -83,19 +84,54 @@ static void setLevelInfoToDefaults()
   for(i=0; i<LEVEL_SCORE_ELEMENTS; i++)
     level.score[i] = 10;
 
-  level.num_yam_contents = STD_ELEMENT_CONTENTS;
+  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++)
-       level.yam_content[i][x][y] =
+       level.yamyam_content[i][x][y] =
          (i < STD_ELEMENT_CONTENTS ? EL_ROCK : EL_EMPTY);
 
-  Feld[0][0] = Ur[0][0] = EL_PLAYER1;
+  Feld[0][0] = Ur[0][0] = EL_PLAYER_1;
   Feld[STD_LEV_FIELDX-1][STD_LEV_FIELDY-1] =
     Ur[STD_LEV_FIELDX-1][STD_LEV_FIELDY-1] = EL_EXIT_CLOSED;
 
+  for (i=0; i < NUM_CUSTOM_ELEMENTS; i++)
+  {
+    int element = EL_CUSTOM_START + i;
+
+    element_info[element].use_gfx_element = FALSE;
+    element_info[element].gfx_element = EL_EMPTY_SPACE;
+
+    element_info[element].push_delay_fixed = 2;                /* special default */
+    element_info[element].push_delay_random = 8;       /* special default */
+    element_info[element].move_delay_fixed = 0;
+    element_info[element].move_delay_random = 0;
+
+    element_info[element].move_pattern = MV_ALL_DIRECTIONS;
+    element_info[element].move_direction_initial = MV_NO_MOVING;
+    element_info[element].move_stepsize = TILEX / 8;
+
+    for(x=0; x<3; x++)
+      for(y=0; y<3; y++)
+       element_info[element].content[x][y] = EL_EMPTY_SPACE;
+
+    element_info[element].change.events = CE_BITMASK_DEFAULT;
+    element_info[element].change.delay_fixed = 0;
+    element_info[element].change.delay_random = 0;
+    element_info[element].change.successor = EL_EMPTY_SPACE;
+
+    /* start with no properties at all */
+    for (j=0; j < NUM_EP_BITFIELDS; j++)
+      Properties[element][j] = EP_BITMASK_DEFAULT;
+  }
+
   BorderElement = EL_STEELWALL;
 
+  level.no_level_file = FALSE;
+
+  if (leveldir_current == NULL)                /* only when dumping level */
+    return;
+
   /* try to determine better author name than 'anonymous' */
   if (strcmp(leveldir_current->author, ANONYMOUS_NAME) != 0)
   {
@@ -125,8 +161,6 @@ static void setLevelInfoToDefaults()
        break;
     }
   }
-
-  level.no_level_file = FALSE;
 }
 
 static int checkLevelElement(int element)
@@ -137,9 +171,9 @@ static int checkLevelElement(int element)
     element = EL_CHAR_QUESTION;
   }
   else if (element == EL_PLAYER_OBSOLETE)
-    element = EL_PLAYER1;
+    element = EL_PLAYER_1;
   else if (element == EL_KEY_OBSOLETE)
-    element = EL_KEY1;
+    element = EL_KEY_1;
 
   return element;
 }
@@ -169,11 +203,11 @@ static int LoadLevel_HEAD(FILE *file, int chunk_size, struct LevelInfo *level)
   for(i=0; i<LEVEL_SCORE_ELEMENTS; i++)
     level->score[i] = fgetc(file);
 
-  level->num_yam_contents = STD_ELEMENT_CONTENTS;
+  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++)
-       level->yam_content[i][x][y] = checkLevelElement(fgetc(file));
+       level->yamyam_content[i][x][y] = checkLevelElement(fgetc(file));
 
   level->amoeba_speed          = fgetc(file);
   level->time_magic_wall       = fgetc(file);
@@ -200,12 +234,10 @@ static int LoadLevel_AUTH(FILE *file, int chunk_size, struct LevelInfo *level)
   return chunk_size;
 }
 
-static int LoadLevel_CONT(FILE *file, int chunk_size, struct LevelInfo *level)
+static int LoadLevel_BODY(FILE *file, int chunk_size, struct LevelInfo *level)
 {
-  int i, x, y;
-  int header_size = 4;
-  int content_size = MAX_ELEMENT_CONTENTS * 3 * 3;
-  int chunk_size_expected = header_size + content_size;
+  int x, y;
+  int chunk_size_expected = level->fieldx * level->fieldy;
 
   /* Note: "chunk_size" was wrong before version 2.0 when elements are
      stored with 16-bit encoding (and should be twice as big then).
@@ -213,7 +245,7 @@ static int LoadLevel_CONT(FILE *file, int chunk_size, struct LevelInfo *level)
      contained 16-bit elements and vice versa. */
 
   if (level->encoding_16bit_field && level->file_version >= FILE_VERSION_2_0)
-    chunk_size_expected += content_size;
+    chunk_size_expected *= 2;
 
   if (chunk_size_expected != chunk_size)
   {
@@ -221,29 +253,20 @@ static int LoadLevel_CONT(FILE *file, int chunk_size, struct LevelInfo *level)
     return chunk_size_expected;
   }
 
-  fgetc(file);
-  level->num_yam_contents = fgetc(file);
-  fgetc(file);
-  fgetc(file);
-
-  /* correct invalid number of content fields -- should never happen */
-  if (level->num_yam_contents < 1 ||
-      level->num_yam_contents > MAX_ELEMENT_CONTENTS)
-    level->num_yam_contents = STD_ELEMENT_CONTENTS;
-
-  for(i=0; i<MAX_ELEMENT_CONTENTS; i++)
-    for(y=0; y<3; y++)
-      for(x=0; x<3; x++)
-       level->yam_content[i][x][y] =
-         checkLevelElement(level->encoding_16bit_field ?
-                           getFile16BitBE(file) : fgetc(file));
+  for(y=0; y<level->fieldy; y++)
+    for(x=0; x<level->fieldx; x++)
+      Feld[x][y] = Ur[x][y] =
+       checkLevelElement(level->encoding_16bit_field ?
+                         getFile16BitBE(file) : fgetc(file));
   return chunk_size;
 }
 
-static int LoadLevel_BODY(FILE *file, int chunk_size, struct LevelInfo *level)
+static int LoadLevel_CONT(FILE *file, int chunk_size, struct LevelInfo *level)
 {
-  int x, y;
-  int chunk_size_expected = level->fieldx * level->fieldy;
+  int i, x, y;
+  int header_size = 4;
+  int content_size = MAX_ELEMENT_CONTENTS * 3 * 3;
+  int chunk_size_expected = header_size + content_size;
 
   /* Note: "chunk_size" was wrong before version 2.0 when elements are
      stored with 16-bit encoding (and should be twice as big then).
@@ -251,7 +274,7 @@ static int LoadLevel_BODY(FILE *file, int chunk_size, struct LevelInfo *level)
      contained 16-bit elements and vice versa. */
 
   if (level->encoding_16bit_field && level->file_version >= FILE_VERSION_2_0)
-    chunk_size_expected *= 2;
+    chunk_size_expected += content_size;
 
   if (chunk_size_expected != chunk_size)
   {
@@ -259,11 +282,22 @@ 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++)
-      Feld[x][y] = Ur[x][y] =
-       checkLevelElement(level->encoding_16bit_field ?
-                         getFile16BitBE(file) : fgetc(file));
+  fgetc(file);
+  level->num_yamyam_contents = fgetc(file);
+  fgetc(file);
+  fgetc(file);
+
+  /* correct invalid number of content fields -- should never happen */
+  if (level->num_yamyam_contents < 1 ||
+      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++)
+       level->yamyam_content[i][x][y] =
+         checkLevelElement(level->encoding_16bit_field ?
+                           getFile16BitBE(file) : fgetc(file));
   return chunk_size;
 }
 
@@ -291,12 +325,12 @@ static int LoadLevel_CNT2(FILE *file, int chunk_size, struct LevelInfo *level)
 
   if (element == EL_YAMYAM)
   {
-    level->num_yam_contents = num_contents;
+    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++)
-         level->yam_content[i][x][y] = content_array[i][x][y];
+         level->yamyam_content[i][x][y] = content_array[i][x][y];
   }
   else if (element == EL_BD_AMOEBA)
   {
@@ -310,9 +344,60 @@ static int LoadLevel_CNT2(FILE *file, int chunk_size, struct LevelInfo *level)
   return chunk_size;
 }
 
-void LoadLevel(int level_nr)
+static int LoadLevel_CUS1(FILE *file, int chunk_size, struct LevelInfo *level)
+{
+  int num_changed_custom_elements = getFile16BitBE(file);
+  int chunk_size_expected = 2 + num_changed_custom_elements * 6;
+  int i;
+
+  if (chunk_size_expected != chunk_size)
+  {
+    ReadUnusedBytesFromFile(file, chunk_size - 2);
+    return chunk_size_expected;
+  }
+
+  for (i=0; i < num_changed_custom_elements; i++)
+  {
+    int element = getFile16BitBE(file);
+    int properties = getFile32BitBE(file);
+
+    if (IS_CUSTOM_ELEMENT(element))
+      Properties[element][EP_BITFIELD_BASE] = properties;
+    else
+      Error(ERR_WARN, "invalid custom element number %d", element);
+  }
+
+  return chunk_size;
+}
+
+static int LoadLevel_CUS2(FILE *file, int chunk_size, struct LevelInfo *level)
+{
+  int num_changed_custom_elements = getFile16BitBE(file);
+  int chunk_size_expected = 2 + num_changed_custom_elements * 4;
+  int i;
+
+  if (chunk_size_expected != chunk_size)
+  {
+    ReadUnusedBytesFromFile(file, chunk_size - 2);
+    return chunk_size_expected;
+  }
+
+  for (i=0; i < num_changed_custom_elements; i++)
+  {
+    int element = getFile16BitBE(file);
+    int custom_element_successor = getFile16BitBE(file);
+
+    if (IS_CUSTOM_ELEMENT(element))
+      element_info[element].change.successor = custom_element_successor;
+    else
+      Error(ERR_WARN, "invalid custom element number %d", element);
+  }
+
+  return chunk_size;
+}
+
+void LoadLevelFromFilename(char *filename)
 {
-  char *filename = getLevelFilename(level_nr);
   char cookie[MAX_LINE_LEN];
   char chunk_name[CHUNK_ID_LEN + 1];
   int chunk_size;
@@ -386,9 +471,11 @@ void LoadLevel(int level_nr)
       { "VERS", FILE_VERS_CHUNK_SIZE,  LoadLevel_VERS },
       { "HEAD", LEVEL_HEADER_SIZE,     LoadLevel_HEAD },
       { "AUTH", MAX_LEVEL_AUTHOR_LEN,  LoadLevel_AUTH },
-      { "CONT", -1,                    LoadLevel_CONT },
       { "BODY", -1,                    LoadLevel_BODY },
+      { "CONT", -1,                    LoadLevel_CONT },
       { "CNT2", LEVEL_CHUNK_CNT2_SIZE, LoadLevel_CNT2 },
+      { "CUS1", -1,                    LoadLevel_CUS1 },
+      { "CUS2", -1,                    LoadLevel_CUS2 },
       {  NULL,  0,                     NULL }
     };
 
@@ -433,6 +520,9 @@ void LoadLevel(int level_nr)
 
   fclose(file);
 
+  if (leveldir_current == NULL)                /* only when dumping level */
+    return;
+
   if (IS_LEVELCLASS_CONTRIBUTION(leveldir_current) ||
       IS_LEVELCLASS_USER(leveldir_current))
   {
@@ -480,10 +570,44 @@ void LoadLevel(int level_nr)
       level.em_slippery_gems = TRUE;
   }
 
+  /* map some elements which have changed in newer versions */
+  if (level.game_version <= VERSION_IDENT(2,2,0))
+  {
+    int x, y;
+
+    /* map game font elements */
+    for(y=0; y<level.fieldy; y++)
+    {
+      for(x=0; x<level.fieldx; x++)
+      {
+       int element = Ur[x][y];
+
+       if (element == EL_CHAR('['))
+         element = EL_CHAR_AUMLAUT;
+       else if (element == EL_CHAR('\\'))
+         element = EL_CHAR_OUMLAUT;
+       else if (element == EL_CHAR(']'))
+         element = EL_CHAR_UUMLAUT;
+       else if (element == EL_CHAR('^'))
+         element = EL_CHAR_COPYRIGHT;
+
+       Feld[x][y] = Ur[x][y] = element;
+      }
+    }
+  }
+
   /* determine border element for this level */
   SetBorderElement();
 }
 
+void LoadLevel(int level_nr)
+{
+  char *filename = getLevelFilename(level_nr);
+
+  LoadLevelFromFilename(filename);
+  InitElementPropertiesEngine(level.game_version);
+}
+
 static void SaveLevel_VERS(FILE *file, struct LevelInfo *level)
 {
   putFileVersion(file, level->file_version);
@@ -510,7 +634,7 @@ static void SaveLevel_HEAD(FILE *file, struct LevelInfo *level)
     for(y=0; y<3; y++)
       for(x=0; x<3; x++)
        fputc((level->encoding_16bit_yamyam ? EL_EMPTY :
-              level->yam_content[i][x][y]),
+              level->yamyam_content[i][x][y]),
              file);
   fputc(level->amoeba_speed, file);
   fputc(level->time_magic_wall, file);
@@ -533,13 +657,25 @@ static void SaveLevel_AUTH(FILE *file, struct LevelInfo *level)
     fputc(level->author[i], file);
 }
 
+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++) 
+      if (level->encoding_16bit_field)
+       putFile16BitBE(file, Ur[x][y]);
+      else
+       fputc(Ur[x][y], file);
+}
+
 #if 0
 static void SaveLevel_CONT(FILE *file, struct LevelInfo *level)
 {
   int i, x, y;
 
   fputc(EL_YAMYAM, file);
-  fputc(level->num_yam_contents, file);
+  fputc(level->num_yamyam_contents, file);
   fputc(0, file);
   fputc(0, file);
 
@@ -547,24 +683,12 @@ static void SaveLevel_CONT(FILE *file, struct LevelInfo *level)
     for(y=0; y<3; y++)
       for(x=0; x<3; x++)
        if (level->encoding_16bit_field)
-         putFile16BitBE(file, level->yam_content[i][x][y]);
+         putFile16BitBE(file, level->yamyam_content[i][x][y]);
        else
-         fputc(level->yam_content[i][x][y], file);
+         fputc(level->yamyam_content[i][x][y], file);
 }
 #endif
 
-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++) 
-      if (level->encoding_16bit_field)
-       putFile16BitBE(file, Ur[x][y]);
-      else
-       fputc(Ur[x][y], file);
-}
-
 static void SaveLevel_CNT2(FILE *file, struct LevelInfo *level, int element)
 {
   int i, x, y;
@@ -573,14 +697,14 @@ static void SaveLevel_CNT2(FILE *file, struct LevelInfo *level, int element)
 
   if (element == EL_YAMYAM)
   {
-    num_contents = level->num_yam_contents;
+    num_contents = level->num_yamyam_contents;
     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++)
-         content_array[i][x][y] = level->yam_content[i][x][y];
+         content_array[i][x][y] = level->yamyam_content[i][x][y];
   }
   else if (element == EL_BD_AMOEBA)
   {
@@ -616,11 +740,67 @@ static void SaveLevel_CNT2(FILE *file, struct LevelInfo *level, int element)
        putFile16BitBE(file, content_array[i][x][y]);
 }
 
+static void SaveLevel_CUS1(FILE *file, struct LevelInfo *level,
+                          int num_changed_custom_elements)
+{
+  int i, check = 0;
+
+  putFile16BitBE(file, num_changed_custom_elements);
+
+  for (i=0; i < NUM_CUSTOM_ELEMENTS; i++)
+  {
+    int element = EL_CUSTOM_START + i;
+
+    if (Properties[element][EP_BITFIELD_BASE] != EP_BITMASK_DEFAULT)
+    {
+      if (check < num_changed_custom_elements)
+      {
+       putFile16BitBE(file, element);
+       putFile32BitBE(file, Properties[element][EP_BITFIELD_BASE]);
+      }
+
+      check++;
+    }
+  }
+
+  if (check != num_changed_custom_elements)    /* should not happen */
+    Error(ERR_WARN, "inconsistent number of custom element properties");
+}
+
+static void SaveLevel_CUS2(FILE *file, struct LevelInfo *level,
+                          int num_changed_custom_elements)
+{
+  int i, check = 0;
+
+  putFile16BitBE(file, num_changed_custom_elements);
+
+  for (i=0; i < NUM_CUSTOM_ELEMENTS; i++)
+  {
+    int element = EL_CUSTOM_START + i;
+
+    if (element_info[element].change.successor != EL_EMPTY_SPACE)
+    {
+      if (check < num_changed_custom_elements)
+      {
+       putFile16BitBE(file, element);
+       putFile16BitBE(file, element_info[element].change.successor);
+      }
+
+      check++;
+    }
+  }
+
+  if (check != num_changed_custom_elements)    /* should not happen */
+    Error(ERR_WARN, "inconsistent number of custom element successors");
+}
+
 void SaveLevel(int level_nr)
 {
-  int i, x, y;
   char *filename = getLevelFilename(level_nr);
   int body_chunk_size;
+  int num_changed_custom_elements1 = 0;
+  int num_changed_custom_elements2 = 0;
+  int i, x, y;
   FILE *file;
 
   if (!(file = fopen(filename, MODE_WRITE)))
@@ -641,10 +821,10 @@ void SaveLevel(int level_nr)
 
   /* check yamyam content for 16-bit elements */
   level.encoding_16bit_yamyam = FALSE;
-  for(i=0; i<level.num_yam_contents; i++)
+  for(i=0; i<level.num_yamyam_contents; i++)
     for(y=0; y<3; y++)
       for(x=0; x<3; x++)
-       if (level.yam_content[i][x][y] > 255)
+       if (level.yamyam_content[i][x][y] > 255)
          level.encoding_16bit_yamyam = TRUE;
 
   /* check amoeba content for 16-bit elements */
@@ -652,9 +832,20 @@ void SaveLevel(int level_nr)
   if (level.amoeba_content > 255)
     level.encoding_16bit_amoeba = TRUE;
 
+  /* calculate size of "BODY" chunk */
   body_chunk_size =
     level.fieldx * level.fieldy * (level.encoding_16bit_field ? 2 : 1);
 
+  /* check for non-standard custom elements and calculate "CUS1" chunk size */
+  for (i=0; i < NUM_CUSTOM_ELEMENTS; i++)
+    if (Properties[EL_CUSTOM_START +i][EP_BITFIELD_BASE] != EP_BITMASK_DEFAULT)
+      num_changed_custom_elements1++;
+
+  /* check for non-standard custom elements and calculate "CUS2" chunk size */
+  for (i=0; i < NUM_CUSTOM_ELEMENTS; i++)
+    if (element_info[EL_CUSTOM_START + i].change.successor != EL_EMPTY_SPACE)
+      num_changed_custom_elements2++;
+
   putFileChunkBE(file, "RND1", CHUNK_SIZE_UNDEFINED);
   putFileChunkBE(file, "CAVE", CHUNK_SIZE_NONE);
 
@@ -671,7 +862,7 @@ void SaveLevel(int level_nr)
   SaveLevel_BODY(file, &level);
 
   if (level.encoding_16bit_yamyam ||
-      level.num_yam_contents != STD_ELEMENT_CONTENTS)
+      level.num_yamyam_contents != STD_ELEMENT_CONTENTS)
   {
     putFileChunkBE(file, "CNT2", LEVEL_CHUNK_CNT2_SIZE);
     SaveLevel_CNT2(file, &level, EL_YAMYAM);
@@ -683,11 +874,52 @@ void SaveLevel(int level_nr)
     SaveLevel_CNT2(file, &level, EL_BD_AMOEBA);
   }
 
+  if (num_changed_custom_elements1 > 0)
+  {
+    putFileChunkBE(file, "CUS1", 2 + num_changed_custom_elements1 * 6);
+    SaveLevel_CUS1(file, &level, num_changed_custom_elements1);
+  }
+
+  if (num_changed_custom_elements2 > 0)
+  {
+    putFileChunkBE(file, "CUS2", 2 + num_changed_custom_elements2 * 4);
+    SaveLevel_CUS2(file, &level, num_changed_custom_elements2);
+  }
+
   fclose(file);
 
   SetFilePermissions(filename, PERMS_PRIVATE);
 }
 
+void DumpLevel(struct LevelInfo *level)
+{
+  printf_line("-", 79);
+  printf("Level xxx (file version %08d, game version %08d)\n",
+        level->file_version, level->game_version);
+  printf_line("-", 79);
+
+  printf("Level Author: '%s'\n", level->author);
+  printf("Level Title:  '%s'\n", level->name);
+  printf("\n");
+  printf("Playfield Size: %d x %d\n", level->fieldx, level->fieldy);
+  printf("\n");
+  printf("Level Time:  %d seconds\n", level->time);
+  printf("Gems needed: %d\n", level->gems_needed);
+  printf("\n");
+  printf("Time for Magic Wall: %d seconds\n", level->time_magic_wall);
+  printf("Time for Wheel:      %d seconds\n", level->time_wheel);
+  printf("Time for Light:      %d seconds\n", level->time_light);
+  printf("Time for Timegate:   %d seconds\n", level->time_timegate);
+  printf("\n");
+  printf("Amoeba Speed: %d\n", level->amoeba_speed);
+  printf("\n");
+  printf("Gravity:                %s\n", (level->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"));
+
+  printf_line("-", 79);
+}
+
 
 /* ========================================================================= */
 /* tape file functions                                                       */
@@ -762,6 +994,26 @@ static int LoadTape_HEAD(FILE *file, int chunk_size, struct TapeInfo *tape)
   return chunk_size;
 }
 
+static int LoadTape_INFO(FILE *file, int chunk_size, struct TapeInfo *tape)
+{
+  int level_identifier_size;
+  int i;
+
+  level_identifier_size = getFile16BitBE(file);
+
+  tape->level_identifier =
+    checked_realloc(tape->level_identifier, level_identifier_size);
+
+  for(i=0; i < level_identifier_size; i++)
+    tape->level_identifier[i] = fgetc(file);
+
+  tape->level_nr = getFile16BitBE(file);
+
+  chunk_size = 2 + level_identifier_size + 2;
+
+  return chunk_size;
+}
+
 static int LoadTape_BODY(FILE *file, int chunk_size, struct TapeInfo *tape)
 {
   int i, j;
@@ -915,6 +1167,7 @@ void LoadTapeFromFilename(char *filename)
     {
       { "VERS", FILE_VERS_CHUNK_SIZE,  LoadTape_VERS },
       { "HEAD", TAPE_HEADER_SIZE,      LoadTape_HEAD },
+      { "INFO", -1,                    LoadTape_INFO },
       { "BODY", -1,                    LoadTape_BODY },
       {  NULL,  0,                     NULL }
     };
@@ -961,6 +1214,10 @@ void LoadTapeFromFilename(char *filename)
   fclose(file);
 
   tape.length_seconds = GetTapeLength();
+
+#if 0
+  printf("tape version: %d\n", tape.game_version);
+#endif
 }
 
 void LoadTape(int level_nr)
@@ -998,6 +1255,19 @@ static void SaveTape_HEAD(FILE *file, struct TapeInfo *tape)
   putFileVersion(file, tape->engine_version);
 }
 
+static void SaveTape_INFO(FILE *file, struct TapeInfo *tape)
+{
+  int level_identifier_size = strlen(tape->level_identifier) + 1;
+  int i;
+
+  putFile16BitBE(file, level_identifier_size);
+
+  for(i=0; i < level_identifier_size; i++)
+    fputc(tape->level_identifier[i], file);
+
+  putFile16BitBE(file, tape->level_nr);
+}
+
 static void SaveTape_BODY(FILE *file, struct TapeInfo *tape)
 {
   int i, j;
@@ -1014,12 +1284,13 @@ static void SaveTape_BODY(FILE *file, struct TapeInfo *tape)
 
 void SaveTape(int level_nr)
 {
-  int i;
   char *filename = getTapeFilename(level_nr);
   FILE *file;
   boolean new_tape = TRUE;
   int num_participating_players = 0;
+  int info_chunk_size;
   int body_chunk_size;
+  int i;
 
   InitTapeDirectory(leveldir_current->filename);
 
@@ -1045,6 +1316,7 @@ void SaveTape(int level_nr)
     if (tape.player_participates[i])
       num_participating_players++;
 
+  info_chunk_size = 2 + (strlen(tape.level_identifier) + 1) + 2;
   body_chunk_size = (num_participating_players + 1) * tape.length;
 
   putFileChunkBE(file, "RND1", CHUNK_SIZE_UNDEFINED);
@@ -1056,6 +1328,9 @@ void SaveTape(int level_nr)
   putFileChunkBE(file, "HEAD", TAPE_HEADER_SIZE);
   SaveTape_HEAD(file, &tape);
 
+  putFileChunkBE(file, "INFO", info_chunk_size);
+  SaveTape_INFO(file, &tape);
+
   putFileChunkBE(file, "BODY", body_chunk_size);
   SaveTape_BODY(file, &tape);
 
@@ -1079,10 +1354,11 @@ void DumpTape(struct TapeInfo *tape)
     return;
   }
 
-  printf_line('-', 79);
-  printf("Tape of Level %d (file version %06d, game version %06d)\n",
+  printf_line("-", 79);
+  printf("Tape of Level %03d (file version %08d, game version %08d)\n",
         tape->level_nr, tape->file_version, tape->game_version);
-  printf_line('-', 79);
+  printf("Level series identifier: '%s'\n", tape->level_identifier);
+  printf_line("-", 79);
 
   for(i=0; i<tape->length; i++)
   {
@@ -1111,7 +1387,7 @@ void DumpTape(struct TapeInfo *tape)
     printf("(%03d)\n", tape->pos[i].delay);
   }
 
-  printf_line('-', 79);
+  printf_line("-", 79);
 }
 
 
@@ -1229,40 +1505,67 @@ void SaveScore(int level_nr)
 
 #define NUM_GLOBAL_SETUP_TOKENS                        22
 
+/* editor setup */
+#define SETUP_TOKEN_EDITOR_EL_BOULDERDASH      0
+#define SETUP_TOKEN_EDITOR_EL_EMERALD_MINE     1
+#define SETUP_TOKEN_EDITOR_EL_MORE             2
+#define SETUP_TOKEN_EDITOR_EL_SOKOBAN          3
+#define SETUP_TOKEN_EDITOR_EL_SUPAPLEX         4
+#define SETUP_TOKEN_EDITOR_EL_DIAMOND_CAVES    5
+#define SETUP_TOKEN_EDITOR_EL_DX_BOULDERDASH   6
+#define SETUP_TOKEN_EDITOR_EL_CHARS            7
+#define SETUP_TOKEN_EDITOR_EL_CUSTOM           8
+
+#define NUM_EDITOR_SETUP_TOKENS                        9
+
 /* shortcut setup */
-#define SETUP_TOKEN_SAVE_GAME                  0
-#define SETUP_TOKEN_LOAD_GAME                  1
-#define SETUP_TOKEN_TOGGLE_PAUSE               2
+#define SETUP_TOKEN_SHORTCUT_SAVE_GAME         0
+#define SETUP_TOKEN_SHORTCUT_LOAD_GAME         1
+#define SETUP_TOKEN_SHORTCUT_TOGGLE_PAUSE      2
 
 #define NUM_SHORTCUT_SETUP_TOKENS              3
 
 /* player setup */
-#define SETUP_TOKEN_USE_JOYSTICK               0
-#define SETUP_TOKEN_JOY_DEVICE_NAME            1
-#define SETUP_TOKEN_JOY_XLEFT                  2
-#define SETUP_TOKEN_JOY_XMIDDLE                        3
-#define SETUP_TOKEN_JOY_XRIGHT                 4
-#define SETUP_TOKEN_JOY_YUPPER                 5
-#define SETUP_TOKEN_JOY_YMIDDLE                        6
-#define SETUP_TOKEN_JOY_YLOWER                 7
-#define SETUP_TOKEN_JOY_SNAP                   8
-#define SETUP_TOKEN_JOY_BOMB                   9
-#define SETUP_TOKEN_KEY_LEFT                   10
-#define SETUP_TOKEN_KEY_RIGHT                  11
-#define SETUP_TOKEN_KEY_UP                     12
-#define SETUP_TOKEN_KEY_DOWN                   13
-#define SETUP_TOKEN_KEY_SNAP                   14
-#define SETUP_TOKEN_KEY_BOMB                   15
+#define SETUP_TOKEN_PLAYER_USE_JOYSTICK                0
+#define SETUP_TOKEN_PLAYER_JOY_DEVICE_NAME     1
+#define SETUP_TOKEN_PLAYER_JOY_XLEFT           2
+#define SETUP_TOKEN_PLAYER_JOY_XMIDDLE         3
+#define SETUP_TOKEN_PLAYER_JOY_XRIGHT          4
+#define SETUP_TOKEN_PLAYER_JOY_YUPPER          5
+#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_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 NUM_PLAYER_SETUP_TOKENS                        16
 
+/* system setup */
+#define SETUP_TOKEN_SYSTEM_SDL_AUDIODRIVER     0
+#define SETUP_TOKEN_SYSTEM_AUDIO_FRAGMENT_SIZE 1
+
+#define NUM_SYSTEM_SETUP_TOKENS                        2
+
+/* options setup */
+#define SETUP_TOKEN_OPTIONS_VERBOSE            0
+
+#define NUM_OPTIONS_SETUP_TOKENS               1
+
+
 static struct SetupInfo si;
+static struct SetupEditorInfo sei;
 static struct SetupShortcutInfo ssi;
 static struct SetupInputInfo sii;
+static struct SetupSystemInfo syi;
+static struct OptionInfo soi;
 
 static struct TokenInfo global_setup_tokens[] =
 {
-  /* global setup */
   { TYPE_STRING, &si.player_name,      "player_name"                   },
   { TYPE_SWITCH, &si.sound,            "sound"                         },
   { TYPE_SWITCH, &si.sound_loops,      "repeating_sound_loops"         },
@@ -1287,9 +1590,21 @@ static struct TokenInfo global_setup_tokens[] =
   { TYPE_SWITCH, &si.override_level_music,    "override_level_music"   },
 };
 
+static struct TokenInfo editor_setup_tokens[] =
+{
+  { TYPE_SWITCH, &sei.el_boulderdash,  "editor.el_boulderdash"         },
+  { TYPE_SWITCH, &sei.el_emerald_mine, "editor.el_emerald_mine"        },
+  { TYPE_SWITCH, &sei.el_more,         "editor.el_more"                },
+  { TYPE_SWITCH, &sei.el_sokoban,      "editor.el_sokoban"             },
+  { TYPE_SWITCH, &sei.el_supaplex,     "editor.el_supaplex"            },
+  { TYPE_SWITCH, &sei.el_diamond_caves,        "editor.el_diamond_caves"       },
+  { TYPE_SWITCH, &sei.el_dx_boulderdash,"editor.el_dx_boulderdash"     },
+  { TYPE_SWITCH, &sei.el_chars,                "editor.el_chars"               },
+  { TYPE_SWITCH, &sei.el_custom,       "editor.el_custom"              },
+};
+
 static struct TokenInfo shortcut_setup_tokens[] =
 {
-  /* shortcut setup */
   { 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"         }
@@ -1297,7 +1612,6 @@ static struct TokenInfo shortcut_setup_tokens[] =
 
 static struct TokenInfo player_setup_tokens[] =
 {
-  /* player setup */
   { TYPE_BOOLEAN, &sii.use_joystick,   ".use_joystick"                 },
   { TYPE_STRING,  &sii.joy.device_name,        ".joy.device_name"              },
   { TYPE_INTEGER, &sii.joy.xleft,      ".joy.xleft"                    },
@@ -1316,11 +1630,37 @@ static struct TokenInfo player_setup_tokens[] =
   { TYPE_KEY_X11, &sii.key.bomb,       ".key.place_bomb"               }
 };
 
+static struct TokenInfo system_setup_tokens[] =
+{
+  { TYPE_STRING,  &syi.sdl_audiodriver,        "system.sdl_audiodriver"        },
+  { TYPE_INTEGER, &syi.audio_fragment_size,"system.audio_fragment_size"        }
+};
+
+static struct TokenInfo options_setup_tokens[] =
+{
+  { TYPE_BOOLEAN, &soi.verbose,                "options.verbose"               }
+};
+
+static char *get_corrected_login_name(char *login_name)
+{
+  /* needed because player name must be a fixed length string */
+  char *login_name_new = checked_malloc(MAX_PLAYER_NAME_LEN + 1);
+
+  strncpy(login_name_new, login_name, MAX_PLAYER_NAME_LEN);
+  login_name_new[MAX_PLAYER_NAME_LEN] = '\0';
+
+  if (strlen(login_name) > MAX_PLAYER_NAME_LEN)                /* name has been cut */
+    if (strchr(login_name_new, ' '))
+      *strchr(login_name_new, ' ') = '\0';
+
+  return login_name_new;
+}
+
 static void setSetupInfoToDefaults(struct SetupInfo *si)
 {
   int i;
 
-  si->player_name = getStringCopy(getLoginName());
+  si->player_name = get_corrected_login_name(getLoginName());
 
   si->sound = TRUE;
   si->sound_loops = TRUE;
@@ -1347,6 +1687,16 @@ static void setSetupInfoToDefaults(struct SetupInfo *si)
   si->override_level_sounds = FALSE;
   si->override_level_music = FALSE;
 
+  si->editor.el_boulderdash = TRUE;
+  si->editor.el_emerald_mine = 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_chars = TRUE;
+  si->editor.el_custom = TRUE;
+
   si->shortcut.save_game = DEFAULT_KEY_SAVE_GAME;
   si->shortcut.load_game = DEFAULT_KEY_LOAD_GAME;
   si->shortcut.toggle_pause = DEFAULT_KEY_TOGGLE_PAUSE;
@@ -1370,27 +1720,39 @@ static void setSetupInfoToDefaults(struct SetupInfo *si)
     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->system.sdl_audiodriver = getStringCopy(ARG_DEFAULT);
+  si->system.audio_fragment_size = DEFAULT_AUDIO_FRAGMENT_SIZE;
+
+  si->options.verbose = FALSE;
 }
 
-static void decodeSetupFileList(struct SetupFileList *setup_file_list)
+static void decodeSetupFileHash(SetupFileHash *setup_file_hash)
 {
   int i, pnr;
 
-  if (!setup_file_list)
+  if (!setup_file_hash)
     return;
 
   /* global setup */
   si = setup;
   for (i=0; i<NUM_GLOBAL_SETUP_TOKENS; i++)
     setSetupInfo(global_setup_tokens, i,
-                getTokenValue(setup_file_list, global_setup_tokens[i].text));
+                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++)
+    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++)
     setSetupInfo(shortcut_setup_tokens, i,
-                getTokenValue(setup_file_list,shortcut_setup_tokens[i].text));
+                getHashEntry(setup_file_hash,shortcut_setup_tokens[i].text));
   setup.shortcut = ssi;
 
   /* player setup */
@@ -1407,42 +1769,51 @@ static void decodeSetupFileList(struct SetupFileList *setup_file_list)
 
       sprintf(full_token, "%s%s", prefix, player_setup_tokens[i].text);
       setSetupInfo(player_setup_tokens, i,
-                  getTokenValue(setup_file_list, full_token));
+                  getHashEntry(setup_file_hash, full_token));
     }
     setup.input[pnr] = sii;
   }
+
+  /* system setup */
+  syi = setup.system;
+  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++)
+    setSetupInfo(options_setup_tokens, i,
+                getHashEntry(setup_file_hash, options_setup_tokens[i].text));
+  setup.options = soi;
 }
 
 void LoadSetup()
 {
   char *filename = getSetupFilename();
-  struct SetupFileList *setup_file_list = NULL;
+  SetupFileHash *setup_file_hash = NULL;
 
   /* always start with reliable default values */
   setSetupInfoToDefaults(&setup);
 
-  setup_file_list = loadSetupFileList(filename);
+  setup_file_hash = loadSetupFileHash(filename);
 
-  if (setup_file_list)
+  if (setup_file_hash)
   {
-    checkSetupFileListIdentifier(setup_file_list, getCookie("SETUP"));
-    decodeSetupFileList(setup_file_list);
+    char *player_name_new;
+
+    checkSetupFileHashIdentifier(setup_file_hash, getCookie("SETUP"));
+    decodeSetupFileHash(setup_file_hash);
 
     setup.direct_draw = !setup.double_buffering;
 
-    freeSetupFileList(setup_file_list);
+    freeSetupFileHash(setup_file_hash);
 
     /* needed to work around problems with fixed length strings */
-    if (strlen(setup.player_name) > MAX_PLAYER_NAME_LEN)
-      setup.player_name[MAX_PLAYER_NAME_LEN] = '\0';
-    else if (strlen(setup.player_name) < MAX_PLAYER_NAME_LEN)
-    {
-      char *new_name = checked_malloc(MAX_PLAYER_NAME_LEN + 1);
-
-      strcpy(new_name, setup.player_name);
-      free(setup.player_name);
-      setup.player_name = new_name;
-    }
+    player_name_new = get_corrected_login_name(setup.player_name);
+    free(setup.player_name);
+    setup.player_name = player_name_new;
   }
   else
     Error(ERR_WARN, "using default setup values");
@@ -1470,13 +1841,20 @@ void SaveSetup()
   si = setup;
   for (i=0; i<NUM_GLOBAL_SETUP_TOKENS; i++)
   {
-    fprintf(file, "%s\n", getSetupLine(global_setup_tokens, "", i));
-
     /* just to make things nicer :) */
-    if (i == SETUP_TOKEN_PLAYER_NAME || i == SETUP_TOKEN_GRAPHICS_SET - 1)
+    if (i == SETUP_TOKEN_PLAYER_NAME + 1 ||
+       i == SETUP_TOKEN_GRAPHICS_SET)
       fprintf(file, "\n");
+
+    fprintf(file, "%s\n", getSetupLine(global_setup_tokens, "", i));
   }
 
+  /* 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");
@@ -1496,7 +1874,94 @@ void SaveSetup()
       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++)
+    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++)
+    fprintf(file, "%s\n", getSetupLine(options_setup_tokens, "", i));
+
   fclose(file);
 
   SetFilePermissions(filename, PERMS_PRIVATE);
 }
+
+void LoadCustomElementDescriptions()
+{
+  char *filename = getCustomArtworkConfigFilename(ARTWORK_TYPE_GRAPHICS);
+  SetupFileHash *setup_file_hash;
+  int i;
+
+  for (i=0; i<NUM_FILE_ELEMENTS; i++)
+  {
+    if (element_info[i].custom_description != NULL)
+    {
+      free(element_info[i].custom_description);
+      element_info[i].custom_description = NULL;
+    }
+  }
+
+  if ((setup_file_hash = loadSetupFileHash(filename)) == NULL)
+    return;
+
+  for (i=0; i<NUM_FILE_ELEMENTS; i++)
+  {
+    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);
+}
+
+void LoadSpecialMenuDesignSettings()
+{
+  char *filename = getCustomArtworkConfigFilename(ARTWORK_TYPE_GRAPHICS);
+  SetupFileHash *setup_file_hash;
+  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++)
+      if (strcmp(image_config_vars[i].token, image_config[j].token) == 0)
+       *image_config_vars[i].value =
+         get_integer_from_string(image_config[j].value);
+
+  if ((setup_file_hash = loadSetupFileHash(filename)) == NULL)
+    return;
+
+  /* special case: initialize with default values that may be overwritten */
+  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");
+    char *list_size = getHashEntry(setup_file_hash, "menu.list_size");
+
+    if (value_x != NULL)
+      menu.draw_xoffset[i] = get_integer_from_string(value_x);
+    if (value_y != NULL)
+      menu.draw_yoffset[i] = get_integer_from_string(value_y);
+    if (list_size != NULL)
+      menu.list_size[i] = get_integer_from_string(list_size);
+  }
+
+  /* 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);
+
+    if (value != NULL)
+      *image_config_vars[i].value = get_integer_from_string(value);
+  }
+
+  freeSetupFileHash(setup_file_hash);
+}