rocksndiamonds-3.3.1.0
[rocksndiamonds.git] / src / files.c
index b16bc6ca5162f709f799680056cdc331ca62ba17..7ff37822dd2d3a7ed3a2c4fe3a100d33c9319652 100644 (file)
@@ -236,6 +236,12 @@ static struct LevelFileConfigInfo chunk_config_INFO[] =
     &li.score[SC_TIME_BONUS],          1
   },
 
+  {
+    -1,                                        -1,
+    TYPE_BOOLEAN,                      CONF_VALUE_8_BIT(9),
+    &li.auto_exit_sokoban,             FALSE
+  },
+
   {
     -1,                                        -1,
     -1,                                        -1,
@@ -1314,6 +1320,22 @@ filetype_id_list[] =
 /* level file functions                                                      */
 /* ========================================================================= */
 
+static boolean check_special_flags(char *flag)
+{
+#if 0
+  printf("::: '%s', '%s', '%s'\n",
+        flag,
+        options.special_flags,
+        leveldir_current->special_flags);
+#endif
+
+  if (strEqual(options.special_flags, flag) ||
+      strEqual(leveldir_current->special_flags, flag))
+    return TRUE;
+
+  return FALSE;
+}
+
 static struct DateInfo getCurrentDate()
 {
   time_t epoch_seconds = time(NULL);
@@ -1324,6 +1346,8 @@ static struct DateInfo getCurrentDate()
   date.month = now->tm_mon  + 1;
   date.day   = now->tm_mday;
 
+  date.src   = DATE_SRC_CLOCK;
+
   return date;
 }
 
@@ -1572,11 +1596,203 @@ void setElementChangeInfoToDefaults(struct ElementChangeInfo *change)
   change->post_change_function = NULL;
 }
 
-static void setLevelInfoToDefaults(struct LevelInfo *level)
+#if 1
+
+static void setLevelInfoToDefaults_Level(struct LevelInfo *level)
+{
+  int i, x, y;
+
+  li = *level;         /* copy level data into temporary buffer */
+  setConfigToDefaultsFromConfigList(chunk_config_INFO);
+  *level = li;         /* copy temporary buffer back to level data */
+
+  setLevelInfoToDefaults_EM();
+  setLevelInfoToDefaults_SP();
+
+  level->native_em_level = &native_em_level;
+  level->native_sp_level = &native_sp_level;
+
+  level->file_version = FILE_VERSION_ACTUAL;
+  level->game_version = GAME_VERSION_ACTUAL;
+
+  level->creation_date = getCurrentDate();
+
+  level->encoding_16bit_field  = TRUE;
+  level->encoding_16bit_yamyam = TRUE;
+  level->encoding_16bit_amoeba = TRUE;
+
+  for (x = 0; x < MAX_LEV_FIELDX; x++)
+    for (y = 0; y < MAX_LEV_FIELDY; y++)
+      level->field[x][y] = EL_SAND;
+
+  for (i = 0; i < MAX_LEVEL_NAME_LEN; i++)
+    level->name[i] = '\0';
+  for (i = 0; i < MAX_LEVEL_AUTHOR_LEN; i++)
+    level->author[i] = '\0';
+
+  strcpy(level->name, NAMELESS_LEVEL_NAME);
+  strcpy(level->author, ANONYMOUS_NAME);
+
+  level->field[0][0] = EL_PLAYER_1;
+  level->field[STD_LEV_FIELDX - 1][STD_LEV_FIELDY - 1] = EL_EXIT_CLOSED;
+
+  BorderElement = EL_STEELWALL;
+
+  /* set all bug compatibility flags to "false" => do not emulate this bug */
+  level->use_action_after_change_bug = FALSE;
+
+  if (leveldir_current)
+  {
+    /* try to determine better author name than 'anonymous' */
+    if (!strEqual(leveldir_current->author, ANONYMOUS_NAME))
+    {
+      strncpy(level->author, leveldir_current->author, MAX_LEVEL_AUTHOR_LEN);
+      level->author[MAX_LEVEL_AUTHOR_LEN] = '\0';
+    }
+    else
+    {
+      switch (LEVELCLASS(leveldir_current))
+      {
+       case LEVELCLASS_TUTORIAL:
+         strcpy(level->author, PROGRAM_AUTHOR_STRING);
+         break;
+
+        case LEVELCLASS_CONTRIB:
+         strncpy(level->author, leveldir_current->name, MAX_LEVEL_AUTHOR_LEN);
+         level->author[MAX_LEVEL_AUTHOR_LEN] = '\0';
+         break;
+
+        case LEVELCLASS_PRIVATE:
+         strncpy(level->author, getRealName(), MAX_LEVEL_AUTHOR_LEN);
+         level->author[MAX_LEVEL_AUTHOR_LEN] = '\0';
+         break;
+
+        default:
+         /* keep default value */
+         break;
+      }
+    }
+  }
+}
+
+static void setLevelInfoToDefaults_Elements(struct LevelInfo *level)
+{
+  static boolean clipboard_elements_initialized = FALSE;
+  int i;
+
+  InitElementPropertiesStatic();
+
+  li = *level;         /* copy level data into temporary buffer */
+  setConfigToDefaultsFromConfigList(chunk_config_ELEM);
+  *level = li;         /* copy temporary buffer back to level data */
+
+  for (i = 0; i < MAX_NUM_ELEMENTS; i++)
+  {
+    int element = i;
+    struct ElementInfo *ei = &element_info[element];
+
+    /* never initialize clipboard elements after the very first time */
+    /* (to be able to use clipboard elements between several levels) */
+    if (IS_CLIPBOARD_ELEMENT(element) && clipboard_elements_initialized)
+      continue;
+
+    if (IS_ENVELOPE(element))
+    {
+      int envelope_nr = element - EL_ENVELOPE_1;
+
+      setConfigToDefaultsFromConfigList(chunk_config_NOTE);
+
+      level->envelope[envelope_nr] = xx_envelope;
+    }
+
+    if (IS_CUSTOM_ELEMENT(element) ||
+       IS_GROUP_ELEMENT(element) ||
+       IS_INTERNAL_ELEMENT(element))
+    {
+      xx_ei = *ei;     /* copy element data into temporary buffer */
+
+      setConfigToDefaultsFromConfigList(chunk_config_CUSX_base);
+
+      *ei = xx_ei;
+    }
+
+    setElementChangePages(ei, 1);
+    setElementChangeInfoToDefaults(ei->change);
+
+    if (IS_CUSTOM_ELEMENT(element) ||
+       IS_GROUP_ELEMENT(element) ||
+       IS_INTERNAL_ELEMENT(element))
+    {
+      setElementDescriptionToDefault(ei);
+
+      ei->modified_settings = FALSE;
+    }
+
+    if (IS_CUSTOM_ELEMENT(element) ||
+       IS_INTERNAL_ELEMENT(element))
+    {
+      /* internal values used in level editor */
+
+      ei->access_type = 0;
+      ei->access_layer = 0;
+      ei->access_protected = 0;
+      ei->walk_to_action = 0;
+      ei->smash_targets = 0;
+      ei->deadliness = 0;
+
+      ei->can_explode_by_fire = FALSE;
+      ei->can_explode_smashed = FALSE;
+      ei->can_explode_impact = FALSE;
+
+      ei->current_change_page = 0;
+    }
+
+    if (IS_GROUP_ELEMENT(element) ||
+       IS_INTERNAL_ELEMENT(element))
+    {
+      struct ElementGroupInfo *group;
+
+      /* initialize memory for list of elements in group */
+      if (ei->group == NULL)
+       ei->group = checked_malloc(sizeof(struct ElementGroupInfo));
+
+      group = ei->group;
+
+      xx_group = *group;       /* copy group data into temporary buffer */
+
+      setConfigToDefaultsFromConfigList(chunk_config_GRPX);
+
+      *group = xx_group;
+    }
+  }
+
+  clipboard_elements_initialized = TRUE;
+}
+
+static void setLevelInfoToDefaults(struct LevelInfo *level,
+                                  boolean level_info_only)
+{
+  setLevelInfoToDefaults_Level(level);
+
+  if (!level_info_only)
+    setLevelInfoToDefaults_Elements(level);
+
+  level->no_valid_file = FALSE;
+
+  level->changed = FALSE;
+}
+
+#else
+
+static void setLevelInfoToDefaults(struct LevelInfo *level,
+                                  boolean level_info_only)
 {
   static boolean clipboard_elements_initialized = FALSE;
   int i, x, y;
 
+  if (level_info_only)
+    return;
+
   InitElementPropertiesStatic();
 
   li = *level;         /* copy level data into temporary buffer */
@@ -1707,40 +1923,42 @@ static void setLevelInfoToDefaults(struct LevelInfo *level)
   /* set all bug compatibility flags to "false" => do not emulate this bug */
   level->use_action_after_change_bug = FALSE;
 
-  if (leveldir_current == NULL)                /* only when dumping level */
-    return;
-
-  /* try to determine better author name than 'anonymous' */
-  if (!strEqual(leveldir_current->author, ANONYMOUS_NAME))
+  if (leveldir_current)
   {
-    strncpy(level->author, leveldir_current->author, MAX_LEVEL_AUTHOR_LEN);
-    level->author[MAX_LEVEL_AUTHOR_LEN] = '\0';
-  }
-  else
-  {
-    switch (LEVELCLASS(leveldir_current))
+    /* try to determine better author name than 'anonymous' */
+    if (!strEqual(leveldir_current->author, ANONYMOUS_NAME))
     {
-      case LEVELCLASS_TUTORIAL:
-       strcpy(level->author, PROGRAM_AUTHOR_STRING);
-       break;
-
-      case LEVELCLASS_CONTRIB:
-       strncpy(level->author, leveldir_current->name, MAX_LEVEL_AUTHOR_LEN);
-       level->author[MAX_LEVEL_AUTHOR_LEN] = '\0';
-       break;
-
-      case LEVELCLASS_PRIVATE:
-       strncpy(level->author, getRealName(), MAX_LEVEL_AUTHOR_LEN);
-       level->author[MAX_LEVEL_AUTHOR_LEN] = '\0';
-       break;
-
-      default:
-       /* keep default value */
-       break;
+      strncpy(level->author, leveldir_current->author, MAX_LEVEL_AUTHOR_LEN);
+      level->author[MAX_LEVEL_AUTHOR_LEN] = '\0';
+    }
+    else
+    {
+      switch (LEVELCLASS(leveldir_current))
+      {
+       case LEVELCLASS_TUTORIAL:
+         strcpy(level->author, PROGRAM_AUTHOR_STRING);
+         break;
+
+        case LEVELCLASS_CONTRIB:
+         strncpy(level->author, leveldir_current->name, MAX_LEVEL_AUTHOR_LEN);
+         level->author[MAX_LEVEL_AUTHOR_LEN] = '\0';
+         break;
+
+        case LEVELCLASS_PRIVATE:
+         strncpy(level->author, getRealName(), MAX_LEVEL_AUTHOR_LEN);
+         level->author[MAX_LEVEL_AUTHOR_LEN] = '\0';
+         break;
+
+        default:
+         /* keep default value */
+         break;
+      }
     }
   }
 }
 
