changed using improved pseudo-random number generator for UUID generation
[rocksndiamonds.git] / src / libgame / misc.c
index 193b15e2a90a95b31da0b0ce531069fc7ca5e00f..2692e69c6b3274b6ad957187b9d30ac95fdd69b9 100644 (file)
@@ -21,7 +21,7 @@
 
 #include "platform.h"
 
-#if !defined(PLATFORM_WIN32)
+#if defined(PLATFORM_UNIX)
 #include <pwd.h>
 #include <sys/param.h>
 #endif
@@ -252,7 +252,7 @@ void PrintLineWithPrefix(char *prefix, char *line_chars, int line_length)
 
 
 // ----------------------------------------------------------------------------
-// generic logging functions
+// generic logging and error handling functions
 // ----------------------------------------------------------------------------
 
 enum log_levels
@@ -288,13 +288,14 @@ static void printf_log_prefix(int log_level, char *mode)
     printf_log_nonewline("[%s] ", log_token);
 }
 
-static void Log(int log_level, char *mode, char *format, va_list ap)
+static void vLog(int log_level, char *mode, char *format, va_list ap)
 {
   if (log_level < 0 || log_level > LOG_FATAL)
     return;
 
   if (log_level == LOG_DEBUG)
   {
+    // only show debug messages when in debug mode
     if (!options.debug)
       return;
 
@@ -303,6 +304,12 @@ static void Log(int log_level, char *mode, char *format, va_list ap)
        strstr(mode, options.debug_mode) == NULL)
       return;
   }
+  else if (log_level == LOG_WARN)
+  {
+    // only show warning messages when in verbose mode
+    if (!options.verbose)
+      return;
+  }
 
 #if defined(PLATFORM_ANDROID)
   android_log_prio = (log_level == LOG_DEBUG ? ANDROID_LOG_DEBUG :
@@ -347,12 +354,53 @@ static void Log(int log_level, char *mode, char *format, va_list ap)
   vprintf_log(format, ap);
 }
 
+static void Log(int log_level, char *mode, char *format, ...)
+{
+  va_list ap;
+
+  va_start(ap, format);
+  vLog(log_level, mode, format, ap);
+  va_end(ap);
+}
+
+void DebugContinued(char *mode, char *format, ...)
+{
+  static char message[MAX_LINE_LEN] = { 0 };
+
+  // initialize message (optional)
+  if (strEqual(format, ""))
+  {
+    message[0] = '\0';
+
+    return;
+  }
+
+  char *message_ptr = message + strlen(message);
+  int size_left = MAX_LINE_LEN - strlen(message);
+  va_list ap;
+
+  // append message
+  va_start(ap, format);
+  vsnprintf(message_ptr, size_left, format, ap);
+  va_end(ap);
+
+  // finalize message
+  if (strSuffix(format, "\n"))
+  {
+    message[strlen(message) - 1] = '\0';
+
+    Debug(mode, message);
+
+    message[0] = '\0';
+  }
+}
+
 void Debug(char *mode, char *format, ...)
 {
   va_list ap;
 
   va_start(ap, format);
-  Log(LOG_DEBUG, mode, format, ap);
+  vLog(LOG_DEBUG, mode, format, ap);
   va_end(ap);
 }
 
@@ -361,7 +409,7 @@ void Info(char *format, ...)
   va_list ap;
 
   va_start(ap, format);
-  Log(LOG_INFO, NULL, format, ap);
+  vLog(LOG_INFO, NULL, format, ap);
   va_end(ap);
 }
 
@@ -370,10 +418,59 @@ void Warn(char *format, ...)
   va_list ap;
 
   va_start(ap, format);
-  Log(LOG_WARN, NULL, format, ap);
+  vLog(LOG_WARN, NULL, format, ap);
   va_end(ap);
 }
 
+void Error(char *format, ...)
+{
+  va_list ap;
+
+  va_start(ap, format);
+  vLog(LOG_ERROR, NULL, format, ap);
+  va_end(ap);
+}
+
+void Fail(char *format, ...)
+{
+  va_list ap;
+
+  va_start(ap, format);
+  vLog(LOG_FATAL, NULL, format, ap);
+  va_end(ap);
+
+  if (!network.is_server_thread)
+  {
+    va_start(ap, format);
+    program.exit_message_function(format, ap);
+    va_end(ap);
+  }
+
+  Log(LOG_FATAL, NULL, "aborting");
+
+  // network server thread: normal exit
+  if (network.is_server_thread)
+    exit(1);
+
+  // main process: clean up stuff
+  program.exit_function(1);
+}
+
+void FailWithHelp(char *format, ...)
+{
+  va_list ap;
+
+  va_start(ap, format);
+  vLog(LOG_FATAL, NULL, format, ap);
+  va_end(ap);
+
+  Log(LOG_FATAL, NULL, "try option '--help' for more information");
+  Log(LOG_FATAL, NULL, "aborting");
+
+  // main process: clean up stuff
+  program.exit_function(1);
+}
+
 
 // ----------------------------------------------------------------------------
 // string functions
