fixed undefined signed integer overflow for expression "1 << 31"
[rocksndiamonds.git] / src / files.c
index 9a4999b857f0408ceca1a105ae73407eeb4aaefe..5d8f6479387a22bd0ec5c25424e865265bc37418 100644 (file)
@@ -22,6 +22,7 @@
 #include "tools.h"
 #include "tape.h"
 #include "config.h"
+#include "api.h"
 
 #define ENABLE_UNUSED_CODE     0       // currently unused functions
 #define ENABLE_HISTORIC_CHUNKS 0       // only for historic reference
@@ -51,6 +52,7 @@
 
 // (element number only)
 #define LEVEL_CHUNK_GRPX_UNCHANGED     2
+#define LEVEL_CHUNK_EMPX_UNCHANGED     2
 #define LEVEL_CHUNK_NOTE_UNCHANGED     2
 
 // (nothing at all if unchanged)
@@ -58,7 +60,6 @@
 
 #define TAPE_CHUNK_VERS_SIZE   8       // size of file version chunk
 #define TAPE_CHUNK_HEAD_SIZE   20      // size of tape file header
-#define TAPE_CHUNK_HEAD_UNUSED 1       // unused tape header bytes
 #define TAPE_CHUNK_SCRN_SIZE   2       // size of screen size chunk
 
 #define SCORE_CHUNK_VERS_SIZE  8       // size of file version chunk
@@ -1376,6 +1377,26 @@ static struct LevelFileConfigInfo chunk_config_GRPX[] =
   }
 };
 
+static struct LevelFileConfigInfo chunk_config_EMPX[] =
+{
+  {
+    -1,                                        -1,
+    TYPE_BOOLEAN,                      CONF_VALUE_8_BIT(1),
+    &xx_ei.use_gfx_element,            FALSE
+  },
+  {
+    -1,                                        -1,
+    TYPE_ELEMENT,                      CONF_VALUE_16_BIT(1),
+    &xx_ei.gfx_element_initial,                EL_EMPTY_SPACE
+  },
+
+  {
+    -1,                                        -1,
+    -1,                                        -1,
+    NULL,                              -1
+  }
+};
+
 static struct LevelFileConfigInfo chunk_config_CONF[] =                // (OBSOLETE)
 {
   {
@@ -1893,6 +1914,16 @@ static void setLevelInfoToDefaults_Elements(struct LevelInfo *level)
 
       *group = xx_group;
     }
+
+    if (IS_EMPTY_ELEMENT(element) ||
+       IS_INTERNAL_ELEMENT(element))
+    {
+      xx_ei = *ei;             // copy element data into temporary buffer
+
+      setConfigToDefaultsFromConfigList(chunk_config_EMPX);
+
+      *ei = xx_ei;
+    }
   }
 
   clipboard_elements_initialized = TRUE;
@@ -2814,9 +2845,10 @@ static int LoadLevel_CUS3(File *file, int chunk_size, struct LevelInfo *level)
       for (x = 0; x < 3; x++)
        ei->content.e[x][y] = getMappedElement(getFile16BitBE(file));
 
+    // bits 0 - 31 of "has_event[]"
     event_bits = getFile32BitBE(file);
-    for (j = 0; j < NUM_CHANGE_EVENTS; j++)
-      if (event_bits & (1 << j))
+    for (j = 0; j < MIN(NUM_CHANGE_EVENTS, 32); j++)
+      if (event_bits & (1u << j))
        ei->change->has_event[j] = TRUE;
 
     ei->change->target_element = getMappedElement(getFile16BitBE(file));
@@ -2952,7 +2984,7 @@ static int LoadLevel_CUS4(File *file, int chunk_size, struct LevelInfo *level)
     // bits 0 - 31 of "has_event[]" ...
     event_bits = getFile32BitBE(file);
     for (j = 0; j < MIN(NUM_CHANGE_EVENTS, 32); j++)
-      if (event_bits & (1 << j))
+      if (event_bits & (1u << j))
        change->has_event[j] = TRUE;
 
     change->target_element = getMappedElement(getFile16BitBE(file));
@@ -2993,7 +3025,7 @@ static int LoadLevel_CUS4(File *file, int chunk_size, struct LevelInfo *level)
     // ... bits 32 - 39 of "has_event[]" (not nice, but downward compatible)
     event_bits = getFile8Bit(file);
     for (j = 32; j < NUM_CHANGE_EVENTS; j++)
-      if (event_bits & (1 << (j - 32)))
+      if (event_bits & (1u << (j - 32)))
        change->has_event[j] = TRUE;
   }
 
@@ -3322,6 +3354,10 @@ static int LoadLevel_CUSX(File *file, int chunk_size, struct LevelInfo *level)
 
   while (!checkEndOfFile(file))
   {
+    // level file might contain invalid change page number
+    if (xx_current_change_page >= ei->num_change_pages)
+      break;
+
     struct ElementChangeInfo *change = &ei->change_page[xx_current_change_page];
 
     xx_change = *change;       // copy change data into temporary buffer
@@ -3351,6 +3387,9 @@ static int LoadLevel_GRPX(File *file, int chunk_size, struct LevelInfo *level)
   struct ElementInfo *ei = &element_info[element];
   struct ElementGroupInfo *group = ei->group;
 
+  if (group == NULL)
+    return -1;
+
   xx_ei = *ei;         // copy element data into temporary buffer
   xx_group = *group;   // copy group data into temporary buffer
 
@@ -3371,6 +3410,30 @@ static int LoadLevel_GRPX(File *file, int chunk_size, struct LevelInfo *level)
   return real_chunk_size;
 }
 
+static int LoadLevel_EMPX(File *file, int chunk_size, struct LevelInfo *level)
+{
+  int element = getMappedElement(getFile16BitBE(file));
+  int real_chunk_size = 2;
+  struct ElementInfo *ei = &element_info[element];
+
+  xx_ei = *ei;         // copy element data into temporary buffer
+
+  while (!checkEndOfFile(file))
+  {
+    real_chunk_size += LoadLevel_MicroChunk(file, chunk_config_EMPX,
+                                           -1, element);
+
+    if (real_chunk_size >= chunk_size)
+      break;
+  }
+
+  *ei = xx_ei;
+
+  level->file_has_custom_elements = TRUE;
+
+  return real_chunk_size;
+}
+
 static void LoadLevelFromFileInfo_RND(struct LevelInfo *level,
                                      struct LevelFileInfo *level_file_info,
                                      boolean level_info_only)
@@ -3493,6 +3556,7 @@ static void LoadLevelFromFileInfo_RND(struct LevelInfo *level,
       { "NOTE", -1,                    LoadLevel_NOTE },
       { "CUSX", -1,                    LoadLevel_CUSX },
       { "GRPX", -1,                    LoadLevel_GRPX },
+      { "EMPX", -1,                    LoadLevel_EMPX },
 
       {  NULL,  0,                     NULL }
     };
