added storing next level to play from hall of fame
[rocksndiamonds.git] / src / files.c
index 7bbcac77d755ddbc4e2e9d8446bbb020d5d03ee3..4a980dffc748708fd4485a8f9b72d4e89252ab4e 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
@@ -6565,6 +6566,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)
@@ -8325,6 +8333,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
@@ -8650,6 +8672,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;
@@ -8658,7 +8687,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)
@@ -9161,273 +9198,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;
@@ -9443,6 +9213,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                       }
   };
@@ -9514,61 +9290,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);
 
@@ -9582,289 +9304,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)
@@ -9889,6 +9328,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();
@@ -9908,6 +9348,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;
 }
 
 
@@ -9976,6 +9419,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"
@@ -10661,6 +10108,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[] =
@@ -10794,6 +10273,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;
@@ -11364,6 +10844,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);
 
@@ -12064,6 +11550,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;
 
@@ -12232,14 +11730,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 =
@@ -12247,6 +11749,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;
@@ -13045,15 +12553,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);
 }
@@ -13410,6 +12910,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,