@@ -452,6 +549,50 @@ boolean getTokenValueFromString(char *string, char **token, char **value)
 }
 
 
+// ----------------------------------------------------------------------------
+// UUID functions
+// ----------------------------------------------------------------------------
+
+#define UUID_BYTES             16
+#define UUID_CHARS             (UUID_BYTES * 2)
+#define UUID_LENGTH            (UUID_CHARS + 4)
+
+static unsigned int uuid_random_function(int max)
+{
+  return GetBetterRandom(max);
+}
+
+char *getUUIDExt(unsigned int (*random_function)(int max))
+{
+  static char uuid[UUID_LENGTH + 1];
+  int data[UUID_BYTES];
+  int count = 0;
+  int i;
+
+  for (i = 0; i < UUID_BYTES; i++)
+    data[i] = random_function(256);
+
+  data[6] = 0x40 | (data[6] & 0x0f);
+  data[8] = 0x80 | (data[8] & 0x3f);
+
+  for (i = 0; i < UUID_BYTES; i++)
+  {
+    sprintf(&uuid[count], "%02x", data[i]);
+    count += 2;
+
+    if (i == 3 || i == 5 || i == 7 || i == 9)
+      strcat(&uuid[count++], "-");
+  }
+
+  return uuid;
+}
+
+char *getUUID(void)
+{
+  return getUUIDExt(uuid_random_function);
+}
+
+
 // ----------------------------------------------------------------------------
 // counter functions
 // ----------------------------------------------------------------------------