@@ -3526,6 +3590,14 @@ static void LoadLevelFromFileInfo_RND(struct LevelInfo *level,
        int chunk_size_expected =
          (chunk_info[i].loader)(file, chunk_size, level);
 
+       if (chunk_size_expected < 0)
+       {
+         Warn("error reading chunk '%s' in level file '%s'",
+              chunk_name, filename);
+
+         break;
+       }
+
        // the size of some chunks cannot be checked before reading other
        // chunks first (like "HEAD" and "BODY") that contain some header
        // information, so check them here
@@ -3533,6 +3605,8 @@ static void LoadLevelFromFileInfo_RND(struct LevelInfo *level,
        {
          Warn("wrong size (%d) of chunk '%s' in level file '%s'",
               chunk_size, chunk_name, filename);
+
+         break;
        }
       }
     }
@@ -6508,6 +6582,13 @@ static void LoadLevel_InitSettings(struct LevelInfo *level)
 {
   // adjust level settings for (non-native) Sokoban-style levels
   LoadLevel_InitSettings_SB(level);
+
+  // rename levels with title "nameless level" or if renaming is forced
+  if (leveldir_current->empty_level_name != NULL &&
+      (strEqual(level->name, NAMELESS_LEVEL_NAME) ||
+       leveldir_current->force_level_name))
+    snprintf(level->name, MAX_LEVEL_NAME_LEN + 1,
+            leveldir_current->empty_level_name, level_nr);
 }
 
 static void LoadLevel_InitStandardElements(struct LevelInfo *level)
@@ -7246,7 +7327,7 @@ static void SaveLevel_CUS4(FILE *file, struct LevelInfo *level, int element)
     event_bits = 0;
     for (j = 0; j < MIN(NUM_CHANGE_EVENTS, 32); j++)
       if (change->has_event[j])
-       event_bits |= (1 << j);
+       event_bits |= (1u << j);
     putFile32BitBE(file, event_bits);
 
     putFile16BitBE(file, change->target_element);
@@ -7286,7 +7367,7 @@ static void SaveLevel_CUS4(FILE *file, struct LevelInfo *level, int element)
     event_bits = 0;
     for (j = 32; j < NUM_CHANGE_EVENTS; j++)
       if (change->has_event[j])
-       event_bits |= (1 << (j - 32));
+       event_bits |= (1u << (j - 32));
     putFile8Bit(file, event_bits);
   }
 }
@@ -7537,6 +7618,22 @@ static int SaveLevel_GRPX(FILE *file, struct LevelInfo *level, int element)
   return chunk_size;
 }
 
+static int SaveLevel_EMPX(FILE *file, struct LevelInfo *level, int element)
+{
+  struct ElementInfo *ei = &element_info[element];
+  int chunk_size = 0;
+  int i;
+
+  chunk_size += putFile16BitBE(file, element);
+
+  xx_ei = *ei;         // copy element data into temporary buffer
+
+  for (i = 0; chunk_config_EMPX[i].data_type != -1; i++)
+    chunk_size += SaveLevel_MicroChunk(file, &chunk_config_EMPX[i], FALSE);
+
+  return chunk_size;
+}
+
 static void SaveLevelFromFilename(struct LevelInfo *level, char *filename,
                                  boolean save_as_template)
 {
@@ -7628,6 +7725,18 @@ static void SaveLevelFromFilename(struct LevelInfo *level, char *filename,
        SaveLevel_GRPX(file, level, element);
       }
     }
+
+    for (i = 0; i < NUM_EMPTY_ELEMENTS_ALL; i++)
+    {
+      int element = GET_EMPTY_ELEMENT(i);
+
+      chunk_size = SaveLevel_EMPX(NULL, level, element);
+      if (chunk_size > LEVEL_CHUNK_EMPX_UNCHANGED)     // save if changed
+      {
+       putFileChunkBE(file, "EMPX", chunk_size);
+       SaveLevel_EMPX(file, level, element);
+      }
+    }
   }
 
   fclose(file);
@@ -7705,6 +7814,28 @@ void DumpLevel(struct LevelInfo *level)
   Print("use step counter: %s\n", (level->use_step_counter ? "yes" : "no"));
   Print("rate time over score: %s\n", (level->rate_time_over_score ? "yes" : "no"));
 
+  if (options.debug)
+  {
+    int i, j;
+
+    for (i = 0; i < NUM_ENVELOPES; i++)
+    {
+      char *text = level->envelope[i].text;
+      int text_len = strlen(text);
+      boolean has_text = FALSE;
+
+      for (j = 0; j < text_len; j++)
+       if (text[j] != ' ' && text[j] != '\n')
+         has_text = TRUE;
+
+      if (has_text)
+      {
+       Print("\n");
+       Print("Envelope %d:\n'%s'\n", i + 1, text);
+      }
+    }
+  }
+
   PrintLine("-", 79);
 }
 
@@ -7755,6 +7886,7 @@ static void setTapeInfoToDefaults(void)
   tape.level_nr = level_nr;
   tape.counter = 0;
   tape.changed = FALSE;
+  tape.solved = FALSE;
 
   tape.recording = FALSE;
   tape.playing = FALSE;
@@ -7841,8 +7973,7 @@ static int LoadTape_HEAD(File *file, int chunk_size, struct TapeInfo *tape)
     setTapeActionFlags(tape, getFile8Bit(file));
 
     tape->property_bits = getFile8Bit(file);
-
-    ReadUnusedBytesFromFile(file, TAPE_CHUNK_HEAD_UNUSED);
+    tape->solved = getFile8Bit(file);
 
     engine_version = getFileVersion(file);
     if (engine_version > 0)
@@ -8240,6 +8371,20 @@ void LoadSolutionTape(int nr)
     CopyNativeTape_SP_to_RND(&level);
 }
 
+void LoadScoreTape(char *score_tape_basename, int nr)
+{
+  char *filename = getScoreTapeFilename(score_tape_basename, nr);
+
+  LoadTapeFromFilename(filename);
+}
+
+void LoadScoreCacheTape(char *score_tape_basename, int nr)
+{
+  char *filename = getScoreCacheTapeFilename(score_tape_basename, nr);
+
+  LoadTapeFromFilename(filename);
+}
+
 static boolean checkSaveTape_SCRN(struct TapeInfo *tape)
 {
   // chunk required for team mode tapes with non-default screen size
@@ -8273,9 +8418,7 @@ static void SaveTape_HEAD(FILE *file, struct TapeInfo *tape)
   putFile8Bit(file, getTapeActionValue(tape));
 
   putFile8Bit(file, tape->property_bits);
-
-  // unused bytes not at the end here for 4-byte alignment of engine_version
-  WriteUnusedBytesToFile(file, TAPE_CHUNK_HEAD_UNUSED);
+  putFile8Bit(file, tape->solved);
 
   putFileVersion(file, tape->engine_version);
 }