+#endif
+
 static void setFileInfoToDefaults(struct LevelFileInfo *level_file_info)
 {
   level_file_info->nr = 0;
@@ -1752,9 +1970,44 @@ static void setFileInfoToDefaults(struct LevelFileInfo *level_file_info)
 
 static void ActivateLevelTemplate()
 {
+  int x, y;
+
   /* Currently there is no special action needed to activate the template
      data, because 'element_info' property settings overwrite the original
      level data, while all other variables do not change. */
+
+  /* Exception: 'from_level_template' elements in the original level playfield
+     are overwritten with the corresponding elements at the same position in
+     playfield from the level template. */
+
+  for (x = 0; x < level.fieldx; x++)
+    for (y = 0; y < level.fieldy; y++)
+      if (level.field[x][y] == EL_FROM_LEVEL_TEMPLATE)
+       level.field[x][y] = level_template.field[x][y];
+
+  if (check_special_flags("load_xsb_to_ces"))
+  {
+    struct LevelInfo level_backup = level;
+
+    /* overwrite all individual level settings from template level settings */
+    level = level_template;
+
+    /* restore playfield size */
+    level.fieldx = level_backup.fieldx;
+    level.fieldy = level_backup.fieldy;
+
+    /* restore playfield content */
+    for (x = 0; x < level.fieldx; x++)
+      for (y = 0; y < level.fieldy; y++)
+       level.field[x][y] = level_backup.field[x][y];
+
+    /* restore name and author from individual level */
+    strcpy(level.name,   level_backup.name);
+    strcpy(level.author, level_backup.author);
+
+    /* restore flag "use_custom_template" */
+    level.use_custom_template = level_backup.use_custom_template;
+  }
 }
 
 static char *getLevelFilenameFromBasename(char *basename)
@@ -1820,18 +2073,23 @@ static boolean checkForPackageFromBasename(char *basename)
   return (getFileTypeFromBasename(basename) != LEVEL_FILE_TYPE_UNKNOWN);
 }
 
-static char *getSingleLevelBasename(int nr)
+static char *getSingleLevelBasenameExt(int nr, char *extension)
 {
   static char basename[MAX_FILENAME_LEN];
 
   if (nr < 0)
-    sprintf(basename, "template.%s", LEVELFILE_EXTENSION);
+    sprintf(basename, "template.%s", extension);
   else
-    sprintf(basename, "%03d.%s", nr, LEVELFILE_EXTENSION);
+    sprintf(basename, "%03d.%s", nr, extension);
 
   return basename;
 }
 
+static char *getSingleLevelBasename(int nr)
+{
+  return getSingleLevelBasenameExt(nr, LEVELFILE_EXTENSION);
+}
+
 static char *getPackedLevelBasename(int type)
 {
   static char basename[MAX_FILENAME_LEN];
@@ -1959,9 +2217,33 @@ static void determineLevelFileInfo_Filename(struct LevelFileInfo *lfi)
   /* special case: level number is negative => check for level template file */
   if (nr < 0)
   {
+#if 1
+    /* global variable "leveldir_current" must be modified in the loop below */
+    LevelDirTree *leveldir_current_last = leveldir_current;
+
+    /* check for template level in path from current to topmost tree node */
+
+    while (leveldir_current != NULL)
+    {
+      setLevelFileInfo_FormatLevelFilename(lfi, LEVEL_FILE_TYPE_RND,
+                                          "template.%s", LEVELFILE_EXTENSION);
+
+      if (fileExists(lfi->filename))
+       break;
+
+      leveldir_current = leveldir_current->node_parent;
+    }
+
+    /* restore global variable "leveldir_current" modified in above loop */
+    leveldir_current = leveldir_current_last;
+
+#else
+
     setLevelFileInfo_FormatLevelFilename(lfi, LEVEL_FILE_TYPE_RND,
                                         "template.%s", LEVELFILE_EXTENSION);
 
+#endif
+
     /* no fallback if template file not existing */
     return;
   }
@@ -2139,6 +2421,8 @@ static int LoadLevel_DATE(FILE *file, int chunk_size, struct LevelInfo *level)
   level->creation_date.month = getFile8Bit(file);
   level->creation_date.day   = getFile8Bit(file);
 
+  level->creation_date.src   = DATE_SRC_LEVELFILE;
+
   return chunk_size;
 }
 
