renamed and moved function to sanitize player name
[rocksndiamonds.git] / src / libgame / misc.c
index 0a1383fde6378f2c152d41e7b3ca5f045cd11973..a6bef9b878af3869cedb0d7272927584606c7093 100644 (file)
@@ -4,7 +4,7 @@
 // (c) 1995-2014 by Artsoft Entertainment
 //                         Holger Schemel
 //                 info@artsoft.org
-//                 http://www.artsoft.org/
+//                 https://www.artsoft.org/
 // ----------------------------------------------------------------------------
 // misc.c
 // ============================================================================
@@ -21,7 +21,7 @@
 
 #include "platform.h"
 
-#if !defined(PLATFORM_WIN32)
+#if defined(PLATFORM_UNIX)
 #include <pwd.h>
 #include <sys/param.h>
 #endif
@@ -251,6 +251,227 @@ void PrintLineWithPrefix(char *prefix, char *line_chars, int line_length)
 }
 
 
+// ----------------------------------------------------------------------------
+// generic logging and error handling functions
+// ----------------------------------------------------------------------------
+
+enum log_levels
+{
+  LOG_UNKNOWN = 0,
+  LOG_DEBUG,
+  LOG_INFO,
+  LOG_WARN,
+  LOG_ERROR,
+  LOG_FATAL
+};
+
+static char *log_tokens[] =
+{
+  "UNKNOWN",
+  "DEBUG",
+  "INFO",
+  "WARN",
+  "ERROR",
+  "FATAL"
+};
+
+static void printf_log_prefix(int log_level, char *mode)
+{
+  if (log_level < 0 || log_level > LOG_FATAL)
+    return;
+
+  char *log_token = log_tokens[log_level];
+
+  if (log_level == LOG_DEBUG)
+    printf_log_nonewline("[%s] [%s] ", log_token, mode);
+  else
+    printf_log_nonewline("[%s] ", log_token);
+}
+
+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;
+
+    // if optional debug mode specified, limit debug output accordingly
+    if (options.debug_mode != NULL &&
+       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 :
+                     log_level == LOG_INFO  ? ANDROID_LOG_INFO :
+                     log_level == LOG_WARN  ? ANDROID_LOG_WARN :
+                     log_level == LOG_ERROR ? ANDROID_LOG_ERROR :
+                     log_level == LOG_FATAL ? ANDROID_LOG_FATAL :
+                     ANDROID_LOG_UNKNOWN);
+#endif
+
+  static boolean last_line_was_separator = FALSE;
+  char *log_token = log_tokens[log_level];
+
+  if (strEqual(format, "===") ||
+      strEqual(format, "---"))
+  {
+    static char *mode_last = NULL;
+    char line_char[2] = { format[0], '\0' };
+    int line_length = 80 - strlen(log_token) - 3;
+
+    if (log_level == LOG_DEBUG)
+      line_length -= strlen(mode) + 3;
+
+    if (last_line_was_separator && strEqual(mode, mode_last))
+      return;
+
+    printf_log_prefix(log_level, mode);
+    printf_log_line(line_char, line_length);
+
+    if (!strEqual(mode, mode_last))
+      setString(&mode_last, mode);
+
+    last_line_was_separator = TRUE;
+
+    return;
+  }
+
+  last_line_was_separator = FALSE;
+
+  printf_log_prefix(log_level, mode);
+
+  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);
+  vLog(LOG_DEBUG, mode, format, ap);
+  va_end(ap);
+}
+
+void Info(char *format, ...)
+{
+  va_list ap;
+
+  va_start(ap, format);
+  vLog(LOG_INFO, NULL, format, ap);
+  va_end(ap);
+}
+
+void Warn(char *format, ...)
+{
+  va_list ap;
+
+  va_start(ap, format);
+  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
 // ----------------------------------------------------------------------------
@@ -453,10 +674,15 @@ void SkipUntilDelayReached(unsigned int *counter_var, unsigned int delay,
 
 #if 0
 #if DEBUG
-  printf("::: %d: %d ms", *loop_var, delay);
   if (skip_frames)
-    printf(" -> SKIP %d FRAME(S) [%d ms]", skip_frames, skip_frames * delay);
-  printf("\n");
+    Debug("internal:SkipUntilDelayReached",
+         "%d: %d ms -> SKIP %d FRAME(S) [%d ms]",
+         *loop_var, delay,
+         skip_frames, skip_frames * delay);
+  else
+    Debug("internal:SkipUntilDelayReached",
+         "%d: %d ms",
+         *loop_var, delay);
 #endif
 #endif
 
@@ -557,6 +783,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;
@@ -565,6 +833,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)
@@ -573,12 +842,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;
@@ -605,10 +874,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;
   }