@@ -8457,6 +8600,10 @@ void DumpTape(struct TapeInfo *tape)
        tape->engine_version);
   Print("Level series identifier: '%s'\n", tape->level_identifier);
 
+  Print("Solution tape: %s\n",
+       tape->solved ? "yes" :
+       tape->game_version < VERSION_IDENT(4,3,2,3) ? "unknown" : "no");
+
   Print("Special tape properties: ");
   if (tape->property_bits == TAPE_PROPERTY_NONE)
     Print("[none]");
@@ -8565,6 +8712,13 @@ static void setScoreInfoToDefaultsExt(struct ScoreInfo *scores)
     strcpy(scores->entry[i].name, EMPTY_PLAYER_NAME);
     scores->entry[i].score = 0;
     scores->entry[i].time = 0;
+
+    scores->entry[i].id = -1;
+    strcpy(scores->entry[i].tape_date,    UNKNOWN_NAME);
+    strcpy(scores->entry[i].platform,     UNKNOWN_NAME);
+    strcpy(scores->entry[i].version,      UNKNOWN_NAME);
+    strcpy(scores->entry[i].country_name, UNKNOWN_NAME);
+    strcpy(scores->entry[i].country_code, "??");
   }
 
   scores->num_entries = 0;
@@ -8573,7 +8727,15 @@ static void setScoreInfoToDefaultsExt(struct ScoreInfo *scores)
 
   scores->updated = FALSE;
   scores->uploaded = FALSE;
+  scores->tape_downloaded = FALSE;
   scores->force_last_added = FALSE;
+
+  // The following values are intentionally not reset here:
+  // - last_level_nr
+  // - last_entry_nr
+  // - next_level_nr
+  // - continue_playing
+  // - continue_on_return
 }
 
 static void setScoreInfoToDefaults(void)
@@ -8720,6 +8882,18 @@ static int LoadScore_SCOR(File *file, int chunk_size, struct ScoreInfo *scores)
   return chunk_size;
 }
 
+static int LoadScore_SC4R(File *file, int chunk_size, struct ScoreInfo *scores)
+{
+  int i;
+
+  for (i = 0; i < scores->num_entries; i++)
+    scores->entry[i].score = getFile32BitBE(file);
+
+  chunk_size = scores->num_entries * 4;
+
+  return chunk_size;
+}
+
 static int LoadScore_TIME(File *file, int chunk_size, struct ScoreInfo *scores)
 {
   int i;
@@ -8821,6 +8995,7 @@ void LoadScore(int nr)
       { "INFO", -1,                    LoadScore_INFO },
       { "NAME", -1,                    LoadScore_NAME },
       { "SCOR", -1,                    LoadScore_SCOR },
+      { "SC4R", -1,                    LoadScore_SC4R },
       { "TIME", -1,                    LoadScore_TIME },
       { "TAPE", -1,                    LoadScore_TAPE },
 
@@ -8940,6 +9115,14 @@ static void SaveScore_SCOR(FILE *file, struct ScoreInfo *scores)
     putFile16BitBE(file, scores->entry[i].score);
 }
 