@@ -615,7 +756,7 @@ void SkipUntilDelayReached(unsigned int *counter_var, unsigned int delay,
 // random generator functions
 // ----------------------------------------------------------------------------
 
-unsigned int init_random_number(int nr, int seed)
+static unsigned int init_random_number_ext(int nr, int seed)
 {
   if (seed == NEW_RANDOMIZE)
   {
@@ -643,9 +784,32 @@ unsigned int init_random_number(int nr, int seed)
   return (unsigned int) seed;
 }
 
+static unsigned int prng_seed_gettimeofday(void)
+{
+  struct timeval current_time;
+
+  gettimeofday(&current_time, NULL);
+
+  prng_seed_bytes(&current_time, sizeof(current_time));
+
+  return 0;
+}
+
+unsigned int init_random_number(int nr, int seed)
+{
+  return (nr == RANDOM_BETTER ? prng_seed_gettimeofday() :
+         init_random_number_ext(nr, seed));
+}
+
+static unsigned int get_random_number_ext(int nr)
+{
+  return (nr == RANDOM_BETTER ? prng_get_uint() :
+         random_linux_libc(nr));
+}
+
 unsigned int get_random_number(int nr, int max)
 {
-  return (max > 0 ? random_linux_libc(nr) % max : 0);
+  return (max > 0 ? get_random_number_ext(nr) % max : 0);
 }
 
 
@@ -686,6 +850,48 @@ static char *get_corrected_real_name(char *real_name)
 }
 #endif
 
+#if defined(PLATFORM_UNIX)
+static struct passwd *getPasswdEntry(void)
+{
+#if defined(PLATFORM_EMSCRIPTEN)
+  // currently not fully supported; force fallback to default values
+  return NULL;
+#else
+  return getpwuid(getuid());
+#endif
+}
+
+char *getUnixLoginName(void)
+{
+  struct passwd *pwd = getPasswdEntry();
+
+  if (pwd != NULL && strlen(pwd->pw_name) != 0)
+    return pwd->pw_name;
+
+  return NULL;
+}
+
+char *getUnixRealName(void)
+{
+  struct passwd *pwd = getPasswdEntry();
+
+  if (pwd != NULL && strlen(pwd->pw_gecos) != 0)
+    return pwd->pw_gecos;
+
+  return NULL;
+}
+
+char *getUnixHomeDir(void)
+{
+  struct passwd *pwd = getPasswdEntry();
+
+  if (pwd != NULL && strlen(pwd->pw_dir) != 0)
+    return pwd->pw_dir;
+
+  return NULL;
+}
+#endif
+
 char *getLoginName(void)
 {
   static char *login_name = NULL;
@@ -694,6 +900,7 @@ char *getLoginName(void)
   if (login_name == NULL)
   {
     unsigned long buffer_size = MAX_USERNAME_LEN + 1;
+
     login_name = checked_malloc(buffer_size);
 
     if (GetUserName(login_name, &buffer_size) == 0)
@@ -702,12 +909,12 @@ char *getLoginName(void)
 #elif defined(PLATFORM_UNIX) && !defined(PLATFORM_ANDROID)
   if (login_name == NULL)
   {
-    struct passwd *pwd;
+    char *name = getUnixLoginName();
 
-    if ((pwd = getpwuid(getuid())) == NULL)
-      login_name = ANONYMOUS_NAME;
+    if (name != NULL)
+      login_name = getStringCopy(name);
     else
-      login_name = getStringCopy(pwd->pw_name);
+      login_name = ANONYMOUS_NAME;
   }
 #else
   login_name = ANONYMOUS_NAME;
@@ -734,10 +941,10 @@ char *getRealName(void)
 #elif defined(PLATFORM_UNIX) && !defined(PLATFORM_ANDROID)
   if (real_name == NULL)
   {
-    struct passwd *pwd;
+    char *name = getUnixRealName();
 
-    if ((pwd = getpwuid(getuid())) != NULL && strlen(pwd->pw_gecos) != 0)
-      real_name = get_corrected_real_name(pwd->pw_gecos);
+    if (name != NULL)
+      real_name = get_corrected_real_name(name);
     else
       real_name = ANONYMOUS_NAME;
   }
@@ -748,6 +955,53 @@ char *getRealName(void)
   return real_name;
 }
 
+char *getFixedUserName(char *name)
+{
+  // needed because player name must be a fixed length string
+  char *name_new = checked_malloc(MAX_PLAYER_NAME_LEN + 1);
+
+  strncpy(name_new, name, MAX_PLAYER_NAME_LEN);
+  name_new[MAX_PLAYER_NAME_LEN] = '\0';
+
+  if (strlen(name) > MAX_PLAYER_NAME_LEN)              // name has been cut
+    if (strchr(name_new, ' '))
+      *strchr(name_new, ' ') = '\0';
+
+  return name_new;
+}
+
+char *getDefaultUserName(int nr)
+{
+  static char *user_name[MAX_PLAYER_NAMES] = { NULL };
+
+  nr = MIN(MAX(0, nr), MAX_PLAYER_NAMES - 1);
+
+  if (user_name[nr] == NULL)
+  {
+    user_name[nr] = (nr == 0 ? getLoginName() : EMPTY_PLAYER_NAME);
+    user_name[nr] = getFixedUserName(user_name[nr]);
+  }
+
+  return user_name[nr];
+}
+
+char *getTimestampFromEpoch(time_t epoch_seconds)
+{
+  struct tm *now = localtime(&epoch_seconds);
+  static char timestamp[20];
+
+  sprintf(timestamp, "%04d%02d%02d-%02d%02d%02d",
+         now->tm_year + 1900, now->tm_mon + 1, now->tm_mday,
+         now->tm_hour, now->tm_min, now->tm_sec);
+
+  return timestamp;
+}
+
+char *getCurrentTimestamp(void)
+{
+  return getTimestampFromEpoch(time(NULL));
+}
+
 time_t getFileTimestampEpochSeconds(char *filename)
 {
   struct stat file_status;
@@ -991,6 +1245,22 @@ boolean strEqualN(char *s1, char *s2, int n)
          strncmp(s1, s2, n) == 0);
 }
 
+boolean strEqualCase(char *s1, char *s2)
+{
+  return (s1 == NULL && s2 == NULL ? TRUE  :
+         s1 == NULL && s2 != NULL ? FALSE :
+         s1 != NULL && s2 == NULL ? FALSE :
+         strcasecmp(s1, s2) == 0);
+}
+
+boolean strEqualCaseN(char *s1, char *s2, int n)
+{
+  return (s1 == NULL && s2 == NULL ? TRUE  :
+         s1 == NULL && s2 != NULL ? FALSE :
+         s1 != NULL && s2 == NULL ? FALSE :
+         strncasecmp(s1, s2, n) == 0);
+}
+
 boolean strPrefix(char *s, char *prefix)
 {
   return (s == NULL && prefix == NULL ? TRUE  :
@@ -1037,8 +1307,7 @@ void GetOptions(int argc, char *argv[],
                void (*print_usage_function)(void),
                void (*print_version_function)(void))
 {
-  char *ro_base_path = getProgramMainDataPath(argv[0], RO_BASE_PATH);
-  char *rw_base_path = getProgramMainDataPath(argv[0], RW_BASE_PATH);
+  char *base_path = getProgramMainDataPath(argv[0], BASE_PATH);
   char **argvplus = checked_calloc((argc + 1) * sizeof(char **));
   char **options_left = &argvplus[1];
 
@@ -1050,18 +1319,22 @@ void GetOptions(int argc, char *argv[],
   options.server_host = NULL;
   options.server_port = 0;
 
-  options.ro_base_directory = ro_base_path;
-  options.rw_base_directory = rw_base_path;
-  options.level_directory    = getPath2(ro_base_path, LEVELS_DIRECTORY);
-  options.graphics_directory = getPath2(ro_base_path, GRAPHICS_DIRECTORY);
-  options.sounds_directory   = getPath2(ro_base_path, SOUNDS_DIRECTORY);
-  options.music_directory    = getPath2(ro_base_path, MUSIC_DIRECTORY);
-  options.docs_directory     = getPath2(ro_base_path, DOCS_DIRECTORY);
-  options.conf_directory     = getPath2(ro_base_path, CONF_DIRECTORY);
+  options.base_directory = base_path;
+
+  options.level_directory    = getPath2(base_path, LEVELS_DIRECTORY);
+  options.graphics_directory = getPath2(base_path, GRAPHICS_DIRECTORY);
+  options.sounds_directory   = getPath2(base_path, SOUNDS_DIRECTORY);
+  options.music_directory    = getPath2(base_path, MUSIC_DIRECTORY);
+  options.docs_directory     = getPath2(base_path, DOCS_DIRECTORY);
+  options.conf_directory     = getPath2(base_path, CONF_DIRECTORY);
 
   options.execute_command = NULL;
+  options.tape_log_filename = NULL;
   options.special_flags = NULL;
   options.debug_mode = NULL;
+  options.player_name = NULL;
+  options.identifier = NULL;
+  options.level_nr = NULL;
 
   options.mytapes = FALSE;
   options.serveronly = FALSE;
@@ -1093,7 +1366,7 @@ void GetOptions(int argc, char *argv[],
     int option_len = strlen(option);
 
     if (option_len >= MAX_OPTION_LEN)
-      Error(ERR_EXIT_HELP, "unrecognized option '%s'", option);
+      FailWithHelp("unrecognized option '%s'", option);
 
     strcpy(option_str, option);                        // copy argument into buffer
     option = option_str;
@@ -1111,14 +1384,14 @@ void GetOptions(int argc, char *argv[],
     {
       *option_arg++ = '\0';                    // cut argument from option
       if (*option_arg == '\0')                 // no argument after '='
-       Error(ERR_EXIT_HELP, "option '%s' has invalid argument", option_str);
+       FailWithHelp("option '%s' has invalid argument", option_str);
     }
 
     option_len = strlen(option);
 
     if (strEqual(option, "-"))
     {
-      Error(ERR_EXIT_HELP, "unrecognized option '%s'", option);
+      FailWithHelp("unrecognized option '%s'", option);
     }
     else if (strncmp(option, "-help", option_len) == 0)
     {
@@ -1129,26 +1402,24 @@ void GetOptions(int argc, char *argv[],
     else if (strncmp(option, "-basepath", option_len) == 0)
     {
       if (option_arg == NULL)
-       Error(ERR_EXIT_HELP, "option '%s' requires an argument", option_str);
+       FailWithHelp("option '%s' requires an argument", option_str);
 
-      // this should be extended to separate options for ro and rw data
-      options.ro_base_directory = ro_base_path = getStringCopy(option_arg);
-      options.rw_base_directory = rw_base_path = getStringCopy(option_arg);
+      options.base_directory = base_path = getStringCopy(option_arg);
       if (option_arg == next_option)
        options_left++;
 
       // adjust paths for sub-directories in base directory accordingly
-      options.level_directory    = getPath2(ro_base_path, LEVELS_DIRECTORY);
-      options.graphics_directory = getPath2(ro_base_path, GRAPHICS_DIRECTORY);
-      options.sounds_directory   = getPath2(ro_base_path, SOUNDS_DIRECTORY);
-      options.music_directory    = getPath2(ro_base_path, MUSIC_DIRECTORY);
-      options.docs_directory     = getPath2(ro_base_path, DOCS_DIRECTORY);
-      options.conf_directory     = getPath2(ro_base_path, CONF_DIRECTORY);
+      options.level_directory    = getPath2(base_path, LEVELS_DIRECTORY);
+      options.graphics_directory = getPath2(base_path, GRAPHICS_DIRECTORY);
+      options.sounds_directory   = getPath2(base_path, SOUNDS_DIRECTORY);
+      options.music_directory    = getPath2(base_path, MUSIC_DIRECTORY);
+      options.docs_directory     = getPath2(base_path, DOCS_DIRECTORY);
+      options.conf_directory     = getPath2(base_path, CONF_DIRECTORY);
     }
     else if (strncmp(option, "-levels", option_len) == 0)
     {
       if (option_arg == NULL)
-       Error(ERR_EXIT_HELP, "option '%s' requires an argument", option_str);
+       FailWithHelp("option '%s' requires an argument", option_str);
 
       options.level_directory = getStringCopy(option_arg);
       if (option_arg == next_option)
@@ -1157,7 +1428,7 @@ void GetOptions(int argc, char *argv[],
     else if (strncmp(option, "-graphics", option_len) == 0)
     {
       if (option_arg == NULL)
-       Error(ERR_EXIT_HELP, "option '%s' requires an argument", option_str);
+       FailWithHelp("option '%s' requires an argument", option_str);
 
       options.graphics_directory = getStringCopy(option_arg);
       if (option_arg == next_option)
@@ -1166,7 +1437,7 @@ void GetOptions(int argc, char *argv[],
     else if (strncmp(option, "-sounds", option_len) == 0)
     {
       if (option_arg == NULL)
-       Error(ERR_EXIT_HELP, "option '%s' requires an argument", option_str);
+       FailWithHelp("option '%s' requires an argument", option_str);
 
       options.sounds_directory = getStringCopy(option_arg);
       if (option_arg == next_option)
@@ -1175,7 +1446,7 @@ void GetOptions(int argc, char *argv[],
     else if (strncmp(option, "-music", option_len) == 0)
     {
       if (option_arg == NULL)
-       Error(ERR_EXIT_HELP, "option '%s' requires an argument", option_str);
+       FailWithHelp("option '%s' requires an argument", option_str);
 
       options.music_directory = getStringCopy(option_arg);
       if (option_arg == next_option)
@@ -1201,6 +1472,33 @@ void GetOptions(int argc, char *argv[],
       if (option_arg != next_option)
        options.debug_mode = getStringCopy(option_arg);
     }
+    else if (strncmp(option, "-player-name", option_len) == 0)
+    {
+      if (option_arg == NULL)
+       FailWithHelp("option '%s' requires an argument", option_str);
+
+      options.player_name = getStringCopy(option_arg);
+      if (option_arg == next_option)
+       options_left++;
+    }
+    else if (strncmp(option, "-identifier", option_len) == 0)
+    {
+      if (option_arg == NULL)
+       FailWithHelp("option '%s' requires an argument", option_str);
+
+      options.identifier = getStringCopy(option_arg);
+      if (option_arg == next_option)
+       options_left++;
+    }
+    else if (strncmp(option, "-level-nr", option_len) == 0)
+    {
+      if (option_arg == NULL)
+       FailWithHelp("option '%s' requires an argument", option_str);
+
+      options.level_nr = getStringCopy(option_arg);
+      if (option_arg == next_option)
+       options_left++;
+    }
     else if (strncmp(option, "-verbose", option_len) == 0)
     {
       options.verbose = TRUE;
@@ -1219,7 +1517,7 @@ void GetOptions(int argc, char *argv[],
     else if (strncmp(option, "-execute", option_len) == 0)
     {
       if (option_arg == NULL)
-       Error(ERR_EXIT_HELP, "option '%s' requires an argument", option_str);
+       FailWithHelp("option '%s' requires an argument", option_str);
 
       options.execute_command = getStringCopy(option_arg);
       if (option_arg == next_option)
@@ -1228,6 +1526,15 @@ void GetOptions(int argc, char *argv[],
       // when doing batch processing, always enable verbose mode (warnings)
       options.verbose = TRUE;
     }
+    else if (strncmp(option, "-tape_logfile", option_len) == 0)
+    {
+      if (option_arg == NULL)
+       FailWithHelp("option '%s' requires an argument", option_str);
+
+      options.tape_log_filename = getStringCopy(option_arg);
+      if (option_arg == next_option)
+       options_left++;
+    }
 #if defined(PLATFORM_MACOSX)
     else if (strPrefix(option, "-psn"))
     {
@@ -1236,7 +1543,7 @@ void GetOptions(int argc, char *argv[],
 #endif
     else if (*option == '-')
     {
-      Error(ERR_EXIT_HELP, "unrecognized option '%s'", option_str);
+      FailWithHelp("unrecognized option '%s'", option_str);
     }
     else if (options.server_host == NULL)
     {
@@ -1246,125 +1553,16 @@ void GetOptions(int argc, char *argv[],
     {
       options.server_port = atoi(*options_left);
       if (options.server_port < 1024)
-       Error(ERR_EXIT_HELP, "bad port number '%d'", options.server_port);
+       FailWithHelp("bad port number '%d'", options.server_port);
     }
     else
-      Error(ERR_EXIT_HELP, "too many arguments");
+      FailWithHelp("too many arguments");
 
     options_left++;
   }
 }
 
 
-// ----------------------------------------------------------------------------
-// error handling functions
-// ----------------------------------------------------------------------------
-
-#define MAX_INTERNAL_ERROR_SIZE                1024
-
-// used by SetError() and GetError() to store internal error messages
-static char internal_error[MAX_INTERNAL_ERROR_SIZE];
-
-void SetError(char *format, ...)
-{
-  va_list ap;
-
-  va_start(ap, format);
-  vsnprintf(internal_error, MAX_INTERNAL_ERROR_SIZE, format, ap);
-  va_end(ap);
-}
-
-char *GetError(void)
-{
-  return internal_error;
-}
-
-void Error(int mode, char *format, ...)
-{
-  static boolean last_line_was_separator = FALSE;
-  char *process_name = "";
-
-  if (program.log_file[LOG_ERR_ID] == NULL)
-    return;
-
-#if defined(PLATFORM_ANDROID)
-  android_log_prio = (mode & ERR_DEBUG ? ANDROID_LOG_DEBUG :
-                     mode & ERR_INFO ? ANDROID_LOG_INFO :
-                     mode & ERR_WARN ? ANDROID_LOG_WARN :
-                     mode & ERR_EXIT ? ANDROID_LOG_FATAL :
-                     ANDROID_LOG_UNKNOWN);
-#endif
-
-  // display debug messages only when running in debug mode
-  if (mode & ERR_DEBUG && !options.debug)
-    return;
-
-  // display warnings only when running in verbose mode
-  if (mode & ERR_WARN && !options.verbose)
-    return;
-
-  if (mode == ERR_INFO_LINE)
-  {
-    if (!last_line_was_separator)
-      printf_log_line(format, 79);
-
-    last_line_was_separator = TRUE;
-
-    return;
-  }
-
-  last_line_was_separator = FALSE;
-
-  if (mode & ERR_SOUND_SERVER)
-    process_name = " sound server";
-  else if (mode & ERR_NETWORK_SERVER)
-    process_name = " network server";
-  else if (mode & ERR_NETWORK_CLIENT)
-    process_name = " network client **";
-
-  if (format)
-  {
-#if !defined(PLATFORM_ANDROID)
-    printf_log_nonewline("%s%s: ", program.command_basename, process_name);
-#endif
-
-    if (mode & ERR_WARN)
-      printf_log_nonewline("warning: ");
-
-    if (mode & ERR_EXIT)
-      printf_log_nonewline("fatal error: ");
-
-    va_list ap;
-
-    va_start(ap, format);
-    vprintf_log(format, ap);
-    va_end(ap);
-
-    if ((mode & ERR_EXIT) && !(mode & ERR_FROM_SERVER))
-    {
-      va_start(ap, format);
-      program.exit_message_function(format, ap);
-      va_end(ap);
-    }
-  }
-  
-  if (mode & ERR_HELP)
-    printf_log("%s: Try option '--help' for more information.",
-              program.command_basename);
-
-  if (mode & ERR_EXIT)
-    printf_log("%s%s: aborting", program.command_basename, process_name);
-
-  if (mode & ERR_EXIT)
-  {
-    if (mode & ERR_FROM_SERVER)
-      exit(1);                         // child process: normal exit
-    else
-      program.exit_function(1);                // main process: clean up stuff
-  }
-}
-
-
 // ----------------------------------------------------------------------------
 // checked memory allocation and freeing functions
 // ----------------------------------------------------------------------------
@@ -1376,7 +1574,7 @@ void *checked_malloc(unsigned int size)
   ptr = malloc(size);
 
   if (ptr == NULL)
-    Error(ERR_EXIT, "cannot allocate %d bytes -- out of memory", size);
+    Fail("cannot allocate %d bytes -- out of memory", size);
 
   return ptr;
 }
@@ -1388,7 +1586,7 @@ void *checked_calloc(unsigned int size)
   ptr = calloc(1, size);
 
   if (ptr == NULL)
-    Error(ERR_EXIT, "cannot allocate %d bytes -- out of memory", size);
+    Fail("cannot allocate %d bytes -- out of memory", size);
 
   return ptr;
 }
@@ -1398,7 +1596,7 @@ void *checked_realloc(void *ptr, unsigned int size)
   ptr = realloc(ptr, size);
 
   if (ptr == NULL)
-    Error(ERR_EXIT, "cannot allocate %d bytes -- out of memory", size);
+    Fail("cannot allocate %d bytes -- out of memory", size);
 
   return ptr;
 }
@@ -1631,6 +1829,167 @@ void WriteUnusedBytesToFile(FILE *file, unsigned int bytes)
 }
 
 
+// ----------------------------------------------------------------------------
+// functions to convert between ISO-8859-1 and UTF-8
+// ----------------------------------------------------------------------------
+
+char *getUTF8FromLatin1(char *latin1)
+{
+  int max_utf8_size = 2 * strlen(latin1) + 1;
+  char *utf8 = checked_calloc(max_utf8_size);
+  unsigned char *src = (unsigned char *)latin1;
+  unsigned char *dst = (unsigned char *)utf8;
+
+  while (*src)
+  {
+    if (*src < 128)            // pure 7-bit ASCII
+    {
+      *dst++ = *src;
+    }
+    else if (*src >= 160)      // non-ASCII characters
+    {
+      *dst++ = 194 + (*src >= 192);
+      *dst++ = 128 + (*src & 63);
+    }
+    else                       // undefined in ISO-8859-1
+    {
+      *dst++ = '?';
+    }
+
+    src++;
+  }
+
+  // only use the smallest possible string buffer size
+  utf8 = checked_realloc(utf8, strlen(utf8) + 1);
+
+  return utf8;
+}
+
+char *getLatin1FromUTF8(char *utf8)
+{
+  int max_latin1_size = strlen(utf8) + 1;
+  char *latin1 = checked_calloc(max_latin1_size);
+  unsigned char *src = (unsigned char *)utf8;
+  unsigned char *dst = (unsigned char *)latin1;
+
+  while (*src)
+  {
+    if (*src < 128)                            // pure 7-bit ASCII
+    {
+      *dst++ = *src++;
+    }
+    else if (src[0] == 194 &&
+            src[1] >= 128 && src[1] < 192)     // non-ASCII characters
+    {
+      *dst++ = src[1];
+      src += 2;
+    }
+    else if (src[0] == 195 &&
+            src[1] >= 128 && src[1] < 192)     // non-ASCII characters
+    {
+      *dst++ = src[1] + 64;
+      src += 2;
+    }
+
+    // all other UTF-8 characters are undefined in ISO-8859-1
+
+    else if (src[0] >= 192 && src[0] < 224 &&
+            src[1] >= 128 && src[1] < 192)
+    {
+      *dst++ = '?';
+      src += 2;
+    }
+    else if (src[0] >= 224 && src[0] < 240 &&
+            src[1] >= 128 && src[1] < 192 &&
+            src[2] >= 128 && src[2] < 192)
+    {
+      *dst++ = '?';
+      src += 3;
+    }
+    else if (src[0] >= 240 && src[0] < 248 &&
+            src[1] >= 128 && src[1] < 192 &&
+            src[2] >= 128 && src[2] < 192 &&
+            src[3] >= 128 && src[3] < 192)
+    {
+      *dst++ = '?';
+      src += 4;
+    }
+    else if (src[0] >= 248 && src[0] < 252 &&
+            src[1] >= 128 && src[1] < 192 &&
+            src[2] >= 128 && src[2] < 192 &&
+            src[3] >= 128 && src[3] < 192 &&
+            src[4] >= 128 && src[4] < 192)
+    {
+      *dst++ = '?';
+      src += 5;
+    }
+    else if (src[0] >= 252 && src[0] < 254 &&
+            src[1] >= 128 && src[1] < 192 &&
+            src[2] >= 128 && src[2] < 192 &&
+            src[3] >= 128 && src[3] < 192 &&
+            src[4] >= 128 && src[4] < 192 &&
+            src[5] >= 128 && src[5] < 192)
+    {
+      *dst++ = '?';
+      src += 6;
+    }
+    else
+    {
+      *dst++ = '?';
+      src++;
+    }
+  }
+
+  // only use the smallest possible string buffer size
+  latin1 = checked_realloc(latin1, strlen(latin1) + 1);
+
+  return latin1;
+}
+
+
+// ----------------------------------------------------------------------------
+// functions for JSON handling
+// ----------------------------------------------------------------------------
+
+char *getEscapedJSON(char *s)
+{
+  int max_json_size = 2 * strlen(s) + 1;
+  char *json = checked_calloc(max_json_size);
+  unsigned char *src = (unsigned char *)s;
+  unsigned char *dst = (unsigned char *)json;
+  char *escaped[256] =
+  {
+    ['\b'] = "\\b",
+    ['\f'] = "\\f",
+    ['\n'] = "\\n",
+    ['\r'] = "\\r",
+    ['\t'] = "\\t",
+    ['\"'] = "\\\"",
+    ['\\'] = "\\\\",
+  };
+
+  while (*src)
+  {
+    if (escaped[*src] != NULL)
+    {
+      char *esc = escaped[*src++];
+
+      while (*esc)
+       *dst++ = *esc++;
+    }
+    else
+    {
+      *dst++ = *src++;
+    }
+  }
+
+  // only use the smallest possible string buffer size
+  json = checked_realloc(json, strlen(json) + 1);
+
+  return json;
+}
+
+
 // ----------------------------------------------------------------------------
 // functions to translate key identifiers between different format
 // ----------------------------------------------------------------------------
@@ -2460,6 +2819,22 @@ int copyFile(char *filename_from, char *filename_to)
   return 0;
 }
 
+boolean touchFile(char *filename)
+{
+  FILE *file;
+
+  if (!(file = fopen(filename, MODE_WRITE)))
+  {
+    Warn("cannot touch file '%s'", filename);
+
+    return FALSE;
+  }
+
+  fclose(file);
+
+  return TRUE;
+}
+
 
 // ----------------------------------------------------------------------------
 // functions for directory handling
@@ -2952,13 +3327,14 @@ struct FileInfo *getFileListFromConfigList(struct ConfigInfo *config_list,
   num_file_list_entries_found = list_pos + 1;
   if (num_file_list_entries_found != num_file_list_entries)
   {
-    Error(ERR_INFO_LINE, "-");
-    Error(ERR_INFO, "inconsistant config list information:");
-    Error(ERR_INFO, "- should be:   %d (according to 'src/conf_xxx.h')",
+    Error("---");
+    Error("inconsistant config list information:");
+    Error("- should be:   %d (according to 'src/conf_xxx.h')",
          num_file_list_entries);
-    Error(ERR_INFO, "- found to be: %d (according to 'src/conf_xxx.c')",
+    Error("- found to be: %d (according to 'src/conf_xxx.c')",
          num_file_list_entries_found);
-    Error(ERR_EXIT,   "please fix");
+
+    Fail("please fix");
   }
 
   freeSetupFileHash(ignore_tokens_hash);
@@ -3473,8 +3849,8 @@ void LoadArtworkConfig(struct ArtworkListInfo *artwork_info)
   char *filename_base = UNDEFINED_FILENAME, *filename_local;
   int i, j;
 
-  DrawInitText("Loading artwork config", 120, FC_GREEN);
-  DrawInitText(ARTWORKINFO_FILENAME(artwork_info->type), 150, FC_YELLOW);
+  DrawInitTextHead("Loading artwork config");
+  DrawInitTextItem(ARTWORKINFO_FILENAME(artwork_info->type));
 
   // always start with reliable default values
   for (i = 0; i < num_file_list_entries; i++)
@@ -3559,6 +3935,11 @@ static void replaceArtworkListEntry(struct ArtworkListInfo *artwork_info,
   char *basename = file_list_entry->filename;
   char *filename = getCustomArtworkFilename(basename, artwork_info->type);
 
+  // mark all images from non-default graphics directory as "redefined"
+  if (artwork_info->type == ARTWORK_TYPE_GRAPHICS &&
+      !strPrefix(filename, options.graphics_directory))
+    file_list_entry->redefined = TRUE;
+
   if (filename == NULL)
   {
     Warn("cannot find artwork file '%s'", basename);
@@ -3569,14 +3950,14 @@ static void replaceArtworkListEntry(struct ArtworkListInfo *artwork_info,
     if (file_list_entry->default_is_cloned &&
        strEqual(basename, UNDEFINED_FILENAME))
     {
-      int error_mode = ERR_WARN;
+      void (*error_func)(char *, ...) = Warn;
 
       // we can get away without sounds and music, but not without graphics
       if (*listnode == NULL && artwork_info->type == ARTWORK_TYPE_GRAPHICS)
-       error_mode = ERR_EXIT;
+       error_func = Fail;
 
-      Error(error_mode, "token '%s' was cloned and has no default filename",
-           file_list_entry->token);
+      error_func("token '%s' was cloned and has no default filename",
+                file_list_entry->token);
 
       return;
     }
@@ -3593,13 +3974,13 @@ static void replaceArtworkListEntry(struct ArtworkListInfo *artwork_info,
 
     if (filename == NULL)
     {
-      int error_mode = ERR_WARN;
+      void (*error_func)(char *, ...) = Warn;
 
       // we can get away without sounds and music, but not without graphics
       if (*listnode == NULL && artwork_info->type == ARTWORK_TYPE_GRAPHICS)
-       error_mode = ERR_EXIT;
+       error_func = Fail;
 
-      Error(error_mode, "cannot find default artwork file '%s'", basename);
+      error_func("cannot find default artwork file '%s'", basename);
 
       return;
     }
@@ -3627,8 +4008,8 @@ static void replaceArtworkListEntry(struct ArtworkListInfo *artwork_info,
       return;
   }
 
-  DrawInitText(init_text[artwork_info->type], 120, FC_GREEN);
-  DrawInitText(basename, 150, FC_YELLOW);
+  DrawInitTextHead(init_text[artwork_info->type]);
+  DrawInitTextItem(basename);
 
   if ((*listnode = artwork_info->load_artwork(filename)) != NULL)
   {
@@ -3639,13 +4020,13 @@ static void replaceArtworkListEntry(struct ArtworkListInfo *artwork_info,
   }
   else
   {
-    int error_mode = ERR_WARN;
+    void (*error_func)(char *, ...) = Warn;
 
     // we can get away without sounds and music, but not without graphics
     if (artwork_info->type == ARTWORK_TYPE_GRAPHICS)
-      error_mode = ERR_EXIT;
+      error_func = Fail;
 
-    Error(error_mode, "cannot load artwork file '%s'", basename);
+    error_func("cannot load artwork file '%s'", basename);
 
     return;
   }
@@ -3729,14 +4110,14 @@ void FreeCustomArtworkLists(struct ArtworkListInfo *artwork_info)
 
 char *getLogFilename(char *basename)
 {
-  return getPath2(getUserGameDataDir(), basename);
+  return getPath2(getMainUserGameDataDir(), basename);
 }
 
 void OpenLogFiles(void)
 {
   int i;
 
-  InitUserDataDirectory();
+  InitMainUserDataDirectory();
 
   for (i = 0; i < NUM_LOGS; i++)
   {
@@ -3796,7 +4177,7 @@ void NotifyUserAboutErrorFile(void)
 
 #if DEBUG
 
-#define DEBUG_PRINT_INIT_TIMESTAMPS            FALSE
+#define DEBUG_PRINT_INIT_TIMESTAMPS            TRUE
 #define DEBUG_PRINT_INIT_TIMESTAMPS_DEPTH      10
 
 #define DEBUG_NUM_TIMESTAMPS                   10
@@ -3844,9 +4225,9 @@ void debug_print_timestamp(int counter_nr, char *message)
   float timestamp_interval;
 
   if (counter_nr < 0)
-    Error(ERR_EXIT, "debugging: invalid negative counter");
+    Fail("debugging: invalid negative counter");
   else if (counter_nr >= DEBUG_NUM_TIMESTAMPS)
-    Error(ERR_EXIT, "debugging: increase DEBUG_NUM_TIMESTAMPS in misc.c");
+    Fail("debugging: increase DEBUG_NUM_TIMESTAMPS in misc.c");
 
 #if DEBUG_TIME_IN_MICROSECONDS
   static double counter[DEBUG_NUM_TIMESTAMPS][2];
@@ -3864,7 +4245,7 @@ void debug_print_timestamp(int counter_nr, char *message)
   counter[counter_nr][1] = counter[counter_nr][0];
 
   if (message)
-    Debug("time", "%s%s%s %.3f %s",
+    Debug("time:init", "%s%s%s %.3f %s",
          debug_print_timestamp_get_padding(counter_nr * indent_size),
          message,
          debug_print_timestamp_get_padding(padding_size - strlen(message)),