@@ -619,6 +888,21 @@ 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;
+}
+
 time_t getFileTimestampEpochSeconds(char *filename)
 {
   struct stat file_status;
@@ -876,7 +1160,7 @@ boolean strSuffix(char *s, char *suffix)
          s == NULL && suffix != NULL ? FALSE :
          s != NULL && suffix == NULL ? FALSE :
          strlen(s) < strlen(suffix)  ? FALSE :
-         strncmp(&s[strlen(s) - strlen(suffix)], suffix, strlen(suffix)) == 0);
+         strcmp(&s[strlen(s) - strlen(suffix)], suffix) == 0);
 }
 
 boolean strPrefixLower(char *s, char *prefix)
@@ -932,6 +1216,7 @@ void GetOptions(int argc, char *argv[],
 
   options.execute_command = NULL;
   options.special_flags = NULL;
+  options.debug_mode = NULL;
 
   options.mytapes = FALSE;
   options.serveronly = FALSE;
@@ -963,7 +1248,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;
@@ -981,14 +1266,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)
     {
@@ -999,11 +1284,11 @@ 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 = option_arg;
-      options.rw_base_directory = rw_base_path = option_arg;
+      options.ro_base_directory = ro_base_path = getStringCopy(option_arg);
+      options.rw_base_directory = rw_base_path = getStringCopy(option_arg);
       if (option_arg == next_option)
        options_left++;
 
@@ -1018,36 +1303,36 @@ void GetOptions(int argc, char *argv[],
     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 = option_arg;
+      options.level_directory = getStringCopy(option_arg);
       if (option_arg == next_option)
        options_left++;
     }
     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 = option_arg;
+      options.graphics_directory = getStringCopy(option_arg);
       if (option_arg == next_option)
        options_left++;
     }
     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 = option_arg;
+      options.sounds_directory = getStringCopy(option_arg);
       if (option_arg == next_option)
        options_left++;
     }
     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 = option_arg;
+      options.music_directory = getStringCopy(option_arg);
       if (option_arg == next_option)
        options_left++;
     }
@@ -1066,6 +1351,10 @@ void GetOptions(int argc, char *argv[],
     else if (strncmp(option, "-debug", option_len) == 0)
     {
       options.debug = TRUE;
+
+      // optionally, debug output can be limited to a specific debug mode
+      if (option_arg != next_option)
+       options.debug_mode = getStringCopy(option_arg);
     }
     else if (strncmp(option, "-verbose", option_len) == 0)
     {
@@ -1085,9 +1374,9 @@ 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 = option_arg;
+      options.execute_command = getStringCopy(option_arg);
       if (option_arg == next_option)
        options_left++;
 
@@ -1102,7 +1391,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)
     {
@@ -1112,125 +1401,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
 // ----------------------------------------------------------------------------
@@ -1242,7 +1422,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;
 }
@@ -1254,7 +1434,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;
 }
@@ -1264,7 +1444,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;
 }