+static void SaveScore_SC4R(FILE *file, struct ScoreInfo *scores)
+{
+  int i;
+
+  for (i = 0; i < scores->num_entries; i++)
+    putFile32BitBE(file, scores->entry[i].score);
+}
+
 static void SaveScore_TIME(FILE *file, struct ScoreInfo *scores)
 {
   int i;
@@ -8967,8 +9150,11 @@ static void SaveScoreToFilename(char *filename)
   int info_chunk_size;
   int name_chunk_size;
   int scor_chunk_size;
+  int sc4r_chunk_size;
   int time_chunk_size;
   int tape_chunk_size;
+  boolean has_large_score_values;
+  int i;
 
   if (!(file = fopen(filename, MODE_WRITE)))
   {
@@ -8980,9 +9166,15 @@ static void SaveScoreToFilename(char *filename)
   info_chunk_size = 2 + (strlen(scores.level_identifier) + 1) + 2 + 2;
   name_chunk_size = scores.num_entries * MAX_PLAYER_NAME_LEN;
   scor_chunk_size = scores.num_entries * 2;
+  sc4r_chunk_size = scores.num_entries * 4;
   time_chunk_size = scores.num_entries * 4;
   tape_chunk_size = scores.num_entries * MAX_SCORE_TAPE_BASENAME_LEN;
 
+  has_large_score_values = FALSE;
+  for (i = 0; i < scores.num_entries; i++)
+    if (scores.entry[i].score > 0xffff)
+      has_large_score_values = TRUE;
+
   putFileChunkBE(file, "RND1", CHUNK_SIZE_UNDEFINED);
   putFileChunkBE(file, "SCOR", CHUNK_SIZE_NONE);
 
@@ -8995,8 +9187,16 @@ static void SaveScoreToFilename(char *filename)
   putFileChunkBE(file, "NAME", name_chunk_size);
   SaveScore_NAME(file, &scores);
 
-  putFileChunkBE(file, "SCOR", scor_chunk_size);
-  SaveScore_SCOR(file, &scores);
+  if (has_large_score_values)
+  {
+    putFileChunkBE(file, "SC4R", sc4r_chunk_size);
+    SaveScore_SC4R(file, &scores);
+  }
+  else
+  {
+    putFileChunkBE(file, "SCOR", scor_chunk_size);
+    SaveScore_SCOR(file, &scores);
+  }
 
   putFileChunkBE(file, "TIME", time_chunk_size);
   SaveScore_TIME(file, &scores);
@@ -9038,273 +9238,6 @@ void SaveScore(int nr)
   SaveScoreToFilename(filename);
 }
 
-void ExecuteAsThread(SDL_ThreadFunction function, char *name, void *data,
-                    char *error)
-{
-#if defined(PLATFORM_EMSCRIPTEN)
-  // threads currently not fully supported by Emscripten/SDL and some browsers
-  function(data);
-#else
-  SDL_Thread *thread = SDL_CreateThread(function, name, data);
-
-  if (thread != NULL)
-    SDL_DetachThread(thread);
-  else
-    Error("Cannot create thread to %s!", error);
-
-  // nasty kludge to lower probability of intermingled thread error messages
-  Delay(1);
-#endif
-}
-
-char *getPasswordJSON(char *password)
-{
-  static char password_json[MAX_FILENAME_LEN] = "";
-  static boolean initialized = FALSE;
-
-  if (!initialized)
-  {
-    if (password != NULL &&
-       !strEqual(password, "") &&
-       !strEqual(password, UNDEFINED_PASSWORD))
-      snprintf(password_json, MAX_FILENAME_LEN,
-              "  \"password\":             \"%s\",\n",
-              setup.api_server_password);
-
-    initialized = TRUE;
-  }
-
-  return password_json;
-}
-
-struct ApiGetScoreThreadData
-{
-  int level_nr;
-  char *score_cache_filename;
-};
-
-static void *CreateThreadData_ApiGetScore(int nr)
-{
-  struct ApiGetScoreThreadData *data =
-    checked_malloc(sizeof(struct ApiGetScoreThreadData));
-  char *score_cache_filename = getScoreCacheFilename(nr);
-
-  data->level_nr = nr;
-  data->score_cache_filename = getStringCopy(score_cache_filename);
-
-  return data;
-}
-
-static void FreeThreadData_ApiGetScore(void *data_raw)
-{
-  struct ApiGetScoreThreadData *data = data_raw;
-
-  checked_free(data->score_cache_filename);
-  checked_free(data);
-}
-
-static boolean SetRequest_ApiGetScore(struct HttpRequest *request,
-                                     void *data_raw)
-{
-  struct ApiGetScoreThreadData *data = data_raw;
-  int level_nr = data->level_nr;
-
-  request->hostname = setup.api_server_hostname;
-  request->port     = API_SERVER_PORT;
-  request->method   = API_SERVER_METHOD;
-  request->uri      = API_SERVER_URI_GET;
-
-  char *levelset_identifier = getEscapedJSON(leveldir_current->identifier);
-  char *levelset_name       = getEscapedJSON(leveldir_current->name);
-
-  snprintf(request->body, MAX_HTTP_BODY_SIZE,
-          "{\n"
-          "%s"
-          "  \"game_version\":         \"%s\",\n"
-          "  \"game_platform\":        \"%s\",\n"
-          "  \"levelset_identifier\":  \"%s\",\n"
-          "  \"levelset_name\":        \"%s\",\n"
-          "  \"level_nr\":             \"%d\"\n"
-          "}\n",
-          getPasswordJSON(setup.api_server_password),
-          getProgramRealVersionString(),
-          getProgramPlatformString(),
-          levelset_identifier,
-          levelset_name,
-          level_nr);
-
-  checked_free(levelset_identifier);
-  checked_free(levelset_name);
-
-  ConvertHttpRequestBodyToServerEncoding(request);
-
-  return TRUE;
-}
-
-static void HandleResponse_ApiGetScore(struct HttpResponse *response,
-                                      void *data_raw)
-{
-  struct ApiGetScoreThreadData *data = data_raw;
-
-  if (response->body_size == 0)
-  {
-    // no scores available for this level
-
-    return;
-  }
-
-  ConvertHttpResponseBodyToClientEncoding(response);
-
-  char *filename = data->score_cache_filename;
-  FILE *file;
-  int i;
-
-  // used instead of "leveldir_current->subdir" (for network games)
-  InitScoreCacheDirectory(levelset.identifier);
-
-  if (!(file = fopen(filename, MODE_WRITE)))
-  {
-    Warn("cannot save score cache file '%s'", filename);
-
-    return;
-  }
-
-  for (i = 0; i < response->body_size; i++)
-    fputc(response->body[i], file);
-
-  fclose(file);
-
-  SetFilePermissions(filename, PERMS_PRIVATE);
-
-  server_scores.updated = TRUE;
-}
-
-#if defined(PLATFORM_EMSCRIPTEN)
-static void Emscripten_ApiGetScore_Loaded(unsigned handle, void *data_raw,
-                                         void *buffer, unsigned int size)
-{
-  struct HttpResponse *response = GetHttpResponseFromBuffer(buffer, size);
-
-  if (response != NULL)
-  {
-    HandleResponse_ApiGetScore(response, data_raw);
-
-    checked_free(response);
-  }
-  else
-  {
-    Error("server response too large to handle (%d bytes)", size);
-  }
-
-  FreeThreadData_ApiGetScore(data_raw);
-}
-
-static void Emscripten_ApiGetScore_Failed(unsigned handle, void *data_raw,
-                                         int code, const char *status)
-{
-  Error("server failed to handle request: %d %s", code, status);
-
-  FreeThreadData_ApiGetScore(data_raw);
-}
-
-static void Emscripten_ApiGetScore_Progress(unsigned handle, void *data_raw,
-                                           int bytes, int size)
-{
-  // nothing to do here
-}
-
-static void Emscripten_ApiGetScore_HttpRequest(struct HttpRequest *request,
-                                              void *data_raw)
-{
-  if (!SetRequest_ApiGetScore(request, data_raw))
-  {
-    FreeThreadData_ApiGetScore(data_raw);
-
-    return;
-  }
-
-  emscripten_async_wget2_data(request->uri,
-                             request->method,
-                             request->body,
-                             data_raw,
-                             TRUE,
-                             Emscripten_ApiGetScore_Loaded,
-                             Emscripten_ApiGetScore_Failed,
-                             Emscripten_ApiGetScore_Progress);
-}
-
-#else
-
-static void ApiGetScore_HttpRequestExt(struct HttpRequest *request,
-                                      struct HttpResponse *response,
-                                      void *data_raw)
-{
-  if (!SetRequest_ApiGetScore(request, data_raw))
-    return;
-
-  if (!DoHttpRequest(request, response))
-  {
-    Error("HTTP request failed: %s", GetHttpError());
-
-    return;
-  }
-
-  if (!HTTP_SUCCESS(response->status_code))
-  {
-    // do not show error message if no scores found for this level set
-    if (response->status_code == 404)
-      return;
-
-    Error("server failed to handle request: %d %s",
-         response->status_code,
-         response->status_text);
-
-    return;
-  }
-
-  HandleResponse_ApiGetScore(response, data_raw);
-}
-
-static void ApiGetScore_HttpRequest(struct HttpRequest *request,
-                                   struct HttpResponse *response,
-                                   void *data_raw)
-{
-  ApiGetScore_HttpRequestExt(request, response, data_raw);
-
-  FreeThreadData_ApiGetScore(data_raw);
-}
-#endif
-
-static int ApiGetScoreThread(void *data_raw)
-{
-  struct HttpRequest *request = checked_calloc(sizeof(struct HttpRequest));
-  struct HttpResponse *response = checked_calloc(sizeof(struct HttpResponse));
-
-  program.api_thread_count++;
-
-#if defined(PLATFORM_EMSCRIPTEN)
-  Emscripten_ApiGetScore_HttpRequest(request, data_raw);
-#else
-  ApiGetScore_HttpRequest(request, response, data_raw);
-#endif
-
-  program.api_thread_count--;
-
-  checked_free(request);
-  checked_free(response);
-
-  return 0;
-}
-
-static void ApiGetScoreAsThread(int nr)
-{
-  struct ApiGetScoreThreadData *data = CreateThreadData_ApiGetScore(nr);
-
-  ExecuteAsThread(ApiGetScoreThread,
-                 "ApiGetScore", data,
-                 "download scores from server");
-}
-
 static void LoadServerScoreFromCache(int nr)
 {
   struct ScoreEntry score_entry;
@@ -9320,6 +9253,12 @@ static void LoadServerScoreFromCache(int nr)
     { &score_entry.time,               FALSE,  0                       },
     { score_entry.name,                        TRUE,   MAX_PLAYER_NAME_LEN     },
     { score_entry.tape_basename,       TRUE,   MAX_FILENAME_LEN        },
+    { score_entry.tape_date,           TRUE,   MAX_ISO_DATE_LEN        },
+    { &score_entry.id,                 FALSE,  0                       },
+    { score_entry.platform,            TRUE,   MAX_PLATFORM_TEXT_LEN   },
+    { score_entry.version,             TRUE,   MAX_VERSION_TEXT_LEN    },
+    { score_entry.country_code,                TRUE,   MAX_COUNTRY_CODE_LEN    },
+    { score_entry.country_name,                TRUE,   MAX_COUNTRY_NAME_LEN    },
 
     { NULL,                            FALSE,  0                       }
   };
@@ -9391,61 +9330,7 @@ void LoadServerScore(int nr, boolean download_score)
   }
 }
 