@@ -2300,13 +2584,21 @@ static int LoadLevel_CNT2(FILE *file, int chunk_size, struct LevelInfo *level)
 {
   int i, x, y;
   int element;
-  int num_contents, content_xsize, content_ysize;
+  int num_contents;
+#if 0
+  int content_xsize, content_ysize;
+#endif
   int content_array[MAX_ELEMENT_CONTENTS][3][3];
 
   element = getMappedElement(getFile16BitBE(file));
   num_contents = getFile8Bit(file);
+#if 1
+  getFile8Bit(file);   /* content x size (unused) */
+  getFile8Bit(file);   /* content y size (unused) */
+#else
   content_xsize = getFile8Bit(file);
   content_ysize = getFile8Bit(file);
+#endif
 
   ReadUnusedBytesFromFile(file, LEVEL_CHUNK_CNT2_UNUSED);
 
@@ -3030,7 +3322,8 @@ static int LoadLevel_GRPX(FILE *file, int chunk_size, struct LevelInfo *level)
 }
 
 static void LoadLevelFromFileInfo_RND(struct LevelInfo *level,
-                                     struct LevelFileInfo *level_file_info)
+                                     struct LevelFileInfo *level_file_info,
+                                     boolean level_info_only)
 {
   char *filename = level_file_info->filename;
   char cookie[MAX_LINE_LEN];
@@ -3042,8 +3335,13 @@ static void LoadLevelFromFileInfo_RND(struct LevelInfo *level,
   {
     level->no_valid_file = TRUE;
 
+#if 1
+    if (!level_info_only)
+      Error(ERR_WARN, "cannot read level '%s' -- using empty level", filename);
+#else
     if (level != &level_template)
       Error(ERR_WARN, "cannot read level '%s' -- using empty level", filename);
+#endif
 
     return;
   }
@@ -3066,7 +3364,8 @@ static void LoadLevelFromFileInfo_RND(struct LevelInfo *level,
   else /* check for pre-2.0 file format with cookie string */
   {
     strcpy(cookie, chunk_name);
-    fgets(&cookie[4], MAX_LINE_LEN - 4, file);
+    if (fgets(&cookie[4], MAX_LINE_LEN - 4, file) == NULL)
+      cookie[4] = '\0';
     if (strlen(cookie) > 0 && cookie[strlen(cookie) - 1] == '\n')
       cookie[strlen(cookie) - 1] = '\0';
 
@@ -3997,7 +4296,8 @@ static void LoadLevelFromFileStream_SP(FILE *file, struct LevelInfo *level,
 }
 
 static void LoadLevelFromFileInfo_SP(struct LevelInfo *level,
-                                    struct LevelFileInfo *level_file_info)
+                                    struct LevelFileInfo *level_file_info,
+                                    boolean level_info_only)
 {
   char *filename = level_file_info->filename;
   FILE *file;
@@ -4015,7 +4315,8 @@ static void LoadLevelFromFileInfo_SP(struct LevelInfo *level,
   {
     level->no_valid_file = TRUE;
 
-    Error(ERR_WARN, "cannot read level '%s' -- using empty level", filename);
+    if (!level_info_only)
+      Error(ERR_WARN, "cannot read level '%s' -- using empty level", filename);
 
     return;
   }
@@ -4184,29 +4485,12 @@ void CopyNativeLevel_RND_to_SP(struct LevelInfo *level)
   LevelInfoType *header = &level_sp->header;
   int i, x, y;
 
-  level_sp->width = level->fieldx;
+  level_sp->width  = level->fieldx;
   level_sp->height = level->fieldy;
 
   for (x = 0; x < level->fieldx; x++)
-  {
     for (y = 0; y < level->fieldy; y++)
-    {
-      int element_old = level->field[x][y];
-      int element_new;
-
-      if (element_old >= EL_SP_START &&
-         element_old <= EL_SP_END)
-       element_new = element_old - EL_SP_START;
-      else if (element_old == EL_EMPTY_SPACE)
-       element_new = 0x00;
-      else if (element_old == EL_INVISIBLE_WALL)
-       element_new = 0x28;
-      else
-       element_new = 0x20;     /* map unknown elements to yellow "hardware" */
-
-      level_sp->playfield[x][y] = element_new;
-    }
-  }
+      level_sp->playfield[x][y] = map_element_RND_to_SP(level->field[x][y]);
 
   header->InitialGravity = (level->initial_player_gravity[0] ? 1 : 0);
 
@@ -4216,7 +4500,67 @@ void CopyNativeLevel_RND_to_SP(struct LevelInfo *level)
 
   header->InfotronsNeeded = level->gems_needed;
 
-  /* !!! ADD SPECIAL PORT DATABASE STUFF !!! */
+  header->SpecialPortCount = 0;
+
+  for (x = 0; x < level->fieldx; x++) for (y = 0; y < level->fieldy; y++)
+  {
+    boolean gravity_port_found = FALSE;
+    boolean gravity_port_valid = FALSE;
+    int gravity_port_flag;
+    int gravity_port_base_element;
+    int element = level->field[x][y];
+
+    if (element >= EL_SP_GRAVITY_ON_PORT_RIGHT &&
+       element <= EL_SP_GRAVITY_ON_PORT_UP)
+    {
+      gravity_port_found = TRUE;
+      gravity_port_valid = TRUE;
+      gravity_port_flag = 1;
+      gravity_port_base_element = EL_SP_GRAVITY_ON_PORT_RIGHT;
+    }
+    else if (element >= EL_SP_GRAVITY_OFF_PORT_RIGHT &&
+            element <= EL_SP_GRAVITY_OFF_PORT_UP)
+    {
+      gravity_port_found = TRUE;
+      gravity_port_valid = TRUE;
+      gravity_port_flag = 0;
+      gravity_port_base_element = EL_SP_GRAVITY_OFF_PORT_RIGHT;
+    }
+    else if (element >= EL_SP_GRAVITY_PORT_RIGHT &&
+            element <= EL_SP_GRAVITY_PORT_UP)
+    {
+      /* change R'n'D style gravity inverting special port to normal port
+        (there are no gravity inverting ports in native Supaplex engine) */
+
+      gravity_port_found = TRUE;
+      gravity_port_valid = FALSE;
+      gravity_port_base_element = EL_SP_GRAVITY_PORT_RIGHT;
+    }
+
+    if (gravity_port_found)
+    {
+      if (gravity_port_valid &&
+         header->SpecialPortCount < SP_MAX_SPECIAL_PORTS)
+      {
+       SpecialPortType *port = &header->SpecialPort[header->SpecialPortCount];
+
+       port->PortLocation = (y * level->fieldx + x) * 2;
+       port->Gravity = gravity_port_flag;
+
+       element += EL_SP_GRAVITY_PORT_RIGHT - gravity_port_base_element;
+
+       header->SpecialPortCount++;
+      }
+      else
+      {
+       /* change special gravity port to normal port */
+
+       element += EL_SP_PORT_RIGHT - gravity_port_base_element;
+      }
+
+      level_sp->playfield[x][y] = element - EL_SP_START;
+    }
+  }
 }
 
 void CopyNativeLevel_SP_to_RND(struct LevelInfo *level)
@@ -4233,20 +4577,12 @@ void CopyNativeLevel_SP_to_RND(struct LevelInfo *level)
     for (y = 0; y < level->fieldy; y++)
     {
       int element_old = level_sp->playfield[x][y];
-      int element_new;
+      int element_new = getMappedElement(map_element_SP_to_RND(element_old));
 
-      if (element_old <= 0x27)
-       element_new = getMappedElement(EL_SP_START + element_old);
-      else if (element_old == 0x28)
-       element_new = EL_INVISIBLE_WALL;
-      else
-      {
+      if (element_new == EL_UNKNOWN)
        Error(ERR_WARN, "invalid element %d at position %d, %d",
              element_old, x, y);
 
-       element_new = EL_UNKNOWN;
-      }
-
       level->field[x][y] = element_new;
     }
   }
@@ -4324,22 +4660,61 @@ void CopyNativeLevel_SP_to_RND(struct LevelInfo *level)
        level->yamyam_content[i].e[x][y] = EL_EMPTY;
 }
 
+static void CopyNativeTape_RND_to_SP(struct LevelInfo *level)
+{
+  struct LevelInfo_SP *level_sp = level->native_sp_level;
+  struct DemoInfo_SP *demo = &level_sp->demo;
+  int i, j;
+
+  /* always start with reliable default values */
+  demo->is_available = FALSE;
+  demo->length = 0;
+
+  if (TAPE_IS_EMPTY(tape))
+    return;
+
+  demo->level_nr = tape.level_nr;      /* (currently not used) */
+
+  level_sp->header.DemoRandomSeed = tape.random_seed;
+
+  demo->length = 0;
+  for (i = 0; i < tape.length; i++)
+  {
+    int demo_action = map_key_RND_to_SP(tape.pos[i].action[0]);
+    int demo_repeat = tape.pos[i].delay;
+
+    for (j = 0; j < demo_repeat / 16; j++)
+      demo->data[demo->length++] = 0xf0 | demo_action;
+
+    if (demo_repeat % 16)
+      demo->data[demo->length++] = ((demo_repeat % 16 - 1) << 4) | demo_action;
+  }
+
+  demo->data[demo->length++] = 0xff;
+
+  demo->is_available = TRUE;
+}
+
 static void setTapeInfoToDefaults();
 
 static void CopyNativeTape_SP_to_RND(struct LevelInfo *level)
 {
   struct LevelInfo_SP *level_sp = level->native_sp_level;
   struct DemoInfo_SP *demo = &level_sp->demo;
+  char *filename = level->file_info.filename;
   int i;
 
   /* always start with reliable default values */
   setTapeInfoToDefaults();
 
+  if (!demo->is_available)
+    return;
+
   tape.level_nr = demo->level_nr;      /* (currently not used) */
   tape.length = demo->length - 1;      /* without "end of demo" byte */
   tape.random_seed = level_sp->header.DemoRandomSeed;
 
-  // tape.date = <SET FROM FILE DATE OF *.SP FILE>
+  TapeSetDateFromEpochSeconds(getFileTimestampEpochSeconds(filename));
 
   for (i = 0; i < demo->length - 1; i++)
   {
@@ -6018,7 +6393,8 @@ static void LoadLevelFromFileStream_DC(FILE *file, struct LevelInfo *level,
 }
 
 static void LoadLevelFromFileInfo_DC(struct LevelInfo *level,
-                                    struct LevelFileInfo *level_file_info)
+                                    struct LevelFileInfo *level_file_info,
+                                    boolean level_info_only)
 {
   char *filename = level_file_info->filename;
   FILE *file;
@@ -6030,7 +6406,8 @@ static void LoadLevelFromFileInfo_DC(struct LevelInfo *level,
   {
     level->no_valid_file = TRUE;
 
-    Error(ERR_WARN, "cannot read level '%s' -- using empty level", filename);
+    if (!level_info_only)
+      Error(ERR_WARN, "cannot read level '%s' -- using empty level", filename);
 
     return;
   }
@@ -6040,7 +6417,8 @@ static void LoadLevelFromFileInfo_DC(struct LevelInfo *level,
   if (level_file_info->packed)
   {
     /* read "magic bytes" from start of file */
-    fgets(magic_bytes, num_magic_bytes + 1, file);
+    if (fgets(magic_bytes, num_magic_bytes + 1, file) == NULL)
+      magic_bytes[0] = '\0';
 
     /* check "magic bytes" for correct file format */
     if (!strPrefix(magic_bytes, "DC2"))
@@ -6133,7 +6511,8 @@ static void LoadLevelFromFileInfo_DC(struct LevelInfo *level,
   {
     level->no_valid_file = TRUE;
 
-    Error(ERR_WARN, "cannot read level '%s' -- using empty level", filename);
+    if (!level_info_only)
+      Error(ERR_WARN, "cannot read level '%s' -- using empty level", filename);
 
     return;
   }
@@ -6331,40 +6710,6 @@ static void LoadLevelFromFileInfo_DC(struct LevelInfo *level,
 /* functions for loading SB level                                            */
 /* ------------------------------------------------------------------------- */
 
-#if 1
-
-static boolean check_special_flags(char *flag)
-{
-#if 0
-  printf("::: '%s', '%s', '%s'\n",
-        flag,
-        options.special_flags,
-        leveldir_current->special_flags);
-#endif
-
-  if (strEqual(options.special_flags, flag) ||
-      strEqual(leveldir_current->special_flags, flag))
-    return TRUE;
-
-  return FALSE;
-}
-
-#else
-
-#define SPECIAL_FLAG_LOAD_XSB_TO_CES   (1 << 0)
-
-static unsigned long get_special_flags(char *flags_string)
-{
-  unsigned long flags_value = 0;
-
-  if (strEqual(flags_string, "load_xsb_to_ces"))
-    flags_value = SPECIAL_FLAG_LOAD_XSB_TO_CES;
-
-  return flags_value;
-}
-
-#endif
-
 int getMappedElement_SB(int element_ascii, boolean use_ces)
 {
   static struct
@@ -6382,7 +6727,11 @@ int getMappedElement_SB(int element_ascii, boolean use_ces)
     { '.', EL_SOKOBAN_FIELD_EMPTY,  EL_CUSTOM_5 },  /* goal square */
     { '*', EL_SOKOBAN_FIELD_FULL,   EL_CUSTOM_6 },  /* box on goal square */
     { '+', EL_SOKOBAN_FIELD_PLAYER, EL_CUSTOM_7 },  /* player on goal square */
+#if 0
     { '_', EL_INVISIBLE_STEELWALL,  EL_CUSTOM_8 },  /* floor beyond border */
+#else
+    { '_', EL_INVISIBLE_STEELWALL,  EL_FROM_LEVEL_TEMPLATE },  /* floor beyond border */
+#endif
 
     { 0,   -1,                      -1          },
   };
@@ -6397,7 +6746,8 @@ int getMappedElement_SB(int element_ascii, boolean use_ces)
 }
 
 static void LoadLevelFromFileInfo_SB(struct LevelInfo *level,
-                                    struct LevelFileInfo *level_file_info)
+                                    struct LevelFileInfo *level_file_info,
+                                    boolean level_info_only)
 {
   char *filename = level_file_info->filename;
   char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
@@ -6410,14 +6760,10 @@ static void LoadLevelFromFileInfo_SB(struct LevelInfo *level,
   boolean reading_playfield = FALSE;
   boolean got_valid_playfield_line = FALSE;
   boolean invalid_playfield_char = FALSE;
-#if 1
   boolean load_xsb_to_ces = check_special_flags("load_xsb_to_ces");
-#else
-  boolean load_xsb_to_ces = options.special_flags & SPECIAL_FLAG_LOAD_XSB_TO_CES;
-#endif
   int file_level_nr = 0;
   int line_nr = 0;
-  int x, y;
+  int x = 0, y = 0;            /* initialized to make compilers happy */
 
 #if 0
   printf("::: looking for level number %d [%d]\n",
@@ -6431,7 +6777,8 @@ static void LoadLevelFromFileInfo_SB(struct LevelInfo *level,
   {
     level->no_valid_file = TRUE;
 
-    Error(ERR_WARN, "cannot read level '%s' -- using empty level", filename);
+    if (!level_info_only)
+      Error(ERR_WARN, "cannot read level '%s' -- using empty level", filename);
 
     return;
   }
@@ -6659,12 +7006,44 @@ static void LoadLevelFromFileInfo_SB(struct LevelInfo *level,
                     level->field, level->fieldx, level->fieldy);
   }
 
+  /* set special level settings for Sokoban levels */
+
+  level->time = 0;
+  level->use_step_counter = TRUE;
+
   if (load_xsb_to_ces)
   {
-    level->time = 0;
-    level->use_step_counter = TRUE;
-
+#if 1
+    /* !!! special global settings can now be set in level template !!! */
+#else
     level->initial_player_stepsize[0] = STEPSIZE_SLOW;
+#endif
+
+    /* fill smaller playfields with padding "beyond border wall" elements */
+    if (level->fieldx < SCR_FIELDX ||
+       level->fieldy < SCR_FIELDY)
+    {
+      short field[level->fieldx][level->fieldy];
+      int new_fieldx = MAX(level->fieldx, SCR_FIELDX);
+      int new_fieldy = MAX(level->fieldy, SCR_FIELDY);
+      int pos_fieldx = (new_fieldx - level->fieldx) / 2;
+      int pos_fieldy = (new_fieldy - level->fieldy) / 2;
+
+      /* copy old playfield (which is smaller than the visible area) */
+      for (y = 0; y < level->fieldy; y++) for (x = 0; x < level->fieldx; x++)
+       field[x][y] = level->field[x][y];
+
+      /* fill new, larger playfield with "beyond border wall" elements */
+      for (y = 0; y < new_fieldy; y++) for (x = 0; x < new_fieldx; x++)
+       level->field[x][y] = getMappedElement_SB('_', load_xsb_to_ces);
+
+      /* copy the old playfield to the middle of the new playfield */
+      for (y = 0; y < level->fieldy; y++) for (x = 0; x < level->fieldx; x++)
+       level->field[pos_fieldx + x][pos_fieldy + y] = field[x][y];
+
+      level->fieldx = new_fieldx;
+      level->fieldy = new_fieldy;
+    }
 
     level->use_custom_template = TRUE;
   }
@@ -6676,14 +7055,16 @@ static void LoadLevelFromFileInfo_SB(struct LevelInfo *level,
 /* ------------------------------------------------------------------------- */
 
 static void LoadLevelFromFileInfo_EM(struct LevelInfo *level,
-                                    struct LevelFileInfo *level_file_info)
+                                    struct LevelFileInfo *level_file_info,
+                                    boolean level_info_only)
 {
-  if (!LoadNativeLevel_EM(level_file_info->filename))
+  if (!LoadNativeLevel_EM(level_file_info->filename, level_info_only))
     level->no_valid_file = TRUE;
 }
 
 static void LoadLevelFromFileInfo_SP(struct LevelInfo *level,
-                                    struct LevelFileInfo *level_file_info)
+                                    struct LevelFileInfo *level_file_info,
+                                    boolean level_info_only)
 {
   int pos = 0;
 
@@ -6691,7 +7072,7 @@ static void LoadLevelFromFileInfo_SP(struct LevelInfo *level,
   if (level_file_info->packed)
     pos = level_file_info->nr - leveldir_current->first_level;
 
-  if (!LoadNativeLevel_SP(level_file_info->filename, pos))
+  if (!LoadNativeLevel_SP(level_file_info->filename, pos, level_info_only))
     level->no_valid_file = TRUE;
 }
 
@@ -6711,49 +7092,68 @@ void CopyNativeLevel_Native_to_RND(struct LevelInfo *level)
     CopyNativeLevel_SP_to_RND(level);
 }
 
+void SaveNativeLevel(struct LevelInfo *level)
+{
+  if (level->game_engine_type == GAME_ENGINE_TYPE_SP)
+  {
+    char *basename = getSingleLevelBasenameExt(level->file_info.nr, "sp");
+    char *filename = getLevelFilenameFromBasename(basename);
+
+    CopyNativeLevel_RND_to_SP(level);
+    CopyNativeTape_RND_to_SP(level);
+
+    SaveNativeLevel_SP(filename);
+  }
+}
+
 
 /* ------------------------------------------------------------------------- */
 /* functions for loading generic level                                       */
 /* ------------------------------------------------------------------------- */
 
-void LoadLevelFromFileInfo(struct LevelInfo *level,
-                          struct LevelFileInfo *level_file_info)
+static void LoadLevelFromFileInfo(struct LevelInfo *level,
+                                 struct LevelFileInfo *level_file_info,
+                                 boolean level_info_only)
 {
   /* always start with reliable default values */
-  setLevelInfoToDefaults(level);
+  setLevelInfoToDefaults(level, level_info_only);
 
   switch (level_file_info->type)
   {
     case LEVEL_FILE_TYPE_RND:
-      LoadLevelFromFileInfo_RND(level, level_file_info);
+      LoadLevelFromFileInfo_RND(level, level_file_info, level_info_only);
       break;
 
     case LEVEL_FILE_TYPE_EM:
-      LoadLevelFromFileInfo_EM(level, level_file_info);
+      LoadLevelFromFileInfo_EM(level, level_file_info, level_info_only);
       level->game_engine_type = GAME_ENGINE_TYPE_EM;
       break;
 
     case LEVEL_FILE_TYPE_SP:
-      LoadLevelFromFileInfo_SP(level, level_file_info);
+      LoadLevelFromFileInfo_SP(level, level_file_info, level_info_only);
       level->game_engine_type = GAME_ENGINE_TYPE_SP;
       break;
 
     case LEVEL_FILE_TYPE_DC:
-      LoadLevelFromFileInfo_DC(level, level_file_info);
+      LoadLevelFromFileInfo_DC(level, level_file_info, level_info_only);
       break;
 
     case LEVEL_FILE_TYPE_SB:
-      LoadLevelFromFileInfo_SB(level, level_file_info);
+      LoadLevelFromFileInfo_SB(level, level_file_info, level_info_only);
       break;
 
     default:
-      LoadLevelFromFileInfo_RND(level, level_file_info);
+      LoadLevelFromFileInfo_RND(level, level_file_info, level_info_only);
       break;
   }
 
   /* if level file is invalid, restore level structure to default values */
   if (level->no_valid_file)
-    setLevelInfoToDefaults(level);
+  {
+    setLevelInfoToDefaults(level, level_info_only);
+
+    level->no_valid_file = TRUE;       /* but keep "no valid file" flag */
+  }
 
   if (level->game_engine_type == GAME_ENGINE_TYPE_UNKNOWN)
     level->game_engine_type = GAME_ENGINE_TYPE_RND;
@@ -6773,7 +7173,7 @@ void LoadLevelFromFilename(struct LevelInfo *level, char *filename)
   level_file_info.type = LEVEL_FILE_TYPE_RND;  /* no others supported yet */
   level_file_info.filename = filename;
 
-  LoadLevelFromFileInfo(level, &level_file_info);
+  LoadLevelFromFileInfo(level, &level_file_info, FALSE);
 }
 
 static void LoadLevel_InitVersion(struct LevelInfo *level, char *filename)
@@ -7161,7 +7561,7 @@ void LoadLevelTemplate(int nr)
   setLevelFileInfo(&level_template.file_info, nr);
   filename = level_template.file_info.filename;
 
-  LoadLevelFromFileInfo(&level_template, &level_template.file_info);
+  LoadLevelFromFileInfo(&level_template, &level_template.file_info, FALSE);
 
   LoadLevel_InitVersion(&level_template, filename);
   LoadLevel_InitElements(&level_template, filename);
@@ -7176,7 +7576,7 @@ void LoadLevel(int nr)
   setLevelFileInfo(&level.file_info, nr);
   filename = level.file_info.filename;
 
-  LoadLevelFromFileInfo(&level, &level.file_info);
+  LoadLevelFromFileInfo(&level, &level.file_info, FALSE);
 
   if (level.use_custom_template)
     LoadLevelTemplate(-1);
@@ -7188,6 +7588,20 @@ void LoadLevel(int nr)
   LoadLevel_InitNativeEngines(&level, filename);
 }
 
+void LoadLevelInfoOnly(int nr)
+{
+#if 0
+  char *filename;
+#endif
+
+  setLevelFileInfo(&level.file_info, nr);
+#if 0
+  filename = level.file_info.filename;
+#endif
+
+  LoadLevelFromFileInfo(&level, &level.file_info, TRUE);
+}
+
 static int SaveLevel_VERS(FILE *file, struct LevelInfo *level)
 {
   int chunk_size = 0;
@@ -8272,6 +8686,79 @@ static int LoadTape_BODY(FILE *file, int chunk_size, struct TapeInfo *tape)
   return chunk_size;
 }
 
+void LoadTape_SokobanSolution(char *filename)
+{
+  FILE *file;
+  int move_delay = TILESIZE / level.initial_player_stepsize[0];
+
+  if (!(file = fopen(filename, MODE_READ)))
+  {
+    tape.no_valid_file = TRUE;
+
+    return;
+  }
+
+  while (!feof(file))
+  {
+    unsigned char c = fgetc(file);
+
+    if (feof(file))
+      break;
+
+    switch (c)
+    {
+      case 'u':
+      case 'U':
+       tape.pos[tape.length].action[0] = MV_UP;
+       tape.pos[tape.length].delay = move_delay + (c < 'a' ? 2 : 0);
+       tape.length++;
+       break;
+
+      case 'd':
+      case 'D':
+       tape.pos[tape.length].action[0] = MV_DOWN;
+       tape.pos[tape.length].delay = move_delay + (c < 'a' ? 2 : 0);
+       tape.length++;
+       break;
+
+      case 'l':
+      case 'L':
+       tape.pos[tape.length].action[0] = MV_LEFT;
+       tape.pos[tape.length].delay = move_delay + (c < 'a' ? 2 : 0);
+       tape.length++;
+       break;
+
+      case 'r':
+      case 'R':
+       tape.pos[tape.length].action[0] = MV_RIGHT;
+       tape.pos[tape.length].delay = move_delay + (c < 'a' ? 2 : 0);
+       tape.length++;
+       break;
+
+      case '\n':
+      case '\r':
+      case '\t':
+      case ' ':
+       /* ignore white-space characters */
+       break;
+
+      default:
+       tape.no_valid_file = TRUE;
+
+       Error(ERR_WARN, "unsupported Sokoban solution file '%s' ['%d']", filename, c);
+
+       break;
+    }
+  }
+
+  fclose(file);
+
+  if (tape.no_valid_file)
+    return;
+
+  tape.length_seconds = GetTapeLength();
+}
+
 void LoadTapeFromFilename(char *filename)
 {
   char cookie[MAX_LINE_LEN];
@@ -8282,6 +8769,13 @@ void LoadTapeFromFilename(char *filename)
   /* always start with reliable default values */
   setTapeInfoToDefaults();
 
+  if (strSuffix(filename, ".sln"))
+  {
+    LoadTape_SokobanSolution(filename);
+
+    return;
+  }
+
   if (!(file = fopen(filename, MODE_READ)))
   {
     tape.no_valid_file = TRUE;
@@ -8307,7 +8801,8 @@ void LoadTapeFromFilename(char *filename)
   else /* check for pre-2.0 file format with cookie string */
   {
     strcpy(cookie, chunk_name);
-    fgets(&cookie[4], MAX_LINE_LEN - 4, file);
+    if (fgets(&cookie[4], MAX_LINE_LEN - 4, file) == NULL)
+      cookie[4] = '\0';
     if (strlen(cookie) > 0 && cookie[strlen(cookie) - 1] == '\n')
       cookie[strlen(cookie) - 1] = '\0';
 
@@ -8326,6 +8821,7 @@ void LoadTapeFromFilename(char *filename)
 
       Error(ERR_WARN, "unsupported version of tape file '%s'", filename);
       fclose(file);
+
       return;
     }
 
@@ -8649,7 +9145,8 @@ void LoadScore(int nr)
     return;
 
   /* check file identifier */
-  fgets(cookie, MAX_LINE_LEN, file);
+  if (fgets(cookie, MAX_LINE_LEN, file) == NULL)
+    cookie[0] = '\0';
   if (strlen(cookie) > 0 && cookie[strlen(cookie) - 1] == '\n')
     cookie[strlen(cookie) - 1] = '\0';
 
@@ -8662,10 +9159,12 @@ void LoadScore(int nr)
 
   for (i = 0; i < MAX_SCORE_ENTRIES; i++)
   {
-    fscanf(file, "%d", &highscore[i].Score);
-    fgets(line, MAX_LINE_LEN, file);
+    if (fscanf(file, "%d", &highscore[i].Score) == EOF)
+      Error(ERR_WARN, "fscanf() failed; %s", strerror(errno));
+    if (fgets(line, MAX_LINE_LEN, file) == NULL)
+      line[0] = '\0';
 
-    if (line[strlen(line) - 1] == '\n')
+    if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
       line[strlen(line) - 1] = '\0';
 
     for (line_ptr = line; *line_ptr; line_ptr++)
@@ -8740,14 +9239,15 @@ void SaveScore(int nr)
 #define SETUP_TOKEN_PREFER_AGA_GRAPHICS                23
 #define SETUP_TOKEN_GAME_FRAME_DELAY           24
 #define SETUP_TOKEN_SP_SHOW_BORDER_ELEMENTS    25
-#define SETUP_TOKEN_GRAPHICS_SET               26
-#define SETUP_TOKEN_SOUNDS_SET                 27
-#define SETUP_TOKEN_MUSIC_SET                  28
-#define SETUP_TOKEN_OVERRIDE_LEVEL_GRAPHICS    29
-#define SETUP_TOKEN_OVERRIDE_LEVEL_SOUNDS      30
-#define SETUP_TOKEN_OVERRIDE_LEVEL_MUSIC       31
+#define SETUP_TOKEN_SMALL_GAME_GRAPHICS                26
+#define SETUP_TOKEN_GRAPHICS_SET               27
+#define SETUP_TOKEN_SOUNDS_SET                 28
+#define SETUP_TOKEN_MUSIC_SET                  29
+#define SETUP_TOKEN_OVERRIDE_LEVEL_GRAPHICS    30
+#define SETUP_TOKEN_OVERRIDE_LEVEL_SOUNDS      31
+#define SETUP_TOKEN_OVERRIDE_LEVEL_MUSIC       32
 
-#define NUM_GLOBAL_SETUP_TOKENS                        32
+#define NUM_GLOBAL_SETUP_TOKENS                        33
 
 /* editor setup */
 #define SETUP_TOKEN_EDITOR_EL_BOULDERDASH      0
@@ -8798,8 +9298,21 @@ void SaveScore(int nr)
 #define SETUP_TOKEN_SHORTCUT_FOCUS_PLAYER_3    5
 #define SETUP_TOKEN_SHORTCUT_FOCUS_PLAYER_4    6
 #define SETUP_TOKEN_SHORTCUT_FOCUS_PLAYER_ALL  7
-
-#define NUM_SHORTCUT_SETUP_TOKENS              8
+#define SETUP_TOKEN_SHORTCUT_TAPE_EJECT                8
+#define SETUP_TOKEN_SHORTCUT_TAPE_EXTRA                9
+#define SETUP_TOKEN_SHORTCUT_TAPE_STOP         10
+#define SETUP_TOKEN_SHORTCUT_TAPE_PAUSE                11
+#define SETUP_TOKEN_SHORTCUT_TAPE_RECORD       12
+#define SETUP_TOKEN_SHORTCUT_TAPE_PLAY         13
+#define SETUP_TOKEN_SHORTCUT_SOUND_SIMPLE      14
+#define SETUP_TOKEN_SHORTCUT_SOUND_LOOPS       15
+#define SETUP_TOKEN_SHORTCUT_SOUND_MUSIC       16
+#define SETUP_TOKEN_SHORTCUT_SNAP_LEFT         17
+#define SETUP_TOKEN_SHORTCUT_SNAP_RIGHT                18
+#define SETUP_TOKEN_SHORTCUT_SNAP_UP           19
+#define SETUP_TOKEN_SHORTCUT_SNAP_DOWN         20
+
+#define NUM_SHORTCUT_SETUP_TOKENS              21
 
 /* player setup */
 #define SETUP_TOKEN_PLAYER_USE_JOYSTICK                0
@@ -8870,6 +9383,7 @@ static struct TokenInfo global_setup_tokens[] =
   { TYPE_SWITCH, &si.prefer_aga_graphics,     "prefer_aga_graphics"    },
   { TYPE_INTEGER,&si.game_frame_delay,        "game_frame_delay"       },
   { TYPE_SWITCH, &si.sp_show_border_elements, "sp_show_border_elements"        },
+  { TYPE_SWITCH, &si.small_game_graphics,     "small_game_graphics"    },
   { TYPE_STRING, &si.graphics_set,            "graphics_set"           },
   { TYPE_STRING, &si.sounds_set,              "sounds_set"             },
   { TYPE_STRING, &si.music_set,               "music_set"              },
@@ -8944,6 +9458,19 @@ static struct TokenInfo shortcut_setup_tokens[] =
   { TYPE_KEY_X11, &ssi.focus_player[2],        "shortcut.focus_player_3"       },
   { TYPE_KEY_X11, &ssi.focus_player[3],        "shortcut.focus_player_4"       },
   { TYPE_KEY_X11, &ssi.focus_player_all,"shortcut.focus_player_all"    },
+  { TYPE_KEY_X11, &ssi.tape_eject,     "shortcut.tape_eject"           },
+  { TYPE_KEY_X11, &ssi.tape_extra,     "shortcut.tape_extra"           },
+  { TYPE_KEY_X11, &ssi.tape_stop,      "shortcut.tape_stop"            },
+  { TYPE_KEY_X11, &ssi.tape_pause,     "shortcut.tape_pause"           },
+  { TYPE_KEY_X11, &ssi.tape_record,    "shortcut.tape_record"          },
+  { TYPE_KEY_X11, &ssi.tape_play,      "shortcut.tape_play"            },
+  { TYPE_KEY_X11, &ssi.sound_simple,   "shortcut.sound_simple"         },
+  { TYPE_KEY_X11, &ssi.sound_loops,    "shortcut.sound_loops"          },
+  { TYPE_KEY_X11, &ssi.sound_music,    "shortcut.sound_music"          },
+  { TYPE_KEY_X11, &ssi.snap_left,      "shortcut.snap_left"            },
+  { TYPE_KEY_X11, &ssi.snap_right,     "shortcut.snap_right"           },
+  { TYPE_KEY_X11, &ssi.snap_up,                "shortcut.snap_up"              },
+  { TYPE_KEY_X11, &ssi.snap_down,      "shortcut.snap_down"            },
 };
 
 static struct TokenInfo player_setup_tokens[] =
@@ -9024,6 +9551,7 @@ static void setSetupInfoToDefaults(struct SetupInfo *si)
   si->prefer_aga_graphics = TRUE;
   si->game_frame_delay = GAME_FRAME_DELAY;
   si->sp_show_border_elements = FALSE;
+  si->small_game_graphics = FALSE;
 
   si->graphics_set = getStringCopy(GFX_DEFAULT_SUBDIR);
   si->sounds_set = getStringCopy(SND_DEFAULT_SUBDIR);
@@ -9060,6 +9588,22 @@ static void setSetupInfoToDefaults(struct SetupInfo *si)
   si->shortcut.focus_player[3] = DEFAULT_KEY_FOCUS_PLAYER_4;
   si->shortcut.focus_player_all        = DEFAULT_KEY_FOCUS_PLAYER_ALL;
 
+  si->shortcut.tape_eject      = DEFAULT_KEY_TAPE_EJECT;
+  si->shortcut.tape_extra      = DEFAULT_KEY_TAPE_EXTRA;
+  si->shortcut.tape_stop       = DEFAULT_KEY_TAPE_STOP;
+  si->shortcut.tape_pause      = DEFAULT_KEY_TAPE_PAUSE;
+  si->shortcut.tape_record     = DEFAULT_KEY_TAPE_RECORD;
+  si->shortcut.tape_play       = DEFAULT_KEY_TAPE_PLAY;
+
+  si->shortcut.sound_simple    = DEFAULT_KEY_SOUND_SIMPLE;
+  si->shortcut.sound_loops     = DEFAULT_KEY_SOUND_LOOPS;
+  si->shortcut.sound_music     = DEFAULT_KEY_SOUND_MUSIC;
+
+  si->shortcut.snap_left       = DEFAULT_KEY_SNAP_LEFT;
+  si->shortcut.snap_right      = DEFAULT_KEY_SNAP_RIGHT;
+  si->shortcut.snap_up         = DEFAULT_KEY_SNAP_UP;
+  si->shortcut.snap_down       = DEFAULT_KEY_SNAP_DOWN;
+
   for (i = 0; i < MAX_PLAYERS; i++)
   {
     si->input[i].use_joystick = FALSE;
@@ -9087,6 +9631,7 @@ static void setSetupInfoToDefaults(struct SetupInfo *si)
   si->options.verbose = FALSE;
 
 #if defined(CREATE_SPECIAL_EDITION_RND_JUE)
+  si->toons = FALSE;
   si->handicap = FALSE;
   si->fullscreen = TRUE;
   si->override_level_graphics = AUTO;
@@ -9520,6 +10065,16 @@ static void InitMenuDesignSettings_SpecialPreProcessing()
     menu.enter_screen[i] = menu.enter_screen[GFX_SPECIAL_ARG_DEFAULT];
     menu.leave_screen[i] = menu.leave_screen[GFX_SPECIAL_ARG_DEFAULT];
   }
+
+  /* special case: initialize "ARG_DEFAULT" values in static default config */
+  /* (eg, init "viewport.door_1.MAIN.xyz" from "viewport.door_1.xyz") */
+  for (i = 0; i < NUM_SPECIAL_GFX_ARGS; i++)
+  {
+    viewport.playfield[i] = viewport.playfield[GFX_SPECIAL_ARG_DEFAULT];
+    viewport.door_1[i] = viewport.door_1[GFX_SPECIAL_ARG_DEFAULT];
+    if (i != GFX_SPECIAL_ARG_EDITOR)   /* editor value already initialized */
+      viewport.door_2[i] = viewport.door_2[GFX_SPECIAL_ARG_DEFAULT];
+  }
 }
 
 static void InitMenuDesignSettings_SpecialPostProcessing()
@@ -9657,6 +10212,50 @@ static void LoadMenuDesignSettingsFromFilename(char *filename)
                                                                  value_6);
   }
 
+  /* special case: initialize with default values that may be overwritten */
+  /* (eg, init "viewport.door_1.MAIN.xyz" from "viewport.door_1.xyz") */
+  for (i = 0; i < NUM_SPECIAL_GFX_ARGS; i++)
+  {
+    char *token_1 = "viewport.playfield.x";
+    char *token_2 = "viewport.playfield.y";
+    char *token_3 = "viewport.playfield.width";
+    char *token_4 = "viewport.playfield.height";
+    char *token_5 = "viewport.playfield.border_size";
+    char *token_6 = "viewport.door_1.x";
+    char *token_7 = "viewport.door_1.y";
+    char *token_8 = "viewport.door_2.x";
+    char *token_9 = "viewport.door_2.y";
+    char *value_1 = getHashEntry(setup_file_hash, token_1);
+    char *value_2 = getHashEntry(setup_file_hash, token_2);
+    char *value_3 = getHashEntry(setup_file_hash, token_3);
+    char *value_4 = getHashEntry(setup_file_hash, token_4);
+    char *value_5 = getHashEntry(setup_file_hash, token_5);
+    char *value_6 = getHashEntry(setup_file_hash, token_6);
+    char *value_7 = getHashEntry(setup_file_hash, token_7);
+    char *value_8 = getHashEntry(setup_file_hash, token_8);
+    char *value_9 = getHashEntry(setup_file_hash, token_9);
+
+    if (value_1 != NULL)
+      viewport.playfield[i].x = get_token_parameter_value(token_1, value_1);
+    if (value_2 != NULL)
+      viewport.playfield[i].y = get_token_parameter_value(token_2, value_2);
+    if (value_3 != NULL)
+      viewport.playfield[i].width = get_token_parameter_value(token_3, value_3);
+    if (value_4 != NULL)
+      viewport.playfield[i].height = get_token_parameter_value(token_4,value_4);
+    if (value_5 != NULL)
+      viewport.playfield[i].border_size = get_token_parameter_value(token_5,
+                                                                   value_5);
+    if (value_6 != NULL)
+      viewport.door_1[i].x = get_token_parameter_value(token_6, value_6);
+    if (value_7 != NULL)
+      viewport.door_1[i].y = get_token_parameter_value(token_7, value_7);
+    if (value_8 != NULL)
+      viewport.door_2[i].x = get_token_parameter_value(token_8, value_8);
+    if (value_9 != NULL)
+      viewport.door_2[i].y = get_token_parameter_value(token_9, value_9);
+  }
+
   /* special case: initialize with default values that may be overwritten */
   /* (e.g., init "titlemessage_1.fade_mode" from "[titlemessage].fade_mode") */
   for (i = 0; titlemessage_arrays[i].array != NULL; i++)
@@ -9993,6 +10592,10 @@ void LoadMusicInfo()
     if (!music_info_listed(music_file_info, music->filename))
     {
       *new = get_music_file_info(music->filename, i);
+#if 0
+      if (*new != NULL)
+       printf(":1: adding '%s' ['%s'] ...\n", (*new)->title, music->filename);
+#endif
       if (*new != NULL)
        new = &(*new)->next;
     }
@@ -10038,6 +10641,10 @@ void LoadMusicInfo()
     if (!music_info_listed(music_file_info, basename))
     {
       *new = get_music_file_info(basename, MAP_NOCONF_MUSIC(num_music_noconf));
+#if 0
+      if (*new != NULL)
+       printf(":2: adding '%s' ['%s'] ...\n", (*new)->title, basename);
+#endif
       if (*new != NULL)
        new = &(*new)->next;
     }
@@ -10507,8 +11114,9 @@ void CreateLevelSketchImages()
     filename1 = getPath2(global.create_images_dir, basename1);
     filename2 = getPath2(global.create_images_dir, basename2);
 
-    getGraphicSource(graphic, 0, &src_bitmap, &src_x, &src_y);
-    BlitBitmap(src_bitmap, bitmap1, src_x, src_y, TILEX, TILEY, 0, 0);
+    getFixedGraphicSource(graphic, 0, &src_bitmap, &src_x, &src_y);
+    BlitBitmap(src_bitmap, bitmap1, src_x, src_y, TILEX, TILEY,
+              0, 0);
 
     if (SDL_SaveBMP(bitmap1->surface, filename1) != 0)
       Error(ERR_EXIT, "cannot save level sketch image file '%s'", filename1);
@@ -10559,7 +11167,7 @@ void CreateCustomElementImages()
                        TILEY * (NUM_CUSTOM_ELEMENTS + NUM_GROUP_ELEMENTS) / 16,
                        DEFAULT_DEPTH);
 
-  getGraphicSource(dummy_graphic, 0, &src_bitmap, &src_x, &src_y);
+  getFixedGraphicSource(dummy_graphic, 0, &src_bitmap, &src_x, &src_y);
 
   for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
   {
@@ -10571,18 +11179,22 @@ void CreateCustomElementImages()
     BlitBitmap(src_bitmap, bitmap, 0, 0, TILEX, TILEY,
               TILEX * x, TILEY * y + yoffset_ce);
 
-    BlitBitmap(src_bitmap, bitmap, 0, TILEY, TILEX, TILEY,
-              TILEX * x + TILEX * 16, TILEY * y + yoffset_ce);
+    BlitBitmap(src_bitmap, bitmap, 0, TILEY,
+              TILEX, TILEY,
+              TILEX * x + TILEX * 16,
+              TILEY * y + yoffset_ce);
 
     for (j = 2; j >= 0; j--)
     {
       int c = ii % 10;
 
-      BlitBitmap(src_bitmap, bitmap, TILEX + c * 7, 0, 6, 10,
+      BlitBitmap(src_bitmap, bitmap,
+                TILEX + c * 7, 0, 6, 10,
                 TILEX * x + 6 + j * 7,
                 TILEY * y + 11 + yoffset_ce);
 
-      BlitBitmap(src_bitmap, bitmap, TILEX + c * 8, TILEY, 6, 10,
+      BlitBitmap(src_bitmap, bitmap,
+                TILEX + c * 8, TILEY, 6, 10,
                 TILEX * 16 + TILEX * x + 6 + j * 8,
                 TILEY * y + 10 + yoffset_ce);
 
@@ -10600,8 +11212,10 @@ void CreateCustomElementImages()
     BlitBitmap(src_bitmap, bitmap, 0, 0, TILEX, TILEY,
               TILEX * x, TILEY * y + yoffset_ge);
 
-    BlitBitmap(src_bitmap, bitmap, 0, TILEY, TILEX, TILEY,
-              TILEX * x + TILEX * 16, TILEY * y + yoffset_ge);
+    BlitBitmap(src_bitmap, bitmap, 0, TILEY,
+              TILEX, TILEY,
+              TILEX * x + TILEX * 16,
+              TILEY * y + yoffset_ge);
 
     for (j = 1; j >= 0; j--)
     {
@@ -10611,7 +11225,8 @@ void CreateCustomElementImages()
                 TILEX * x + 6 + j * 10,
                 TILEY * y + 11 + yoffset_ge);
 
-      BlitBitmap(src_bitmap, bitmap, TILEX + c * 8, TILEY + 12, 6, 10,
+      BlitBitmap(src_bitmap, bitmap,
+                TILEX + c * 8, TILEY + 12, 6, 10,
                 TILEX * 16 + TILEX * x + 10 + j * 8,
                 TILEY * y + 10 + yoffset_ge);