@@ -1480,7 +1660,7 @@ void WriteBytesToFile(FILE *file, byte *buffer, unsigned int bytes)
 {
   int i;
 
-  for(i = 0; i < bytes; i++)
+  for (i = 0; i < bytes; i++)
     fputc(buffer[i], file);
 }
 
@@ -1552,10 +1732,6 @@ static void translate_keyname(Key *keysym, char **x11name, char **name, int mode
     { KSYM_Meta_R,     "XK_Meta_R",            "right meta" },
     { KSYM_Alt_L,      "XK_Alt_L",             "left alt" },
     { KSYM_Alt_R,      "XK_Alt_R",             "right alt" },
-#if !defined(TARGET_SDL2)
-    { KSYM_Super_L,    "XK_Super_L",           "left super" },  // Win-L
-    { KSYM_Super_R,    "XK_Super_R",           "right super" }, // Win-R
-#endif
     { KSYM_Mode_switch,        "XK_Mode_switch",       "mode switch" }, // Alt-R
     { KSYM_Multi_key,  "XK_Multi_key",         "multi key" },   // Ctrl-R
 
@@ -1569,7 +1745,6 @@ static void translate_keyname(Key *keysym, char **x11name, char **name, int mode
     { KSYM_Page_Up,    "XK_Page_Up",           "page up" },
     { KSYM_Page_Down,  "XK_Page_Down",         "page down" },
 
-#if defined(TARGET_SDL2)
     { KSYM_Select,     "XK_Select",            "select" },
     { KSYM_Menu,       "XK_Menu",              "menu" },        // menu key
     { KSYM_Back,       "XK_Back",              "back" },        // back key
@@ -1577,7 +1752,6 @@ static void translate_keyname(Key *keysym, char **x11name, char **name, int mode
 #if defined(PLATFORM_ANDROID)
     { KSYM_Rewind,     "XK_Rewind",            "rewind" },
     { KSYM_FastForward,        "XK_FastForward",       "fast forward" },
-#endif
 #endif
 
     // ASCII 0x20 to 0x40 keys (except numbers)
@@ -1628,7 +1802,6 @@ static void translate_keyname(Key *keysym, char **x11name, char **name, int mode
     { KSYM_udiaeresis, "XK_udiaeresis",        "u umlaut" },
     { KSYM_ssharp,     "XK_ssharp",            "sharp s" },
 
-#if defined(TARGET_SDL2)
     // special (non-ASCII) keys (UTF-8, for reverse mapping only)
     { KSYM_degree,     "XK_degree",            "\xc2\xb0" },
     { KSYM_Adiaeresis, "XK_Adiaeresis",        "\xc3\x84" },
@@ -1641,9 +1814,7 @@ static void translate_keyname(Key *keysym, char **x11name, char **name, int mode
 
     // other keys (for reverse mapping only)
     { KSYM_space,      "XK_space",             " " },
-#endif
 
-#if defined(TARGET_SDL2)
     // keypad keys are not in numerical order in SDL2
     { KSYM_KP_0,       "XK_KP_0",              "keypad 0" },
     { KSYM_KP_1,       "XK_KP_1",              "keypad 1" },
@@ -1655,7 +1826,6 @@ static void translate_keyname(Key *keysym, char **x11name, char **name, int mode
     { KSYM_KP_7,       "XK_KP_7",              "keypad 7" },
     { KSYM_KP_8,       "XK_KP_8",              "keypad 8" },
     { KSYM_KP_9,       "XK_KP_9",              "keypad 9" },
-#endif
 
     // end-of-array identifier
     { 0,                NULL,                  NULL }
@@ -1674,10 +1844,6 @@ static void translate_keyname(Key *keysym, char **x11name, char **name, int mode
       sprintf(name_buffer, "%c", 'a' + (char)(key - KSYM_a));
     else if (key >= KSYM_0 && key <= KSYM_9)
       sprintf(name_buffer, "%c", '0' + (char)(key - KSYM_0));
-#if !defined(TARGET_SDL2)
-    else if (key >= KSYM_KP_0 && key <= KSYM_KP_9)
-      sprintf(name_buffer, "keypad %c", '0' + (char)(key - KSYM_KP_0));
-#endif
     else if (key >= KSYM_FKEY_FIRST && key <= KSYM_FKEY_LAST)
       sprintf(name_buffer, "F%d", (int)(key - KSYM_FKEY_FIRST + 1));
     else if (key == KSYM_UNDEFINED)
@@ -1713,10 +1879,6 @@ static void translate_keyname(Key *keysym, char **x11name, char **name, int mode
       sprintf(name_buffer, "XK_%c", 'a' + (char)(key - KSYM_a));
     else if (key >= KSYM_0 && key <= KSYM_9)
       sprintf(name_buffer, "XK_%c", '0' + (char)(key - KSYM_0));
-#if !defined(TARGET_SDL2)
-    else if (key >= KSYM_KP_0 && key <= KSYM_KP_9)
-      sprintf(name_buffer, "XK_KP_%c", '0' + (char)(key - KSYM_KP_0));
-#endif
     else if (key >= KSYM_FKEY_FIRST && key <= KSYM_FKEY_LAST)
       sprintf(name_buffer, "XK_F%d", (int)(key - KSYM_FKEY_FIRST + 1));
     else if (key == KSYM_UNDEFINED)
@@ -1774,7 +1936,7 @@ static void translate_keyname(Key *keysym, char **x11name, char **name, int mode
     }
 
     if (key == KSYM_UNDEFINED)
-      Error(ERR_WARN, "getKeyFromKeyName(): not completely implemented");
+      Warn("getKeyFromKeyName(): not completely implemented");
 
     *keysym = key;
   }
@@ -1794,15 +1956,6 @@ static void translate_keyname(Key *keysym, char **x11name, char **name, int mode
       else if (c >= '0' && c <= '9')
        key = KSYM_0 + (Key)(c - '0');
     }
-#if !defined(TARGET_SDL2)
-    else if (strPrefix(name_ptr, "XK_KP_") && strlen(name_ptr) == 7)
-    {
-      char c = name_ptr[6];
-
-      if (c >= '0' && c <= '9')
-       key = KSYM_KP_0 + (Key)(c - '0');
-    }
-#endif
     else if (strPrefix(name_ptr, "XK_F") && strlen(name_ptr) <= 6)
     {
       char c1 = name_ptr[4];
@@ -2151,12 +2304,12 @@ static void dumpList(ListNode *node_first)
 
   while (node)
   {
-    printf("['%s' (%d)]\n", node->key,
-          ((struct ListNodeInfo *)node->content)->num_references);
+    Debug("internal:dumpList", "['%s' (%d)]", node->key,
+         ((struct ListNodeInfo *)node->content)->num_references);
     node = node->next;
   }
 
-  printf("[%d nodes]\n", getNumNodes(node_first));
+  Debug("internal:dumpList", "[%d nodes]", getNumNodes(node_first));
 }
 #endif
 
@@ -2636,8 +2789,19 @@ char *get_mapped_token(char *token)
   // !!! make this dynamically configurable (init.c:InitArtworkConfig) !!!
   static char *map_token_prefix[][2] =
   {
-    { "char_procent",          "char_percent"  },
-    { NULL,                                    }
+    { "char_procent",          "char_percent"                  },
+    { "bd_magic_wall_filling", "bd_magic_wall.filling"         },
+    { "bd_magic_wall_emptying",        "bd_magic_wall.emptying"        },
+    { "bd_butterfly_left",     "bd_butterfly.left"             },
+    { "bd_butterfly_right",    "bd_butterfly.right"            },
+    { "bd_butterfly_up",       "bd_butterfly.up"               },
+    { "bd_butterfly_down",     "bd_butterfly.down"             },
+    { "bd_firefly_left",       "bd_firefly.left"               },
+    { "bd_firefly_right",      "bd_firefly.right"              },
+    { "bd_firefly_up",         "bd_firefly.up"                 },
+    { "bd_firefly_down",       "bd_firefly.down"               },
+
+    { NULL,                                                    }
   };
   int i;
 
@@ -2735,273 +2899,6 @@ static char *get_special_base_token(struct ArtworkListInfo *artwork_info,
   return getStringCopyN(token, strlen(token) - len_suffix);
 }
 
-// This function checks if a string <s> of the format "string1, string2, ..."
-// exactly contains a string <s_contained>.
-
-static boolean string_has_parameter(char *s, char *s_contained)
-{
-  char *substring;
-
-  if (s == NULL || s_contained == NULL)
-    return FALSE;
-
-  if (strlen(s_contained) > strlen(s))
-    return FALSE;
-
-  if (strncmp(s, s_contained, strlen(s_contained)) == 0)
-  {
-    char next_char = s[strlen(s_contained)];
-
-    // check if next character is delimiter or whitespace
-    return (next_char == ',' || next_char == '\0' ||
-           next_char == ' ' || next_char == '\t' ? TRUE : FALSE);
-  }
-
-  // check if string contains another parameter string after a comma
-  substring = strchr(s, ',');
-  if (substring == NULL)       // string does not contain a comma
-    return FALSE;
-
-  // advance string pointer to next character after the comma
-  substring++;
-
-  // skip potential whitespaces after the comma
-  while (*substring == ' ' || *substring == '\t')
-    substring++;
-
-  return string_has_parameter(substring, s_contained);
-}
-
-static int get_anim_parameter_value(char *s)
-{
-  char *pattern_1 = "click:anim_";
-  char *pattern_2 = ".part_";
-  char *matching_char = NULL;
-  char *s_ptr = s;
-  int result = ANIM_EVENT_NONE;
-
-  matching_char = strstr(s_ptr, pattern_1);
-  if (matching_char == NULL)
-    return ANIM_EVENT_NONE;
-
-  s_ptr = matching_char + strlen(pattern_1);
-
-  // check for main animation number ("anim_X" or "anim_XX")
-  if (*s_ptr >= '0' && *s_ptr <= '9')
-  {
-    int gic_anim_nr = (*s_ptr++ - '0');
-
-    if (*s_ptr >= '0' && *s_ptr <= '9')
-      gic_anim_nr = 10 * gic_anim_nr + (*s_ptr++ - '0');
-
-    if (gic_anim_nr < 1 || gic_anim_nr > MAX_GLOBAL_ANIMS)
-      return ANIM_EVENT_NONE;
-
-    result |= gic_anim_nr << ANIM_EVENT_ANIM_BIT;
-  }
-  else
-  {
-    // invalid main animation number specified
-
-    return ANIM_EVENT_NONE;
-  }
-
-  // check for animation part number ("part_X" or "part_XX") (optional)
-  if (strPrefix(s_ptr, pattern_2))
-  {
-    s_ptr += strlen(pattern_2);
-
-    if (*s_ptr >= '0' && *s_ptr <= '9')
-    {
-      int gic_part_nr = (*s_ptr++ - '0');
-
-      if (*s_ptr >= '0' && *s_ptr <= '9')
-       gic_part_nr = 10 * gic_part_nr + (*s_ptr++ - '0');
-
-      if (gic_part_nr < 1 || gic_part_nr > MAX_GLOBAL_ANIM_PARTS)
-       return ANIM_EVENT_NONE;
-
-      result |= gic_part_nr << ANIM_EVENT_PART_BIT;
-    }
-    else
-    {
-      // invalid animation part number specified
-
-      return ANIM_EVENT_NONE;
-    }
-  }
-
-  // discard result if next character is neither delimiter nor whitespace
-  if (!(*s_ptr == ',' || *s_ptr == '\0' ||
-       *s_ptr == ' ' || *s_ptr == '\t'))
-    return ANIM_EVENT_NONE;
-
-  return result;
-}
-
-static int get_anim_action_parameter_value(char *token)
-{
-  int result = getImageIDFromToken(token);
-
-  if (result == -1)
-  {
-    char *gfx_token = getStringCat2("gfx.", token);
-
-    result = getImageIDFromToken(gfx_token);
-
-    checked_free(gfx_token);
-  }
-
-  if (result == -1)
-  {
-    Key key = getKeyFromX11KeyName(token);
-
-    if (key != KSYM_UNDEFINED)
-      result = -(int)key;
-  }
-
-  if (result == -1)
-    result = ANIM_EVENT_ACTION_NONE;
-
-  return result;
-}
-
-int get_parameter_value(char *value_raw, char *suffix, int type)
-{
-  char *value = getStringToLower(value_raw);
-  int result = 0;      // probably a save default value
-
-  if (strEqual(suffix, ".direction"))
-  {
-    result = (strEqual(value, "left")  ? MV_LEFT :
-             strEqual(value, "right") ? MV_RIGHT :
-             strEqual(value, "up")    ? MV_UP :
-             strEqual(value, "down")  ? MV_DOWN : MV_NONE);
-  }
-  else if (strEqual(suffix, ".position"))
-  {
-    result = (strEqual(value, "left")   ? POS_LEFT :
-             strEqual(value, "right")  ? POS_RIGHT :
-             strEqual(value, "top")    ? POS_TOP :
-             strEqual(value, "upper")  ? POS_UPPER :
-             strEqual(value, "middle") ? POS_MIDDLE :
-             strEqual(value, "lower")  ? POS_LOWER :
-             strEqual(value, "bottom") ? POS_BOTTOM :
-             strEqual(value, "any")    ? POS_ANY :
-             strEqual(value, "last")   ? POS_LAST : POS_UNDEFINED);
-  }
-  else if (strEqual(suffix, ".align"))
-  {
-    result = (strEqual(value, "left")   ? ALIGN_LEFT :
-             strEqual(value, "right")  ? ALIGN_RIGHT :
-             strEqual(value, "center") ? ALIGN_CENTER :
-             strEqual(value, "middle") ? ALIGN_CENTER : ALIGN_DEFAULT);
-  }
-  else if (strEqual(suffix, ".valign"))
-  {
-    result = (strEqual(value, "top")    ? VALIGN_TOP :
-             strEqual(value, "bottom") ? VALIGN_BOTTOM :
-             strEqual(value, "middle") ? VALIGN_MIDDLE :
-             strEqual(value, "center") ? VALIGN_MIDDLE : VALIGN_DEFAULT);
-  }
-  else if (strEqual(suffix, ".anim_mode"))
-  {
-    result = (string_has_parameter(value, "none")      ? ANIM_NONE :
-             string_has_parameter(value, "loop")       ? ANIM_LOOP :
-             string_has_parameter(value, "linear")     ? ANIM_LINEAR :
-             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, "ce_value")   ? ANIM_CE_VALUE :
-             string_has_parameter(value, "ce_score")   ? ANIM_CE_SCORE :
-             string_has_parameter(value, "ce_delay")   ? ANIM_CE_DELAY :
-             string_has_parameter(value, "horizontal") ? ANIM_HORIZONTAL :
-             string_has_parameter(value, "vertical")   ? ANIM_VERTICAL :
-             string_has_parameter(value, "centered")   ? ANIM_CENTERED :
-             string_has_parameter(value, "all")        ? ANIM_ALL :
-             ANIM_DEFAULT);
-
-    if (string_has_parameter(value, "once"))
-      result |= ANIM_ONCE;
-
-    if (string_has_parameter(value, "reverse"))
-      result |= ANIM_REVERSE;
-
-    if (string_has_parameter(value, "opaque_player"))
-      result |= ANIM_OPAQUE_PLAYER;
-
-    if (string_has_parameter(value, "static_panel"))
-      result |= ANIM_STATIC_PANEL;
-  }
-  else if (strEqual(suffix, ".init_event") ||
-          strEqual(suffix, ".anim_event"))
-  {
-    result = ANIM_EVENT_DEFAULT;
-
-    if (string_has_parameter(value, "any"))
-      result |= ANIM_EVENT_ANY;
-
-    if (string_has_parameter(value, "click"))
-      result |= ANIM_EVENT_SELF;
-
-    // add optional "click:anim_X" or "click:anim_X.part_X" parameter
-    result |= get_anim_parameter_value(value);
-  }
-  else if (strEqual(suffix, ".init_event_action") ||
-          strEqual(suffix, ".anim_event_action"))
-  {
-    result = get_anim_action_parameter_value(value_raw);
-  }
-  else if (strEqual(suffix, ".class"))
-  {
-    result = (strEqual(value, ARG_UNDEFINED) ? ARG_UNDEFINED_VALUE :
-             get_hash_from_key(value));
-  }
-  else if (strEqual(suffix, ".style"))
-  {
-    result = STYLE_DEFAULT;
-
-    if (string_has_parameter(value, "accurate_borders"))
-      result |= STYLE_ACCURATE_BORDERS;
-
-    if (string_has_parameter(value, "inner_corners"))
-      result |= STYLE_INNER_CORNERS;
-
-    if (string_has_parameter(value, "reverse"))
-      result |= STYLE_REVERSE;
-
-    if (string_has_parameter(value, "passthrough_clicks"))
-      result |= STYLE_PASSTHROUGH;
-
-    if (string_has_parameter(value, "multiple_actions"))
-      result |= STYLE_MULTIPLE_ACTIONS;
-  }
-  else if (strEqual(suffix, ".fade_mode"))
-  {
-    result = (string_has_parameter(value, "none")      ? FADE_MODE_NONE :
-             string_has_parameter(value, "fade")       ? FADE_MODE_FADE :
-             string_has_parameter(value, "crossfade")  ? FADE_MODE_CROSSFADE :
-             string_has_parameter(value, "melt")       ? FADE_MODE_MELT :
-             string_has_parameter(value, "curtain")    ? FADE_MODE_CURTAIN :
-             FADE_MODE_DEFAULT);
-  }
-  else if (strPrefix(suffix, ".font"))         // (may also be ".font_xyz")
-  {
-    result = gfx.get_font_from_token_function(value);
-  }
-  else         // generic parameter of type integer or boolean
-  {
-    result = (strEqual(value, ARG_UNDEFINED) ? ARG_UNDEFINED_VALUE :
-             type == TYPE_INTEGER ? get_integer_from_string(value) :
-             type == TYPE_BOOLEAN ? get_boolean_from_string(value) :
-             ARG_UNDEFINED_VALUE);
-  }
-
-  free(value);
-
-  return result;
-}
-
 static void FreeCustomArtworkList(struct ArtworkListInfo *,
                                  struct ListNodeInfo ***, int *);
 
@@ -3101,13 +2998,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);
@@ -3558,53 +3456,53 @@ static void LoadArtworkConfigFromFilename(struct ArtworkListInfo *artwork_info,
 
     if (options.debug && dynamic_tokens_found)
     {
-      Error(ERR_INFO_LINE, "-");
-      Error(ERR_INFO, "dynamic token(s) found in config file:");
-      Error(ERR_INFO, "- config file: '%s'", filename);
+      Debug("config", "---");
+      Debug("config", "dynamic token(s) found in config file:");
+      Debug("config", "- config file: '%s'", filename);
 
       for (list = setup_file_list; list != NULL; list = list->next)
       {
        char *value = getHashEntry(extra_file_hash, list->token);
 
        if (value != NULL && strEqual(value, known_token_value))
-         Error(ERR_INFO, "- dynamic token: '%s'", list->token);
+         Debug("config", "- dynamic token: '%s'", list->token);
       }
 
-      Error(ERR_INFO_LINE, "-");
+      Debug("config", "---");
     }
 
     if (unknown_tokens_found)
     {
-      Error(ERR_INFO_LINE, "-");
-      Error(ERR_INFO, "warning: unknown token(s) found in config file:");
-      Error(ERR_INFO, "- config file: '%s'", filename);
+      Warn("---");
+      Warn("unknown token(s) found in config file:");
+      Warn("- config file: '%s'", filename);
 
       for (list = setup_file_list; list != NULL; list = list->next)
       {
        char *value = getHashEntry(extra_file_hash, list->token);
 
        if (value != NULL && !strEqual(value, known_token_value))
-         Error(ERR_INFO, "- dynamic token: '%s'", list->token);
+         Warn("- dynamic token: '%s'", list->token);
       }
 
-      Error(ERR_INFO_LINE, "-");
+      Warn("---");
     }
 
     if (undefined_values_found)
     {
-      Error(ERR_INFO_LINE, "-");
-      Error(ERR_INFO, "warning: undefined values found in config file:");
-      Error(ERR_INFO, "- config file: '%s'", filename);
+      Warn("---");
+      Warn("undefined values found in config file:");
+      Warn("- config file: '%s'", filename);
 
       for (list = setup_file_list; list != NULL; list = list->next)
       {
        char *value = getHashEntry(empty_file_hash, list->token);
 
        if (value != NULL)
-         Error(ERR_INFO, "- undefined value for token: '%s'", list->token);
+         Warn("- undefined value for token: '%s'", list->token);
       }
 
-      Error(ERR_INFO_LINE, "-");
+      Warn("---");
     }
 
     freeSetupFileList(setup_file_list);
@@ -3710,7 +3608,7 @@ static void replaceArtworkListEntry(struct ArtworkListInfo *artwork_info,
 
   if (filename == NULL)
   {
-    Error(ERR_WARN, "cannot find artwork file '%s'", basename);
+    Warn("cannot find artwork file '%s'", basename);
 
     basename = file_list_entry->default_filename;
 
@@ -3718,14 +3616,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;
     }
@@ -3736,19 +3634,19 @@ static void replaceArtworkListEntry(struct ArtworkListInfo *artwork_info,
 
     file_list_entry->fallback_to_default = TRUE;
 
-    Error(ERR_WARN, "trying default artwork file '%s'", basename);
+    Warn("trying default artwork file '%s'", basename);
 
     filename = getCustomArtworkFilename(basename, artwork_info->type);
 
     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;
     }
@@ -3788,13 +3686,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;
   }
@@ -3878,14 +3776,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++)
   {
@@ -3894,8 +3792,8 @@ void OpenLogFiles(void)
     {
       program.log_file[i] = program.log_file_default[i];   // reset to default
 
-      Error(ERR_WARN, "cannot open file '%s' for writing: %s",
-           program.log_filename[i], strerror(errno));
+      Warn("cannot open file '%s' for writing: %s",
+          program.log_filename[i], strerror(errno));
     }
 
     // output should be unbuffered so it is not truncated in a crash
@@ -3945,7 +3843,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
@@ -3993,9 +3891,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];
@@ -4013,12 +3911,12 @@ void debug_print_timestamp(int counter_nr, char *message)
   counter[counter_nr][1] = counter[counter_nr][0];
 
   if (message)
-    Error(ERR_DEBUG, "%s%s%s %.3f %s",
-          debug_print_timestamp_get_padding(counter_nr * indent_size),
-          message,
-          debug_print_timestamp_get_padding(padding_size - strlen(message)),
-          timestamp_interval / 1000,
-          unit);
+    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)),
+         timestamp_interval / 1000,
+         unit);
 }
 
 #if 0