-static char *get_file_base64(char *filename)
-{
-  struct stat file_status;
-
-  if (stat(filename, &file_status) != 0)
-  {
-    Error("cannot stat file '%s'", filename);
-
-    return NULL;
-  }
-
-  int buffer_size = file_status.st_size;
-  byte *buffer = checked_malloc(buffer_size);
-  FILE *file;
-  int i;
-
-  if (!(file = fopen(filename, MODE_READ)))
-  {
-    Error("cannot open file '%s'", filename);
-
-    checked_free(buffer);
-
-    return NULL;
-  }
-
-  for (i = 0; i < buffer_size; i++)
-  {
-    int c = fgetc(file);
-
-    if (c == EOF)
-    {
-      Error("cannot read from input file '%s'", filename);
-
-      fclose(file);
-      checked_free(buffer);
-
-      return NULL;
-    }
-
-    buffer[i] = (byte)c;
-  }
-
-  fclose(file);
-
-  int buffer_encoded_size = base64_encoded_size(buffer_size);
-  char *buffer_encoded = checked_malloc(buffer_encoded_size);
-
-  base64_encode(buffer_encoded, buffer, buffer_size);
-
-  checked_free(buffer);
-
-  return buffer_encoded;
-}
-
-static void PrepareScoreTapesForUpload(char *leveldir_subdir)
+void PrepareScoreTapesForUpload(char *leveldir_subdir)
 {
   MarkTapeDirectoryUploadsAsIncomplete(leveldir_subdir);
 
@@ -9459,289 +9344,6 @@ static void PrepareScoreTapesForUpload(char *leveldir_subdir)
   SaveSetup_ServerSetup();
 }
 
-struct ApiAddScoreThreadData
-{
-  int level_nr;
-  boolean tape_saved;
-  char *leveldir_subdir;
-  char *score_tape_filename;
-  struct ScoreEntry score_entry;
-};
-
-static void *CreateThreadData_ApiAddScore(int nr, boolean tape_saved,
-                                         char *score_tape_filename)
-{
-  struct ApiAddScoreThreadData *data =
-    checked_malloc(sizeof(struct ApiAddScoreThreadData));
-  struct ScoreEntry *score_entry = &scores.entry[scores.last_added];
-
-  if (score_tape_filename == NULL)
-    score_tape_filename = getScoreTapeFilename(score_entry->tape_basename, nr);
-
-  data->level_nr = nr;
-  data->tape_saved = tape_saved;
-  data->leveldir_subdir = getStringCopy(leveldir_current->subdir);
-  data->score_tape_filename = getStringCopy(score_tape_filename);
-  data->score_entry = *score_entry;
-
-  return data;
-}
-
-static void FreeThreadData_ApiAddScore(void *data_raw)
-{
-  struct ApiAddScoreThreadData *data = data_raw;
-
-  checked_free(data->leveldir_subdir);
-  checked_free(data->score_tape_filename);
-  checked_free(data);
-}
-
-static boolean SetRequest_ApiAddScore(struct HttpRequest *request,
-                                     void *data_raw)
-{
-  struct ApiAddScoreThreadData *data = data_raw;
-  struct ScoreEntry *score_entry = &data->score_entry;
-  char *score_tape_filename = data->score_tape_filename;
-  boolean tape_saved = data->tape_saved;
-  int level_nr = data->level_nr;
-
-  request->hostname = setup.api_server_hostname;
-  request->port     = API_SERVER_PORT;
-  request->method   = API_SERVER_METHOD;
-  request->uri      = API_SERVER_URI_ADD;
-
-  char *tape_base64 = get_file_base64(score_tape_filename);
-
-  if (tape_base64 == NULL)
-  {
-    Error("loading and base64 encoding score tape file failed");
-
-    return FALSE;
-  }
-
-  char *player_name_raw = score_entry->name;
-  char *player_uuid_raw = setup.player_uuid;
-
-  if (options.player_name != NULL && global.autoplay_leveldir != NULL)
-  {
-    player_name_raw = options.player_name;
-    player_uuid_raw = "";
-  }
-
-  char *levelset_identifier = getEscapedJSON(leveldir_current->identifier);
-  char *levelset_name       = getEscapedJSON(leveldir_current->name);
-  char *levelset_author     = getEscapedJSON(leveldir_current->author);
-  char *level_name          = getEscapedJSON(level.name);
-  char *level_author        = getEscapedJSON(level.author);
-  char *player_name         = getEscapedJSON(player_name_raw);
-  char *player_uuid         = getEscapedJSON(player_uuid_raw);
-
-  snprintf(request->body, MAX_HTTP_BODY_SIZE,
-          "{\n"
-          "%s"
-          "  \"game_version\":         \"%s\",\n"
-          "  \"game_platform\":        \"%s\",\n"
-          "  \"batch_time\":           \"%d\",\n"
-          "  \"levelset_identifier\":  \"%s\",\n"
-          "  \"levelset_name\":        \"%s\",\n"
-          "  \"levelset_author\":      \"%s\",\n"
-          "  \"levelset_num_levels\":  \"%d\",\n"
-          "  \"levelset_first_level\": \"%d\",\n"
-          "  \"level_nr\":             \"%d\",\n"
-          "  \"level_name\":           \"%s\",\n"
-          "  \"level_author\":         \"%s\",\n"
-          "  \"use_step_counter\":     \"%d\",\n"
-          "  \"rate_time_over_score\": \"%d\",\n"
-          "  \"player_name\":          \"%s\",\n"
-          "  \"player_uuid\":          \"%s\",\n"
-          "  \"score\":                \"%d\",\n"
-          "  \"time\":                 \"%d\",\n"
-          "  \"tape_basename\":        \"%s\",\n"
-          "  \"tape_saved\":           \"%d\",\n"
-          "  \"tape\":                 \"%s\"\n"
-          "}\n",
-          getPasswordJSON(setup.api_server_password),
-          getProgramRealVersionString(),
-          getProgramPlatformString(),
-          (int)global.autoplay_time,
-          levelset_identifier,
-          levelset_name,
-          levelset_author,
-          leveldir_current->levels,
-          leveldir_current->first_level,
-          level_nr,
-          level_name,
-          level_author,
-          level.use_step_counter,
-          level.rate_time_over_score,
-          player_name,
-          player_uuid,
-          score_entry->score,
-          score_entry->time,
-          score_entry->tape_basename,
-          tape_saved,
-          tape_base64);
-
-  checked_free(tape_base64);
-
-  checked_free(levelset_identifier);
-  checked_free(levelset_name);
-  checked_free(levelset_author);
-  checked_free(level_name);
-  checked_free(level_author);
-  checked_free(player_name);
-  checked_free(player_uuid);
-
-  ConvertHttpRequestBodyToServerEncoding(request);
-
-  return TRUE;
-}
-
-static void HandleResponse_ApiAddScore(struct HttpResponse *response,
-                                      void *data_raw)
-{
-  server_scores.uploaded = TRUE;
-}
-
-static void HandleFailure_ApiAddScore(void *data_raw)
-{
-  struct ApiAddScoreThreadData *data = data_raw;
-
-  PrepareScoreTapesForUpload(data->leveldir_subdir);
-}
-
-#if defined(PLATFORM_EMSCRIPTEN)
-static void Emscripten_ApiAddScore_Loaded(unsigned handle, void *data_raw,
-                                         void *buffer, unsigned int size)
-{
-  struct HttpResponse *response = GetHttpResponseFromBuffer(buffer, size);
-
-  if (response != NULL)
-  {
-    HandleResponse_ApiAddScore(response, data_raw);
-
-    checked_free(response);
-  }
-  else
-  {
-    Error("server response too large to handle (%d bytes)", size);
-
-    HandleFailure_ApiAddScore(data_raw);
-  }
-
-  FreeThreadData_ApiAddScore(data_raw);
-}
-
-static void Emscripten_ApiAddScore_Failed(unsigned handle, void *data_raw,
-                                         int code, const char *status)
-{
-  Error("server failed to handle request: %d %s", code, status);
-
-  HandleFailure_ApiAddScore(data_raw);
-
-  FreeThreadData_ApiAddScore(data_raw);
-}
-
-static void Emscripten_ApiAddScore_Progress(unsigned handle, void *data_raw,
-                                           int bytes, int size)
-{
-  // nothing to do here
-}
-
-static void Emscripten_ApiAddScore_HttpRequest(struct HttpRequest *request,
-                                              void *data_raw)
-{
-  if (!SetRequest_ApiAddScore(request, data_raw))
-  {
-    FreeThreadData_ApiAddScore(data_raw);
-
-    return;
-  }
-
-  emscripten_async_wget2_data(request->uri,
-                             request->method,
-                             request->body,
-                             data_raw,
-                             TRUE,
-                             Emscripten_ApiAddScore_Loaded,
-                             Emscripten_ApiAddScore_Failed,
-                             Emscripten_ApiAddScore_Progress);
-}
-
-#else
-
-static void ApiAddScore_HttpRequestExt(struct HttpRequest *request,
-                                      struct HttpResponse *response,
-                                      void *data_raw)
-{
-  if (!SetRequest_ApiAddScore(request, data_raw))
-    return;
-
-  if (!DoHttpRequest(request, response))
-  {
-    Error("HTTP request failed: %s", GetHttpError());
-
-    HandleFailure_ApiAddScore(data_raw);
-
-    return;
-  }
-
-  if (!HTTP_SUCCESS(response->status_code))
-  {
-    Error("server failed to handle request: %d %s",
-         response->status_code,
-         response->status_text);
-
-    HandleFailure_ApiAddScore(data_raw);
-
-    return;
-  }
-
-  HandleResponse_ApiAddScore(response, data_raw);
-}
-
-static void ApiAddScore_HttpRequest(struct HttpRequest *request,
-                                   struct HttpResponse *response,
-                                   void *data_raw)
-{
-  ApiAddScore_HttpRequestExt(request, response, data_raw);
-
-  FreeThreadData_ApiAddScore(data_raw);
-}
-#endif
-
-static int ApiAddScoreThread(void *data_raw)
-{
-  struct HttpRequest *request = checked_calloc(sizeof(struct HttpRequest));
-  struct HttpResponse *response = checked_calloc(sizeof(struct HttpResponse));
-
-  program.api_thread_count++;
-
-#if defined(PLATFORM_EMSCRIPTEN)
-  Emscripten_ApiAddScore_HttpRequest(request, data_raw);
-#else
-  ApiAddScore_HttpRequest(request, response, data_raw);
-#endif
-
-  program.api_thread_count--;
-
-  checked_free(request);
-  checked_free(response);
-
-  return 0;
-}
-
-static void ApiAddScoreAsThread(int nr, boolean tape_saved,
-                               char *score_tape_filename)
-{
-  struct ApiAddScoreThreadData *data =
-    CreateThreadData_ApiAddScore(nr, tape_saved, score_tape_filename);
-
-  ExecuteAsThread(ApiAddScoreThread,
-                 "ApiAddScore", data,
-                 "upload score to server");
-}
-
 void SaveServerScore(int nr, boolean tape_saved)
 {
   if (!runtime.use_api_server)
@@ -9766,6 +9368,7 @@ void SaveServerScoreFromFile(int nr, boolean tape_saved,
 void LoadLocalAndServerScore(int nr, boolean download_score)
 {
   int last_added_local = scores.last_added_local;
+  boolean force_last_added = scores.force_last_added;
 
   // needed if only showing server scores
   setScoreInfoToDefaults();
@@ -9785,6 +9388,9 @@ void LoadLocalAndServerScore(int nr, boolean download_score)
     // merge local scores with scores from server
     MergeServerScore();
   }
+
+  if (force_last_added)
+    scores.force_last_added = force_last_added;
 }
 
 
@@ -9853,6 +9459,10 @@ static struct TokenInfo global_setup_tokens[] =
     TYPE_SWITCH,
     &setup.autorecord,                         "automatic_tape_recording"
   },
+  {
+    TYPE_SWITCH,
+    &setup.auto_pause_on_start,                        "auto_pause_on_start"
+  },
   {
     TYPE_SWITCH,
     &setup.show_titlescreen,                   "show_titlescreen"
@@ -10069,6 +9679,10 @@ static struct TokenInfo global_setup_tokens[] =
     TYPE_INTEGER,
     &setup.touch.grid_ysize[1],                        "touch.virtual_buttons.1.ysize"
   },
+  {
+    TYPE_SWITCH,
+    &setup.touch.overlay_buttons,              "touch.overlay_buttons"
+  },
 };
 
 static struct TokenInfo auto_setup_tokens[] =
@@ -10213,6 +9827,10 @@ static struct TokenInfo editor_cascade_setup_tokens[] =
     TYPE_SWITCH,
     &setup.editor_cascade.el_ge,               "editor.cascade.el_ge"
   },
+  {
+    TYPE_SWITCH,
+    &setup.editor_cascade.el_es,               "editor.cascade.el_es"
+  },
   {
     TYPE_SWITCH,
     &setup.editor_cascade.el_ref,              "editor.cascade.el_ref"
@@ -10237,6 +9855,14 @@ static struct TokenInfo shortcut_setup_tokens[] =
     TYPE_KEY_X11,
     &setup.shortcut.load_game,                 "shortcut.load_game"
   },
+  {
+    TYPE_KEY_X11,
+    &setup.shortcut.restart_game,              "shortcut.restart_game"
+  },
+  {
+    TYPE_KEY_X11,
+    &setup.shortcut.pause_before_end,          "shortcut.pause_before_end"
+  },
   {
     TYPE_KEY_X11,
     &setup.shortcut.toggle_pause,              "shortcut.toggle_pause"
@@ -10526,6 +10152,38 @@ static struct TokenInfo internal_setup_tokens[] =
     TYPE_BOOLEAN,
     &setup.internal.menu_save_and_exit,                "menu_save_and_exit"
   },
+  {
+    TYPE_BOOLEAN,
+    &setup.internal.info_title,                        "info_title"
+  },
+  {
+    TYPE_BOOLEAN,
+    &setup.internal.info_elements,             "info_elements"
+  },
+  {
+    TYPE_BOOLEAN,
+    &setup.internal.info_music,                        "info_music"
+  },
+  {
+    TYPE_BOOLEAN,
+    &setup.internal.info_credits,              "info_credits"
+  },
+  {
+    TYPE_BOOLEAN,
+    &setup.internal.info_program,              "info_program"
+  },
+  {
+    TYPE_BOOLEAN,
+    &setup.internal.info_version,              "info_version"
+  },
+  {
+    TYPE_BOOLEAN,
+    &setup.internal.info_levelset,             "info_levelset"
+  },
+  {
+    TYPE_BOOLEAN,
+    &setup.internal.info_exit,                 "info_exit"
+  },
 };
 
 static struct TokenInfo debug_setup_tokens[] =
@@ -10659,6 +10317,7 @@ static void setSetupInfoToDefaults(struct SetupInfo *si)
   si->engine_snapshot_memory = SNAPSHOT_MEMORY_DEFAULT;
   si->fade_screens = TRUE;
   si->autorecord = TRUE;
+  si->auto_pause_on_start = FALSE;
   si->show_titlescreen = TRUE;
   si->quick_doors = FALSE;
   si->team_mode = FALSE;
@@ -10764,6 +10423,8 @@ static void setSetupInfoToDefaults(struct SetupInfo *si)
 
   si->touch.grid_initialized           = video.initialized;
 
+  si->touch.overlay_buttons            = FALSE;
+
   si->editor.el_boulderdash            = TRUE;
   si->editor.el_emerald_mine           = TRUE;
   si->editor.el_emerald_mine_club      = TRUE;
@@ -10795,6 +10456,8 @@ static void setSetupInfoToDefaults(struct SetupInfo *si)
 
   si->shortcut.save_game       = DEFAULT_KEY_SAVE_GAME;
   si->shortcut.load_game       = DEFAULT_KEY_LOAD_GAME;
+  si->shortcut.restart_game    = DEFAULT_KEY_RESTART_GAME;
+  si->shortcut.pause_before_end        = DEFAULT_KEY_PAUSE_BEFORE_END;
   si->shortcut.toggle_pause    = DEFAULT_KEY_TOGGLE_PAUSE;
 
   si->shortcut.focus_player[0] = DEFAULT_KEY_FOCUS_PLAYER_1;
@@ -10904,6 +10567,7 @@ static void setSetupInfoToDefaults(struct SetupInfo *si)
 
 #if defined(PLATFORM_ANDROID)
   si->fullscreen = TRUE;
+  si->touch.overlay_buttons = TRUE;
 #endif
 
   setHideSetupEntry(&setup.debug.xsn_mode);
@@ -10947,6 +10611,7 @@ static void setSetupInfoToDefaults_EditorCascade(struct SetupInfo *si)
   si->editor_cascade.el_steel_chars    = FALSE;
   si->editor_cascade.el_ce             = FALSE;
   si->editor_cascade.el_ge             = FALSE;
+  si->editor_cascade.el_es             = FALSE;
   si->editor_cascade.el_ref            = FALSE;
   si->editor_cascade.el_user           = FALSE;
   si->editor_cascade.el_dynamic                = FALSE;
@@ -11226,6 +10891,12 @@ void LoadSetup_Default(void)
   // try to load setup values from default setup file
   filename = getDefaultSetupFilename();
 
+  if (fileExists(filename))
+    LoadSetupFromFilename(filename);
+
+  // try to load setup values from platform setup file
+  filename = getPlatformSetupFilename();
+
   if (fileExists(filename))
     LoadSetupFromFilename(filename);
 
@@ -11926,6 +11597,18 @@ static int get_anim_action_parameter_value(char *token)
       result = -(int)key;
   }
 
+  if (result == -1)
+  {
+    if (isURL(token))
+    {
+      result = get_hash_from_key(token);       // unsigned int => int
+      result = ABS(result);                    // may be negative now
+      result += (result < MAX_IMAGE_FILES ? MAX_IMAGE_FILES : 0);
+
+      setHashEntry(anim_url_hash, int2str(result, 0), token);
+    }
+  }
+
   if (result == -1)
     result = ANIM_EVENT_ACTION_NONE;
 
@@ -11978,6 +11661,7 @@ int get_parameter_value(char *value_raw, char *suffix, int type)
              string_has_parameter(value, "pingpong")   ? ANIM_PINGPONG :
              string_has_parameter(value, "pingpong2")  ? ANIM_PINGPONG2 :
              string_has_parameter(value, "random")     ? ANIM_RANDOM :
+             string_has_parameter(value, "random_static") ? ANIM_RANDOM_STATIC :
              string_has_parameter(value, "ce_value")   ? ANIM_CE_VALUE :
              string_has_parameter(value, "ce_score")   ? ANIM_CE_SCORE :
              string_has_parameter(value, "ce_delay")   ? ANIM_CE_DELAY :
@@ -12093,14 +11777,18 @@ static int get_token_parameter_value(char *token, char *value_raw)
   return get_parameter_value(value_raw, suffix, TYPE_INTEGER);
 }
 
-void InitMenuDesignSettings_Static(void)
+void InitMenuDesignSettings_FromHash(SetupFileHash *setup_file_hash,
+                                    boolean ignore_defaults)
 {
   int i;
 
-  // always start with reliable default values from static default config
   for (i = 0; image_config_vars[i].token != NULL; i++)
   {
-    char *value = getHashEntry(image_config_hash, image_config_vars[i].token);
+    char *value = getHashEntry(setup_file_hash, image_config_vars[i].token);
+
+    // (ignore definitions set to "[DEFAULT]" which are already initialized)
+    if (ignore_defaults && strEqual(value, ARG_DEFAULT))
+      continue;
 
     if (value != NULL)
       *image_config_vars[i].value =
@@ -12108,6 +11796,12 @@ void InitMenuDesignSettings_Static(void)
   }
 }
 
+void InitMenuDesignSettings_Static(void)
+{
+  // always start with reliable default values from static default config
+  InitMenuDesignSettings_FromHash(image_config_hash, FALSE);
+}
+
 static void InitMenuDesignSettings_SpecialPreProcessing(void)
 {
   int i;
@@ -12906,15 +12600,7 @@ static void LoadMenuDesignSettingsFromFilename(char *filename)
       *menu_config_players[i].value = TRUE;
 
   // read (and overwrite with) values that may be specified in config file
-  for (i = 0; image_config_vars[i].token != NULL; i++)
-  {
-    char *value = getHashEntry(setup_file_hash, image_config_vars[i].token);
-
-    // (ignore definitions set to "[DEFAULT]" which are already initialized)
-    if (value != NULL && !strEqual(value, ARG_DEFAULT))
-      *image_config_vars[i].value =
-       get_token_parameter_value(image_config_vars[i].token, value);
-  }
+  InitMenuDesignSettings_FromHash(setup_file_hash, TRUE);
 
   freeSetupFileHash(setup_file_hash);
 }
@@ -13271,6 +12957,18 @@ void LoadMusicInfo(void)
        new = &(*new)->next;
     }
   }
+
+  // add pointers to previous list nodes
+
+  struct MusicFileInfo *node = music_file_info;
+
+  while (node != NULL)
+  {
+    if (node->next)
+      node->next->prev = node;
+
+    node = node->next;
+  }
 }
 
 static void add_helpanim_entry(int element, int action, int direction,
@@ -13773,8 +13471,25 @@ void CreateCollectElementImages(void)
   int dst_width  = anim_width * 2;
   int dst_height = anim_height * num_collect_images / 2;
   Bitmap *dst_bitmap = CreateBitmap(dst_width, dst_height, DEFAULT_DEPTH);
-  char *basename = "RocksCollect.bmp";
-  char *filename = getPath2(global.create_collect_images_dir, basename);
+  char *basename_bmp = "RocksCollect.bmp";
+  char *basename_png = "RocksCollect.png";
+  char *filename_bmp = getPath2(global.create_collect_images_dir, basename_bmp);
+  char *filename_png = getPath2(global.create_collect_images_dir, basename_png);
+  int len_filename_bmp = strlen(filename_bmp);
+  int len_filename_png = strlen(filename_png);
+  int max_command_len = MAX_FILENAME_LEN + len_filename_bmp + len_filename_png;
+  char cmd_convert[max_command_len];
+
+  snprintf(cmd_convert, max_command_len, "convert \"%s\" \"%s\"",
+          filename_bmp,
+          filename_png);
+
+  // force using RGBA surface for destination bitmap
+  SDL_SetColorKey(dst_bitmap->surface, SET_TRANSPARENT_PIXEL,
+                 SDL_MapRGB(dst_bitmap->surface->format, 0x00, 0x00, 0x00));
+
+  dst_bitmap->surface =
+    SDL_ConvertSurfaceFormat(dst_bitmap->surface, SDL_PIXELFORMAT_ARGB8888, 0);
 
   for (i = 0; i < MAX_NUM_ELEMENTS; i++)
   {
@@ -13796,6 +13511,13 @@ void CreateCollectElementImages(void)
     BlitBitmap(src_bitmap, tmp_bitmap, src_x, src_y,
               tile_size, tile_size, 0, 0);
 
+    // force using RGBA surface for temporary bitmap (using transparent black)
+    SDL_SetColorKey(tmp_bitmap->surface, SET_TRANSPARENT_PIXEL,
+                   SDL_MapRGB(tmp_bitmap->surface->format, 0x00, 0x00, 0x00));
+
+    tmp_bitmap->surface =
+      SDL_ConvertSurfaceFormat(tmp_bitmap->surface, SDL_PIXELFORMAT_ARGB8888, 0);
+
     tmp_bitmap->surface_masked = tmp_bitmap->surface;
 
     for (j = 0; j < anim_frames; j++)
@@ -13816,9 +13538,9 @@ void CreateCollectElementImages(void)
        frame_bitmap = half_bitmap;
       }
 
-      BlitBitmap(frame_bitmap, dst_bitmap, 0, 0,
-                frame_size_final, frame_size_final,
-                dst_x + j * tile_size + offset, dst_y + offset);
+      BlitBitmapMasked(frame_bitmap, dst_bitmap, 0, 0,
+                      frame_size_final, frame_size_final,
+                      dst_x + j * tile_size + offset, dst_y + offset);
 
       FreeBitmap(frame_bitmap);
     }
@@ -13830,11 +13552,18 @@ void CreateCollectElementImages(void)
     pos_collect_images++;
   }
 
-  if (SDL_SaveBMP(dst_bitmap->surface, filename) != 0)
-    Fail("cannot save element collecting image file '%s'", filename);
+  if (SDL_SaveBMP(dst_bitmap->surface, filename_bmp) != 0)
+    Fail("cannot save element collecting image file '%s'", filename_bmp);
 
   FreeBitmap(dst_bitmap);
 
+  Info("Converting image file from BMP to PNG ...");
+
+  if (system(cmd_convert) != 0)
+    Fail("converting image file failed");
+
+  unlink(filename_bmp);
+
   Info("Done.");
 
   CloseAllAndExit(0);