moved functions to parse GIC parameters from 'libgame' to main source tree
[rocksndiamonds.git] / src / libgame / misc.c
index eda87185ec74990bc6ac9efbee0b648a93d82436..fedc30487786287ea3e99dc75cb4aab57243e1af 100644 (file)
@@ -1,15 +1,13 @@
-/***********************************************************
-* Artsoft Retro-Game Library                               *
-*----------------------------------------------------------*
-* (c) 1994-2006 Artsoft Entertainment                      *
-*               Holger Schemel                             *
-*               Detmolder Strasse 189                      *
-*               33604 Bielefeld                            *
-*               Germany                                    *
-*               e-mail: info@artsoft.org                   *
-*----------------------------------------------------------*
-* misc.c                                                   *
-***********************************************************/
+// ============================================================================
+// Artsoft Retro-Game Library
+// ----------------------------------------------------------------------------
+// (c) 1995-2014 by Artsoft Entertainment
+//                         Holger Schemel
+//                 info@artsoft.org
+//                 http://www.artsoft.org/
+// ----------------------------------------------------------------------------
+// misc.c
+// ============================================================================
 
 #include <time.h>
 #include <sys/time.h>
 #include "image.h"
 
 
-/* ========================================================================= */
-/* some generic helper functions                                             */
-/* ========================================================================= */
+// ============================================================================
+// some generic helper functions
+// ============================================================================
+
+// ----------------------------------------------------------------------------
+// logging functions
+// ----------------------------------------------------------------------------
+
+#define DUPLICATE_LOG_OUT_TO_STDOUT            TRUE
+#define DUPLICATE_LOG_ERR_TO_STDERR            TRUE
 
-/* ------------------------------------------------------------------------- */
-/* platform independent wrappers for printf() et al. (newline aware)         */
-/* ------------------------------------------------------------------------- */
 
 #if defined(PLATFORM_ANDROID)
 static int android_log_prio = ANDROID_LOG_INFO;
-#endif
+static char *android_log_buffer = NULL;
 
-#if 0
-static void vfPrintLog(FILE *stream, char *format, va_list ap)
+static void append_to_android_log_buffer(char *format, va_list ap)
 {
+  char text_new[MAX_OUTPUT_LINESIZE];
+
+  // print text to temporary string
+  vsnprintf(text_new, MAX_OUTPUT_LINESIZE, format, ap);
+
+  if (android_log_buffer == NULL)
+  {
+    android_log_buffer = getStringCopy(text_new);
+  }
+  else
+  {
+    char *android_log_buffer_old = android_log_buffer;
+
+    // append new text to existing text
+    android_log_buffer = getStringCat2(android_log_buffer, text_new);
+
+    checked_free(android_log_buffer_old);
+  }
 }
 
-static void vfPrintLog(FILE *stream, char *format, va_list ap)
+static void vprintf_log_nonewline(char *format, va_list ap)
 {
+  // add log output to buffer until text with newline is printed
+  append_to_android_log_buffer(format, ap);
 }
 
-static void fPrintLog(FILE *stream, char *format, va_list ap)
+static void vprintf_log(char *format, va_list ap)
 {
+  // add log output to buffer
+  append_to_android_log_buffer(format, ap);
+
+  // __android_log_vprint(android_log_prio, program.program_title, format, ap);
+  __android_log_write(android_log_prio, program.program_title,
+                     android_log_buffer);
+
+  checked_free(android_log_buffer);
+  android_log_buffer = NULL;
 }
 
-static void fPrintLog(FILE *stream, char *format, va_list ap)
+#else
+
+static void vprintf_log_nonewline(char *format, va_list ap)
 {
-}
+  FILE *file = program.log_file[LOG_ERR_ID];
+
+#if DUPLICATE_LOG_ERR_TO_STDERR
+  if (file != program.log_file_default[LOG_ERR_ID])
+  {
+    va_list ap2;
+    va_copy(ap2, ap);
+
+    vfprintf(program.log_file_default[LOG_ERR_ID], format, ap2);
+
+    va_end(ap2);
+  }
 #endif
 
-static void vfprintf_newline(FILE *stream, char *format, va_list ap)
+  vfprintf(file, format, ap);
+}
+
+static void vprintf_log(char *format, va_list ap)
 {
-#if defined(PLATFORM_ANDROID)
-  __android_log_vprint(android_log_prio, program.program_title, format, ap);
-#else
+  FILE *file = program.log_file[LOG_ERR_ID];
   char *newline = STRING_NEWLINE;
 
-  vfprintf(stream, format, ap);
-  fprintf(stream, "%s", newline);
+#if DUPLICATE_LOG_ERR_TO_STDERR
+  if (file != program.log_file_default[LOG_ERR_ID])
+  {
+    va_list ap2;
+    va_copy(ap2, ap);
+
+    vfprintf(program.log_file_default[LOG_ERR_ID], format, ap2);
+    fprintf(program.log_file_default[LOG_ERR_ID], "%s", newline);
+
+    va_end(ap2);
+  }
 #endif
+
+  vfprintf(file, format, ap);
+  fprintf(file, "%s", newline);
 }
+#endif
 
-static void fprintf_newline(FILE *stream, char *format, ...)
+static void printf_log_nonewline(char *format, ...)
 {
   va_list ap;
 
   va_start(ap, format);
-  vfprintf_newline(stream, format, ap);
+  vprintf_log_nonewline(format, ap);
   va_end(ap);
 }
 
-void fprintf_line(FILE *stream, char *line_chars, int line_length)
+static void printf_log(char *format, ...)
+{
+  va_list ap;
+
+  va_start(ap, format);
+  vprintf_log(format, ap);
+  va_end(ap);
+}
+
+static void printf_log_line(char *line_chars, int line_length)
+{
+  int i;
+
+  for (i = 0; i < line_length; i++)
+    printf_log_nonewline("%s", line_chars);
+
+  printf_log("");
+}
+
+
+// ----------------------------------------------------------------------------
+// platform independent wrappers for printf() et al.
+// ----------------------------------------------------------------------------
+
+void fprintf_line(FILE *file, char *line_chars, int line_length)
 {
   int i;
 
   for (i = 0; i < line_length; i++)
-    fprintf(stream, "%s", line_chars);
+    fprintf(file, "%s", line_chars);
+
+  fprintf(file, "\n");
+}
 
-  fprintf_newline(stream, "");
+void fprintf_line_with_prefix(FILE *file, char *prefix, char *line_chars,
+                             int line_length)
+{
+  fprintf(file, "%s", prefix);
+  fprintf_line(file, line_chars, line_length);
 }
 
 void printf_line(char *line_chars, int line_length)
@@ -103,21 +191,75 @@ void printf_line(char *line_chars, int line_length)
 
 void printf_line_with_prefix(char *prefix, char *line_chars, int line_length)
 {
-  fprintf(stdout, "%s", prefix);
-  fprintf_line(stdout, line_chars, line_length);
+  fprintf_line_with_prefix(stdout, prefix, line_chars, line_length);
+}
+
+static void vPrint(char *format, va_list ap)
+{
+  FILE *file = program.log_file[LOG_OUT_ID];
+
+#if DUPLICATE_LOG_OUT_TO_STDOUT
+  if (file != program.log_file_default[LOG_OUT_ID])
+  {
+    va_list ap2;
+    va_copy(ap2, ap);
+
+    vfprintf(program.log_file_default[LOG_OUT_ID], format, ap2);
+
+    va_end(ap2);
+  }
+#endif
+
+  vfprintf(file, format, ap);
+}
+
+void Print(char *format, ...)
+{
+  va_list ap;
+
+  va_start(ap, format);
+  vPrint(format, ap);
+  va_end(ap);
 }
 
+void PrintNoLog(char *format, ...)
+{
+  FILE *file = program.log_file_default[LOG_OUT_ID];
+  va_list ap;
+
+  va_start(ap, format);
+  vfprintf(file, format, ap);
+  va_end(ap);
 
-/* ------------------------------------------------------------------------- */
-/* string functions                                                          */
-/* ------------------------------------------------------------------------- */
+  fflush(file);
+}
+
+void PrintLine(char *line_chars, int line_length)
+{
+  int i;
+
+  for (i = 0; i < line_length; i++)
+    Print(line_chars);
+
+  Print("\n");
+}
+
+void PrintLineWithPrefix(char *prefix, char *line_chars, int line_length)
+{
+  Print(prefix);
+  PrintLine(line_chars, line_length);
+}
+
+
+// ----------------------------------------------------------------------------
+// string functions
+// ----------------------------------------------------------------------------
 
 /* int2str() returns a number converted to a string;
    the used memory is static, but will be overwritten by later calls,
    so if you want to save the result, copy it to a private string buffer;
    there can be 10 local calls of int2str() without buffering the result --
-   the 11th call will then destroy the result from the first call and so on.
-*/
+   the 11th call will then destroy the result from the first call and so on. */
 
 char *int2str(int number, int size)
 {
@@ -143,9 +285,9 @@ char *int2str(int number, int size)
 }
 
 
-/* something similar to "int2str()" above, but allocates its own memory
-   and has a different interface; we cannot use "itoa()", because this
-   seems to be already defined when cross-compiling to the win32 target */
+// something similar to "int2str()" above, but allocates its own memory
+// and has a different interface; we cannot use "itoa()", because this
+// seems to be already defined when cross-compiling to the win32 target
 
 char *i_to_a(unsigned int i)
 {
@@ -153,7 +295,7 @@ char *i_to_a(unsigned int i)
 
   checked_free(a);
 
-  if (i > 2147483647)  /* yes, this is a kludge */
+  if (i > 2147483647)  // yes, this is a kludge
     i = 2147483647;
 
   a = checked_malloc(10 + 1);
@@ -164,8 +306,8 @@ char *i_to_a(unsigned int i)
 }
 
 
-/* calculate base-2 logarithm of argument (rounded down to integer;
-   this function returns the number of the highest bit set in argument) */
+// calculate base-2 logarithm of argument (rounded down to integer;
+// this function returns the number of the highest bit set in argument)
 
 int log_2(unsigned int x)
 {
@@ -173,7 +315,7 @@ int log_2(unsigned int x)
 
   while ((1 << e) < x)
   {
-    x -= (1 << e);     /* for rounding down (rounding up: remove this line) */
+    x -= (1 << e);     // for rounding down (rounding up: remove this line)
     e++;
   }
 
@@ -186,199 +328,96 @@ boolean getTokenValueFromString(char *string, char **token, char **value)
 }
 
 
-/* ------------------------------------------------------------------------- */
-/* counter functions                                                         */
-/* ------------------------------------------------------------------------- */
-
-#if defined(PLATFORM_MSDOS)
-volatile unsigned int counter = 0;
+// ----------------------------------------------------------------------------
+// counter functions
+// ----------------------------------------------------------------------------
 
-void increment_counter()
-{
-  counter++;
-}
-
-END_OF_FUNCTION(increment_counter);
-#endif
-
-
-/* maximal allowed length of a command line option */
+// maximal allowed length of a command line option
 #define MAX_OPTION_LEN         256
 
-#if 1
-
-#if defined(TARGET_SDL)
-static unsigned int getCurrentMS()
+static unsigned int getCurrentMS(void)
 {
   return SDL_GetTicks();
 }
 
-#else /* !TARGET_SDL */
-
-#if defined(PLATFORM_UNIX)
-static unsigned int getCurrentMS()
-{
-  struct timeval current_time;
-
-  gettimeofday(&current_time, NULL);
-
-  return current_time.tv_sec * 1000 + current_time.tv_usec / 1000;
-}
-#endif /* PLATFORM_UNIX */
-#endif /* !TARGET_SDL */
-
 static unsigned int mainCounter(int mode)
 {
   static unsigned int base_ms = 0;
   unsigned int current_ms;
 
-  /* get current system milliseconds */
+  // get current system milliseconds
   current_ms = getCurrentMS();
 
-  /* reset base timestamp in case of counter reset or wrap-around */
+  // reset base timestamp in case of counter reset or wrap-around
   if (mode == INIT_COUNTER || current_ms < base_ms)
     base_ms = current_ms;
 
-  /* return milliseconds since last counter reset */
+  // return milliseconds since last counter reset
   return current_ms - base_ms;
 }
 
-#else
-
-#if defined(TARGET_SDL)
-static unsigned int mainCounter(int mode)
-{
-  static unsigned int base_ms = 0;
-  unsigned int current_ms;
-  unsigned int counter_ms;
-
-  current_ms = SDL_GetTicks();
-
-  /* reset base time in case of counter initializing or wrap-around */
-  if (mode == INIT_COUNTER || current_ms < base_ms)
-    base_ms = current_ms;
-
-  counter_ms = current_ms - base_ms;
-
-  return counter_ms;           /* return milliseconds since last init */
-}
-
-#else /* !TARGET_SDL */
-
-#if defined(PLATFORM_UNIX)
-static unsigned int mainCounter(int mode)
+void InitCounter()             // set counter back to zero
 {
-  static struct timeval base_time = { 0, 0 };
-  struct timeval current_time;
-  unsigned int counter_ms;
-
-  gettimeofday(&current_time, NULL);
-
-  /* reset base time in case of counter initializing or wrap-around */
-  if (mode == INIT_COUNTER || current_time.tv_sec < base_time.tv_sec)
-    base_time = current_time;
-
-  counter_ms = (current_time.tv_sec  - base_time.tv_sec)  * 1000
-             + (current_time.tv_usec - base_time.tv_usec) / 1000;
-
-  return counter_ms;           /* return milliseconds since last init */
-}
-#endif /* PLATFORM_UNIX */
-#endif /* !TARGET_SDL */
-
-#endif
-
-void InitCounter()             /* set counter back to zero */
-{
-#if !defined(PLATFORM_MSDOS)
   mainCounter(INIT_COUNTER);
-#else
-  LOCK_VARIABLE(counter);
-  LOCK_FUNCTION(increment_counter);
-  install_int_ex(increment_counter, BPS_TO_TIMER(100));
-#endif
 }
 
-unsigned int Counter() /* get milliseconds since last call of InitCounter() */
+unsigned int Counter() // get milliseconds since last call of InitCounter()
 {
-#if !defined(PLATFORM_MSDOS)
   return mainCounter(READ_COUNTER);
-#else
-  return (counter * 10);
-#endif
 }
 
 static void sleep_milliseconds(unsigned int milliseconds_delay)
 {
-  boolean do_busy_waiting = (milliseconds_delay < 5 ? TRUE : FALSE);
-
-  if (do_busy_waiting)
-  {
-    /* we want to wait only a few ms -- if we assume that we have a
-       kernel timer resolution of 10 ms, we would wait far to long;
-       therefore it's better to do a short interval of busy waiting
-       to get our sleeping time more accurate */
-
-    unsigned int base_counter = Counter(), actual_counter = Counter();
-
-    while (actual_counter < base_counter + milliseconds_delay &&
-          actual_counter >= base_counter)
-      actual_counter = Counter();
-  }
-  else
-  {
-#if defined(TARGET_SDL)
-    SDL_Delay(milliseconds_delay);
-#elif defined(TARGET_ALLEGRO)
-    rest(milliseconds_delay);
-#else
-    struct timeval delay;
-
-    delay.tv_sec  = milliseconds_delay / 1000;
-    delay.tv_usec = 1000 * (milliseconds_delay % 1000);
-
-    if (select(0, NULL, NULL, NULL, &delay) != 0)
-      Error(ERR_WARN, "sleep_milliseconds(): select() failed");
-#endif
-  }
+  SDL_Delay(milliseconds_delay);
 }
 
-void Delay(unsigned int delay) /* Sleep specified number of milliseconds */
+void Delay(unsigned int delay) // Sleep specified number of milliseconds
 {
   sleep_milliseconds(delay);
 }
 
-boolean FrameReached(unsigned int *frame_counter_var,
-                    unsigned int frame_delay)
+boolean DelayReachedExt(unsigned int *counter_var, unsigned int delay,
+                       unsigned int actual_counter)
 {
-  unsigned int actual_frame_counter = FrameCounter;
-
-  if (actual_frame_counter >= *frame_counter_var &&
-      actual_frame_counter < *frame_counter_var + frame_delay)
+  if (actual_counter >= *counter_var &&
+      actual_counter < *counter_var + delay)
     return FALSE;
 
-  *frame_counter_var = actual_frame_counter;
+  *counter_var = actual_counter;
 
   return TRUE;
 }
 
-boolean DelayReached(unsigned int *counter_var,
-                    unsigned int delay)
+boolean FrameReached(unsigned int *frame_counter_var, unsigned int frame_delay)
 {
-  unsigned int actual_counter = Counter();
+  return DelayReachedExt(frame_counter_var, frame_delay, FrameCounter);
+}
 
-  if (actual_counter >= *counter_var &&
-      actual_counter < *counter_var + delay)
-    return FALSE;
+boolean DelayReached(unsigned int *counter_var, unsigned int delay)
+{
+  return DelayReachedExt(counter_var, delay, Counter());
+}
 
-  *counter_var = actual_counter;
+void ResetDelayCounterExt(unsigned int *counter_var,
+                         unsigned int actual_counter)
+{
+  DelayReachedExt(counter_var, 0, actual_counter);
+}
 
-  return TRUE;
+void ResetFrameCounter(unsigned int *frame_counter_var)
+{
+  FrameReached(frame_counter_var, 0);
 }
 
-void WaitUntilDelayReached(unsigned int *counter_var, unsigned int delay)
+void ResetDelayCounter(unsigned int *counter_var)
+{
+  DelayReached(counter_var, 0);
+}
+
+int WaitUntilDelayReached(unsigned int *counter_var, unsigned int delay)
 {
   unsigned int actual_counter;
+  int skip_frames = 0;
 
   while (1)
   {
@@ -391,23 +430,71 @@ void WaitUntilDelayReached(unsigned int *counter_var, unsigned int delay)
       break;
   }
 
+  if (*counter_var != 0 &&
+      delay != 0 &&
+      actual_counter >= *counter_var + delay)
+  {
+    int lag = actual_counter - (*counter_var + delay);
+    int delay2 = (delay + 1) / 2;
+
+    if (lag >= delay2)
+      skip_frames = (lag + delay2) / delay;
+  }
+
   *counter_var = actual_counter;
+
+  return skip_frames;
+}
+
+void SkipUntilDelayReached(unsigned int *counter_var, unsigned int delay,
+                          int *loop_var, int last_loop_value)
+{
+  int skip_frames = WaitUntilDelayReached(counter_var, 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");
+#endif
+#endif
+
+  if (skip_frames == 0)
+    return;
+
+  // when skipping frames, make sure to never skip the last frame, as
+  // this may be needed for animations to reach a defined end state;
+  // furthermore, we assume that this function is called at the end
+  // of a "for" loop, which continues by incrementing the loop variable
+  // by one before checking the loop condition again; therefore we have
+  // to check against the last loop value minus one here
+
+  last_loop_value--;
+
+  if (*loop_var < last_loop_value)     // never skip the last frame
+  {
+    *loop_var += skip_frames;
+
+    if (*loop_var > last_loop_value)   // never skip the last frame
+      *loop_var = last_loop_value;
+  }
 }
 
 
-/* ------------------------------------------------------------------------- */
-/* random generator functions                                                */
-/* ------------------------------------------------------------------------- */
+// ----------------------------------------------------------------------------
+// random generator functions
+// ----------------------------------------------------------------------------
 
 unsigned int init_random_number(int nr, int seed)
 {
   if (seed == NEW_RANDOMIZE)
   {
-    /* default random seed */
+    // default random seed
     seed = (int)time(NULL);                    // seconds since the epoch
 
 #if !defined(PLATFORM_WIN32)
-    /* add some more randomness */
+    // add some more randomness
     struct timeval current_time;
 
     gettimeofday(&current_time, NULL);
@@ -415,15 +502,11 @@ unsigned int init_random_number(int nr, int seed)
     seed += (int)current_time.tv_usec;         // microseconds since the epoch
 #endif
 
-#if defined(TARGET_SDL)
-    /* add some more randomness */
+    // add some more randomness
     seed += (int)SDL_GetTicks();               // milliseconds since SDL init
-#endif
 
-#if 1
-    /* add some more randomness */
+    // add some more randomness
     seed += GetSimpleRandom(1000000);
-#endif
   }
 
   srandom_linux_libc(nr, (unsigned int) seed);
@@ -437,28 +520,28 @@ unsigned int get_random_number(int nr, int max)
 }
 
 
-/* ------------------------------------------------------------------------- */
-/* system info functions                                                     */
-/* ------------------------------------------------------------------------- */
+// ----------------------------------------------------------------------------
+// system info functions
+// ----------------------------------------------------------------------------
 
-#if !defined(PLATFORM_MSDOS) && !defined(PLATFORM_ANDROID)
+#if !defined(PLATFORM_ANDROID)
 static char *get_corrected_real_name(char *real_name)
 {
   char *real_name_new = checked_malloc(MAX_USERNAME_LEN + 1);
   char *from_ptr = real_name;
   char *to_ptr   = real_name_new;
 
-  /* copy the name string, but not more than MAX_USERNAME_LEN characters */
+  // copy the name string, but not more than MAX_USERNAME_LEN characters
   while (*from_ptr && (int)(to_ptr - real_name_new) < MAX_USERNAME_LEN - 1)
   {
-    /* the name field read from "passwd" file may also contain additional
-       user information, separated by commas, which will be removed here */
+    // the name field read from "passwd" file may also contain additional
+    // user information, separated by commas, which will be removed here
     if (*from_ptr == ',')
       break;
 
-    /* the user's real name may contain 'ß' characters (german sharp s),
-       which have no equivalent in upper case letters (used by our fonts) */
-    if (*from_ptr == 'ß')
+    // the user's real name may contain 'german sharp s' characters,
+    // which have no equivalent in upper case letters (used by our fonts)
+    if (*from_ptr == CHAR_BYTE_SHARP_S)
     {
       from_ptr++;
       *to_ptr++ = 's';
@@ -474,7 +557,7 @@ static char *get_corrected_real_name(char *real_name)
 }
 #endif
 
-char *getLoginName()
+char *getLoginName(void)
 {
   static char *login_name = NULL;
 
@@ -487,7 +570,7 @@ char *getLoginName()
     if (GetUserName(login_name, &buffer_size) == 0)
       strcpy(login_name, ANONYMOUS_NAME);
   }
-#else
+#elif defined(PLATFORM_UNIX) && !defined(PLATFORM_ANDROID)
   if (login_name == NULL)
   {
     struct passwd *pwd;
@@ -497,12 +580,14 @@ char *getLoginName()
     else
       login_name = getStringCopy(pwd->pw_name);
   }
+#else
+  login_name = ANONYMOUS_NAME;
 #endif
 
   return login_name;
 }
 
-char *getRealName()
+char *getRealName(void)
 {
   static char *real_name = NULL;
 
@@ -538,22 +623,22 @@ time_t getFileTimestampEpochSeconds(char *filename)
 {
   struct stat file_status;
 
-  if (stat(filename, &file_status) != 0)       /* cannot stat file */
+  if (stat(filename, &file_status) != 0)       // cannot stat file
     return 0;
 
   return file_status.st_mtime;
 }
 
 
-/* ------------------------------------------------------------------------- */
-/* path manipulation functions                                               */
-/* ------------------------------------------------------------------------- */
+// ----------------------------------------------------------------------------
+// path manipulation functions
+// ----------------------------------------------------------------------------
 
 static char *getLastPathSeparatorPtr(char *filename)
 {
   char *last_separator = strrchr(filename, CHAR_PATH_SEPARATOR_UNIX);
 
-  if (last_separator == NULL)  /* also try DOS/Windows variant */
+  if (last_separator == NULL)  // also try DOS/Windows variant
     last_separator = strrchr(filename, CHAR_PATH_SEPARATOR_DOS);
 
   return last_separator;
@@ -564,9 +649,9 @@ char *getBaseNamePtr(char *filename)
   char *last_separator = getLastPathSeparatorPtr(filename);
 
   if (last_separator != NULL)
-    return last_separator + 1; /* separator found: strip base path */
+    return last_separator + 1; // separator found: strip base path
   else
-    return filename;           /* no separator found: filename has no path */
+    return filename;           // no separator found: filename has no path
 }
 
 char *getBaseName(char *filename)
@@ -574,56 +659,52 @@ char *getBaseName(char *filename)
   return getStringCopy(getBaseNamePtr(filename));
 }
 
-char *getBasePath(char *filename)
+char *getBaseNameNoSuffix(char *filename)
 {
-  char *basepath = getStringCopy(filename);
-  char *last_separator = getLastPathSeparatorPtr(basepath);
+  char *basename = getStringCopy(getBaseNamePtr(filename));
 
-  if (last_separator != NULL)
-    *last_separator = '\0';    /* separator found: strip basename */
-  else
-    basepath = ".";            /* no separator found: use current path */
+  // remove trailing suffix (separated by dot or hyphen)
+  if (basename[0] != '.' && basename[0] != '-')
+  {
+    if (strchr(basename, '.') != NULL)
+      *strchr(basename, '.') = '\0';
 
-  return basepath;
+    if (strchr(basename, '-') != NULL)
+      *strchr(basename, '-') = '\0';
+  }
+
+  return basename;
 }
 
-static char *getProgramMainDataPath()
+char *getBasePath(char *filename)
 {
-  char *main_data_path = getStringCopy(program.command_basepath);
-
-#if defined(PLATFORM_MACOSX)
-  static char *main_data_binary_subdir = NULL;
+  char *basepath = getStringCopy(filename);
+  char *last_separator = getLastPathSeparatorPtr(basepath);
 
-  if (main_data_binary_subdir == NULL)
+  // if no separator was found, use current directory
+  if (last_separator == NULL)
   {
-    main_data_binary_subdir = checked_malloc(strlen(program.program_title) + 1 +
-                                            strlen("app") + 1 +
-                                            strlen(MAC_APP_BINARY_SUBDIR) + 1);
+    free(basepath);
 
-    sprintf(main_data_binary_subdir, "%s.app/%s",
-           program.program_title, MAC_APP_BINARY_SUBDIR);
+    return getStringCopy(".");
   }
 
-  // cut relative path to Mac OS X application binary directory from path
-  if (strSuffix(main_data_path, main_data_binary_subdir))
-    main_data_path[strlen(main_data_path) -
-                  strlen(main_data_binary_subdir)] = '\0';
+  // separator found: strip basename
+  *last_separator = '\0';
 
-  // cut trailing path separator from path (but not if path is root directory)
-  if (strSuffix(main_data_path, "/") && !strEqual(main_data_path, "/"))
-    main_data_path[strlen(main_data_path) - 1] = '\0';
-#endif
-
-  return main_data_path;
+  return basepath;
 }
 
 
-/* ------------------------------------------------------------------------- */
-/* various string functions                                                  */
-/* ------------------------------------------------------------------------- */
+// ----------------------------------------------------------------------------
+// various string functions
+// ----------------------------------------------------------------------------
 
 char *getStringCat2WithSeparator(char *s1, char *s2, char *sep)
 {
+  if (s1 == NULL || s2 == NULL || sep == NULL)
+    return NULL;
+
   char *complete_string = checked_malloc(strlen(s1) + strlen(sep) +
                                         strlen(s2) + 1);
 
@@ -634,6 +715,9 @@ char *getStringCat2WithSeparator(char *s1, char *s2, char *sep)
 
 char *getStringCat3WithSeparator(char *s1, char *s2, char *s3, char *sep)
 {
+  if (s1 == NULL || s2 == NULL || s3 == NULL || sep == NULL)
+    return NULL;
+
   char *complete_string = checked_malloc(strlen(s1) + strlen(sep) +
                                         strlen(s2) + strlen(sep) +
                                         strlen(s3) + 1);
@@ -656,9 +740,11 @@ char *getStringCat3(char *s1, char *s2, char *s3)
 char *getPath2(char *path1, char *path2)
 {
 #if defined(PLATFORM_ANDROID)
-  // workaround for reading from APK assets directory -- skip leading "./"
+  // workaround for reading from assets directory -- skip "." subdirs in path
   if (strEqual(path1, "."))
     return getStringCopy(path2);
+  else if (strEqual(path2, "."))
+    return getStringCopy(path1);
 #endif
 
   return getStringCat2WithSeparator(path1, path2, STRING_PATH_SEPARATOR);
@@ -667,15 +753,42 @@ char *getPath2(char *path1, char *path2)
 char *getPath3(char *path1, char *path2, char *path3)
 {
 #if defined(PLATFORM_ANDROID)
-  // workaround for reading from APK assets directory -- skip leading "./"
+  // workaround for reading from assets directory -- skip "." subdirs in path
   if (strEqual(path1, "."))
     return getStringCat2WithSeparator(path2, path3, STRING_PATH_SEPARATOR);
+  else if (strEqual(path2, "."))
+    return getStringCat2WithSeparator(path1, path3, STRING_PATH_SEPARATOR);
+  else if (strEqual(path3, "."))
+    return getStringCat2WithSeparator(path1, path2, STRING_PATH_SEPARATOR);
 #endif
 
   return getStringCat3WithSeparator(path1, path2, path3, STRING_PATH_SEPARATOR);
 }
 
-char *getStringCopy(char *s)
+static char *getPngOrPcxIfNotExists(char *filename)
+{
+  // switch from PNG to PCX file and vice versa, if file does not exist
+  // (backwards compatibility with PCX files used in previous versions)
+
+  if (!fileExists(filename) && strSuffix(filename, ".png"))
+    strcpy(&filename[strlen(filename) - 3], "pcx");
+  else if (!fileExists(filename) && strSuffix(filename, ".pcx"))
+    strcpy(&filename[strlen(filename) - 3], "png");
+
+  return filename;
+}
+
+char *getImg2(char *path1, char *path2)
+{
+  return getPngOrPcxIfNotExists(getPath2(path1, path2));
+}
+
+char *getImg3(char *path1, char *path2, char *path3)
+{
+  return getPngOrPcxIfNotExists(getPath3(path1, path2, path3));
+}
+
+char *getStringCopy(const char *s)
 {
   char *s_copy;
 
@@ -688,7 +801,7 @@ char *getStringCopy(char *s)
   return s_copy;
 }
 
-char *getStringCopyN(char *s, int n)
+char *getStringCopyN(const char *s, int n)
 {
   char *s_copy;
   int s_len = MAX(0, n);
@@ -703,7 +816,18 @@ char *getStringCopyN(char *s, int n)
   return s_copy;
 }
 
-char *getStringToLower(char *s)
+char *getStringCopyNStatic(const char *s, int n)
+{
+  static char *s_copy = NULL;
+
+  checked_free(s_copy);
+
+  s_copy = getStringCopyN(s, n);
+
+  return s_copy;
+}
+
+char *getStringToLower(const char *s)
 {
   char *s_copy = checked_malloc(strlen(s) + 1);
   char *s_ptr = s_copy;
@@ -776,45 +900,24 @@ boolean strSuffixLower(char *s, char *suffix)
 }
 
 
-/* ------------------------------------------------------------------------- */
-/* command line option handling functions                                    */
-/* ------------------------------------------------------------------------- */
+// ----------------------------------------------------------------------------
+// command line option handling functions
+// ----------------------------------------------------------------------------
 
-void GetOptions(char *argv[], void (*print_usage_function)(void))
+void GetOptions(int argc, char *argv[],
+               void (*print_usage_function)(void),
+               void (*print_version_function)(void))
 {
-  char *ro_base_path = RO_BASE_PATH;
-  char *rw_base_path = RW_BASE_PATH;
-  char **options_left = &argv[1];
+  char *ro_base_path = getProgramMainDataPath(argv[0], RO_BASE_PATH);
+  char *rw_base_path = getProgramMainDataPath(argv[0], RW_BASE_PATH);
+  char **argvplus = checked_calloc((argc + 1) * sizeof(char **));
+  char **options_left = &argvplus[1];
 
-#if 1
-  /* if the program is configured to start from current directory (default),
-     determine program package directory from program binary (some versions
-     of KDE/Konqueror and Mac OS X (especially "Mavericks") apparently do not
-     set the current working directory to the program package directory) */
-
-  if (strEqual(ro_base_path, "."))
-    ro_base_path = getProgramMainDataPath();
-  if (strEqual(rw_base_path, "."))
-    rw_base_path = getProgramMainDataPath();
-#else
+  // replace original "argv" with null-terminated array of string pointers
+  while (argc--)
+    argvplus[argc] = argv[argc];
 
-#if !defined(PLATFORM_MACOSX)
-  /* if the program is configured to start from current directory (default),
-     determine program package directory (KDE/Konqueror does not do this by
-     itself and fails otherwise); on Mac OS X, the program binary is stored
-     in an application package directory -- do not try to use this directory
-     as the program data directory (Mac OS X handles this correctly anyway) */
-
-  if (strEqual(ro_base_path, "."))
-    ro_base_path = program.command_basepath;
-  if (strEqual(rw_base_path, "."))
-    rw_base_path = program.command_basepath;
-#endif
-
-#endif
-
-  /* initialize global program options */
-  options.display_name = NULL;
+  // initialize global program options
   options.server_host = NULL;
   options.server_port = 0;
 
@@ -825,23 +928,30 @@ void GetOptions(char *argv[], void (*print_usage_function)(void))
   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.execute_command = NULL;
   options.special_flags = NULL;
 
+  options.mytapes = FALSE;
   options.serveronly = FALSE;
   options.network = FALSE;
   options.verbose = FALSE;
   options.debug = FALSE;
-  options.debug_x11_sync = FALSE;
 
 #if 1
   options.verbose = TRUE;
 #else
 #if !defined(PLATFORM_UNIX)
-  if (*options_left == NULL)   /* no options given -- enable verbose mode */
+  if (*options_left == NULL)   // no options given -- enable verbose mode
     options.verbose = TRUE;
 #endif
+#endif
+
+#if DEBUG
+#if defined(PLATFORM_ANDROID)
+  options.debug = TRUE;
+#endif
 #endif
 
   while (*options_left)
@@ -855,61 +965,55 @@ void GetOptions(char *argv[], void (*print_usage_function)(void))
     if (option_len >= MAX_OPTION_LEN)
       Error(ERR_EXIT_HELP, "unrecognized option '%s'", option);
 
-    strcpy(option_str, option);                        /* copy argument into buffer */
+    strcpy(option_str, option);                        // copy argument into buffer
     option = option_str;
 
-    if (strEqual(option, "--"))                        /* stop scanning arguments */
+    if (strEqual(option, "--"))                        // stop scanning arguments
       break;
 
-    if (strPrefix(option, "--"))               /* treat '--' like '-' */
+    if (strPrefix(option, "--"))               // treat '--' like '-'
       option++;
 
     option_arg = strchr(option, '=');
-    if (option_arg == NULL)                    /* no '=' in option */
+    if (option_arg == NULL)                    // no '=' in option
       option_arg = next_option;
     else
     {
-      *option_arg++ = '\0';                    /* cut argument from option */
-      if (*option_arg == '\0')                 /* no argument after '=' */
+      *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);
     }
 
     option_len = strlen(option);
 
     if (strEqual(option, "-"))
+    {
       Error(ERR_EXIT_HELP, "unrecognized option '%s'", option);
+    }
     else if (strncmp(option, "-help", option_len) == 0)
     {
       print_usage_function();
 
       exit(0);
     }
-    else if (strncmp(option, "-display", option_len) == 0)
-    {
-      if (option_arg == NULL)
-       Error(ERR_EXIT_HELP, "option '%s' requires an argument", option_str);
-
-      options.display_name = option_arg;
-      if (option_arg == next_option)
-       options_left++;
-    }
     else if (strncmp(option, "-basepath", option_len) == 0)
     {
       if (option_arg == NULL)
        Error(ERR_EXIT_HELP, "option '%s' requires an argument", option_str);
 
-      /* this should be extended to separate options for ro and rw data */
+      // 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;
       if (option_arg == next_option)
        options_left++;
 
-      /* adjust paths for sub-directories in base directory accordingly */
+      // 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);
     }
     else if (strncmp(option, "-levels", option_len) == 0)
     {
@@ -947,6 +1051,10 @@ void GetOptions(char *argv[], void (*print_usage_function)(void))
       if (option_arg == next_option)
        options_left++;
     }
+    else if (strncmp(option, "-mytapes", option_len) == 0)
+    {
+      options.mytapes = TRUE;
+    }
     else if (strncmp(option, "-network", option_len) == 0)
     {
       options.network = TRUE;
@@ -955,36 +1063,24 @@ void GetOptions(char *argv[], void (*print_usage_function)(void))
     {
       options.serveronly = TRUE;
     }
-    else if (strncmp(option, "-verbose", option_len) == 0)
-    {
-      options.verbose = TRUE;
-    }
     else if (strncmp(option, "-debug", option_len) == 0)
     {
       options.debug = TRUE;
     }
-    else if (strncmp(option, "-debug-x11-sync", option_len) == 0)
+    else if (strncmp(option, "-verbose", option_len) == 0)
     {
-      options.debug_x11_sync = TRUE;
+      options.verbose = TRUE;
+    }
+    else if (strncmp(option, "-version", option_len) == 0 ||
+            strncmp(option, "-V", option_len) == 0)
+    {
+      print_version_function();
+
+      exit(0);
     }
     else if (strPrefix(option, "-D"))
     {
-#if 1
       options.special_flags = getStringCopy(&option[2]);
-#else
-      char *flags_string = &option[2];
-      unsigned int flags_value;
-
-      if (*flags_string == '\0')
-       Error(ERR_EXIT_HELP, "empty flag ignored");
-
-      flags_value = get_special_flags_function(flags_string);
-
-      if (flags_value == 0)
-       Error(ERR_EXIT_HELP, "unknown flag '%s'", flags_string);
-
-      options.special_flags |= flags_value;
-#endif
     }
     else if (strncmp(option, "-execute", option_len) == 0)
     {
@@ -995,9 +1091,15 @@ void GetOptions(char *argv[], void (*print_usage_function)(void))
       if (option_arg == next_option)
        options_left++;
 
-      /* when doing batch processing, always enable verbose mode (warnings) */
+      // when doing batch processing, always enable verbose mode (warnings)
       options.verbose = TRUE;
     }
+#if defined(PLATFORM_MACOSX)
+    else if (strPrefix(option, "-psn"))
+    {
+      // ignore process serial number when launched via GUI on Mac OS X
+    }
+#endif
     else if (*option == '-')
     {
       Error(ERR_EXIT_HELP, "unrecognized option '%s'", option_str);
@@ -1020,23 +1122,25 @@ void GetOptions(char *argv[], void (*print_usage_function)(void))
 }
 
 
-/* ------------------------------------------------------------------------- */
-/* error handling functions                                                  */
-/* ------------------------------------------------------------------------- */
+// ----------------------------------------------------------------------------
+// error handling functions
+// ----------------------------------------------------------------------------
 
-/* used by SetError() and GetError() to store internal error messages */
-static char internal_error[1024];      /* this is bad */
+#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);
-  vsprintf(internal_error, format, ap);
+  vsnprintf(internal_error, MAX_INTERNAL_ERROR_SIZE, format, ap);
   va_end(ap);
 }
 
-char *GetError()
+char *GetError(void)
 {
   return internal_error;
 }
@@ -1046,6 +1150,9 @@ 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 :
@@ -1054,16 +1161,18 @@ void Error(int mode, char *format, ...)
                      ANDROID_LOG_UNKNOWN);
 #endif
 
-#if 1
-  /* display warnings only when running in verbose mode */
+  // 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;
-#endif
 
   if (mode == ERR_INFO_LINE)
   {
     if (!last_line_was_separator)
-      fprintf_line(program.error_file, format, 79);
+      printf_log_line(format, 79);
 
     last_line_was_separator = TRUE;
 
@@ -1081,16 +1190,20 @@ void Error(int mode, char *format, ...)
 
   if (format)
   {
-    va_list ap;
-
-    fprintf(program.error_file, "%s%s: ", program.command_basename,
-           process_name);
+#if !defined(PLATFORM_ANDROID)
+    printf_log_nonewline("%s%s: ", program.command_basename, process_name);
+#endif
 
     if (mode & ERR_WARN)
-      fprintf(program.error_file, "warning: ");
+      printf_log_nonewline("warning: ");
+
+    if (mode & ERR_EXIT)
+      printf_log_nonewline("fatal error: ");
+
+    va_list ap;
 
     va_start(ap, format);
-    vfprintf_newline(program.error_file, format, ap);
+    vprintf_log(format, ap);
     va_end(ap);
 
     if ((mode & ERR_EXIT) && !(mode & ERR_FROM_SERVER))
@@ -1102,27 +1215,25 @@ void Error(int mode, char *format, ...)
   }
   
   if (mode & ERR_HELP)
-    fprintf_newline(program.error_file,
-                   "%s: Try option '--help' for more information.",
-                   program.command_basename);
+    printf_log("%s: Try option '--help' for more information.",
+              program.command_basename);
 
   if (mode & ERR_EXIT)
-    fprintf_newline(program.error_file, "%s%s: aborting",
-                   program.command_basename, process_name);
+    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 */
+      exit(1);                         // child process: normal exit
     else
-      program.exit_function(1);                /* main process: clean up stuff */
+      program.exit_function(1);                // main process: clean up stuff
   }
 }
 
 
-/* ------------------------------------------------------------------------- */
-/* checked memory allocation and freeing functions                           */
-/* ------------------------------------------------------------------------- */
+// ----------------------------------------------------------------------------
+// checked memory allocation and freeing functions
+// ----------------------------------------------------------------------------
 
 void *checked_malloc(unsigned int size)
 {
@@ -1160,14 +1271,14 @@ void *checked_realloc(void *ptr, unsigned int size)
 
 void checked_free(void *ptr)
 {
-  if (ptr != NULL)     /* this check should be done by free() anyway */
+  if (ptr != NULL)     // this check should be done by free() anyway
     free(ptr);
 }
 
 void clear_mem(void *ptr, unsigned int size)
 {
 #if defined(PLATFORM_WIN32)
-  /* for unknown reason, memset() sometimes crashes when compiled with MinGW */
+  // for unknown reason, memset() sometimes crashes when compiled with MinGW
   char *cptr = (char *)ptr;
 
   while (size--)
@@ -1178,11 +1289,11 @@ void clear_mem(void *ptr, unsigned int size)
 }
 
 
-/* ------------------------------------------------------------------------- */
-/* various helper functions                                                  */
-/* ------------------------------------------------------------------------- */
+// ----------------------------------------------------------------------------
+// various helper functions
+// ----------------------------------------------------------------------------
 
-inline void swap_numbers(int *i1, int *i2)
+void swap_numbers(int *i1, int *i2)
 {
   int help = *i1;
 
@@ -1190,7 +1301,7 @@ inline void swap_numbers(int *i1, int *i2)
   *i2 = help;
 }
 
-inline void swap_number_pairs(int *x1, int *y1, int *x2, int *y2)
+void swap_number_pairs(int *x1, int *y1, int *x2, int *y2)
 {
   int help_x = *x1;
   int help_y = *y1;
@@ -1208,9 +1319,9 @@ inline void swap_number_pairs(int *x1, int *y1, int *x2, int *y2)
    of the (not yet written) chunk, write the correct chunk size and finally
    write the chunk itself */
 
-int getFile8BitInteger(FILE *file)
+int getFile8BitInteger(File *file)
 {
-  return fgetc(file);
+  return getByteFromFile(file);
 }
 
 int putFile8BitInteger(FILE *file, int value)
@@ -1221,14 +1332,14 @@ int putFile8BitInteger(FILE *file, int value)
   return 1;
 }
 
-int getFile16BitInteger(FILE *file, int byte_order)
+int getFile16BitInteger(File *file, int byte_order)
 {
   if (byte_order == BYTE_ORDER_BIG_ENDIAN)
-    return ((fgetc(file) << 8) |
-           (fgetc(file) << 0));
-  else          /* BYTE_ORDER_LITTLE_ENDIAN */
-    return ((fgetc(file) << 0) |
-           (fgetc(file) << 8));
+    return ((getByteFromFile(file) << 8) |
+           (getByteFromFile(file) << 0));
+  else          // BYTE_ORDER_LITTLE_ENDIAN
+    return ((getByteFromFile(file) << 0) |
+           (getByteFromFile(file) << 8));
 }
 
 int putFile16BitInteger(FILE *file, int value, int byte_order)
@@ -1240,7 +1351,7 @@ int putFile16BitInteger(FILE *file, int value, int byte_order)
       fputc((value >> 8) & 0xff, file);
       fputc((value >> 0) & 0xff, file);
     }
-    else          /* BYTE_ORDER_LITTLE_ENDIAN */
+    else          // BYTE_ORDER_LITTLE_ENDIAN
     {
       fputc((value >> 0) & 0xff, file);
       fputc((value >> 8) & 0xff, file);
@@ -1250,18 +1361,18 @@ int putFile16BitInteger(FILE *file, int value, int byte_order)
   return 2;
 }
 
-int getFile32BitInteger(FILE *file, int byte_order)
+int getFile32BitInteger(File *file, int byte_order)
 {
   if (byte_order == BYTE_ORDER_BIG_ENDIAN)
-    return ((fgetc(file) << 24) |
-           (fgetc(file) << 16) |
-           (fgetc(file) <<  8) |
-           (fgetc(file) <<  0));
-  else          /* BYTE_ORDER_LITTLE_ENDIAN */
-    return ((fgetc(file) <<  0) |
-           (fgetc(file) <<  8) |
-           (fgetc(file) << 16) |
-           (fgetc(file) << 24));
+    return ((getByteFromFile(file) << 24) |
+           (getByteFromFile(file) << 16) |
+           (getByteFromFile(file) <<  8) |
+           (getByteFromFile(file) <<  0));
+  else          // BYTE_ORDER_LITTLE_ENDIAN
+    return ((getByteFromFile(file) <<  0) |
+           (getByteFromFile(file) <<  8) |
+           (getByteFromFile(file) << 16) |
+           (getByteFromFile(file) << 24));
 }
 
 int putFile32BitInteger(FILE *file, int value, int byte_order)
@@ -1275,7 +1386,7 @@ int putFile32BitInteger(FILE *file, int value, int byte_order)
       fputc((value >>  8) & 0xff, file);
       fputc((value >>  0) & 0xff, file);
     }
-    else          /* BYTE_ORDER_LITTLE_ENDIAN */
+    else          // BYTE_ORDER_LITTLE_ENDIAN
     {
       fputc((value >>  0) & 0xff, file);
       fputc((value >>  8) & 0xff, file);
@@ -1287,22 +1398,22 @@ int putFile32BitInteger(FILE *file, int value, int byte_order)
   return 4;
 }
 
-boolean getFileChunk(FILE *file, char *chunk_name, int *chunk_size,
+boolean getFileChunk(File *file, char *chunk_name, int *chunk_size,
                     int byte_order)
 {
   const int chunk_name_length = 4;
 
-  /* read chunk name */
-  if (fgets(chunk_name, chunk_name_length + 1, file) == NULL)
+  // read chunk name
+  if (getStringFromFile(file, chunk_name, chunk_name_length + 1) == NULL)
     return FALSE;
 
   if (chunk_size != NULL)
   {
-    /* read chunk size */
+    // read chunk size
     *chunk_size = getFile32BitInteger(file, byte_order);
   }
 
-  return (feof(file) || ferror(file) ? FALSE : TRUE);
+  return (checkEndOfFile(file) ? FALSE : TRUE);
 }
 
 int putFileChunk(FILE *file, char *chunk_name, int chunk_size,
@@ -1310,7 +1421,7 @@ int putFileChunk(FILE *file, char *chunk_name, int chunk_size,
 {
   int num_bytes = 0;
 
-  /* write chunk name */
+  // write chunk name
   if (file != NULL)
     fputs(chunk_name, file);
 
@@ -1318,7 +1429,7 @@ int putFileChunk(FILE *file, char *chunk_name, int chunk_size,
 
   if (chunk_size >= 0)
   {
-    /* write chunk size */
+    // write chunk size
     if (file != NULL)
       putFile32BitInteger(file, chunk_size, byte_order);
 
@@ -1328,41 +1439,41 @@ int putFileChunk(FILE *file, char *chunk_name, int chunk_size,
   return num_bytes;
 }
 
-int getFileVersion(FILE *file)
+int getFileVersion(File *file)
 {
-  int version_major = fgetc(file);
-  int version_minor = fgetc(file);
-  int version_patch = fgetc(file);
-  int version_build = fgetc(file);
+  int version_super = getByteFromFile(file);
+  int version_major = getByteFromFile(file);
+  int version_minor = getByteFromFile(file);
+  int version_patch = getByteFromFile(file);
 
-  return VERSION_IDENT(version_major, version_minor, version_patch,
-                      version_build);
+  return VERSION_IDENT(version_super, version_major, version_minor,
+                      version_patch);
 }
 
 int putFileVersion(FILE *file, int version)
 {
   if (file != NULL)
   {
+    int version_super = VERSION_SUPER(version);
     int version_major = VERSION_MAJOR(version);
     int version_minor = VERSION_MINOR(version);
     int version_patch = VERSION_PATCH(version);
-    int version_build = VERSION_BUILD(version);
 
+    fputc(version_super, file);
     fputc(version_major, file);
     fputc(version_minor, file);
     fputc(version_patch, file);
-    fputc(version_build, file);
   }
 
   return 4;
 }
 
-void ReadBytesFromFile(FILE *file, byte *buffer, unsigned int bytes)
+void ReadBytesFromFile(File *file, byte *buffer, unsigned int bytes)
 {
   int i;
 
-  for(i = 0; i < bytes && !feof(file); i++)
-    buffer[i] = fgetc(file);
+  for (i = 0; i < bytes && !checkEndOfFile(file); i++)
+    buffer[i] = getByteFromFile(file);
 }
 
 void WriteBytesToFile(FILE *file, byte *buffer, unsigned int bytes)
@@ -1373,10 +1484,10 @@ void WriteBytesToFile(FILE *file, byte *buffer, unsigned int bytes)
     fputc(buffer[i], file);
 }
 
-void ReadUnusedBytesFromFile(FILE *file, unsigned int bytes)
+void ReadUnusedBytesFromFile(File *file, unsigned int bytes)
 {
-  while (bytes-- && !feof(file))
-    fgetc(file);
+  while (bytes-- && !checkEndOfFile(file))
+    getByteFromFile(file);
 }
 
 void WriteUnusedBytesToFile(FILE *file, unsigned int bytes)
@@ -1386,16 +1497,16 @@ void WriteUnusedBytesToFile(FILE *file, unsigned int bytes)
 }
 
 
-/* ------------------------------------------------------------------------- */
-/* functions to translate key identifiers between different format           */
-/* ------------------------------------------------------------------------- */
+// ----------------------------------------------------------------------------
+// functions to translate key identifiers between different format
+// ----------------------------------------------------------------------------
 
 #define TRANSLATE_KEYSYM_TO_KEYNAME    0
 #define TRANSLATE_KEYSYM_TO_X11KEYNAME 1
 #define TRANSLATE_KEYNAME_TO_KEYSYM    2
 #define TRANSLATE_X11KEYNAME_TO_KEYSYM 3
 
-void translate_keyname(Key *keysym, char **x11name, char **name, int mode)
+static void translate_keyname(Key *keysym, char **x11name, char **name, int mode)
 {
   static struct
   {
@@ -1404,13 +1515,17 @@ void translate_keyname(Key *keysym, char **x11name, char **name, int mode)
     char *name;
   } translate_key[] =
   {
-    /* normal cursor keys */
+    // return and escape keys
+    { KSYM_Return,     "XK_Return",            "return" },
+    { KSYM_Escape,     "XK_Escape",            "escape" },
+
+    // normal cursor keys
     { KSYM_Left,       "XK_Left",              "cursor left" },
     { KSYM_Right,      "XK_Right",             "cursor right" },
     { KSYM_Up,         "XK_Up",                "cursor up" },
     { KSYM_Down,       "XK_Down",              "cursor down" },
 
-    /* keypad cursor keys */
+    // keypad cursor keys
 #ifdef KSYM_KP_Left
     { KSYM_KP_Left,    "XK_KP_Left",           "keypad left" },
     { KSYM_KP_Right,   "XK_KP_Right",          "keypad right" },
@@ -1418,7 +1533,7 @@ void translate_keyname(Key *keysym, char **x11name, char **name, int mode)
     { KSYM_KP_Down,    "XK_KP_Down",           "keypad down" },
 #endif
 
-    /* other keypad keys */
+    // other keypad keys
 #ifdef KSYM_KP_Enter
     { KSYM_KP_Enter,   "XK_KP_Enter",          "keypad enter" },
     { KSYM_KP_Add,     "XK_KP_Add",            "keypad +" },
@@ -1428,7 +1543,7 @@ void translate_keyname(Key *keysym, char **x11name, char **name, int mode)
     { KSYM_KP_Separator,"XK_KP_Separator",     "keypad ," },
 #endif
 
-    /* modifier keys */
+    // modifier keys
     { KSYM_Shift_L,    "XK_Shift_L",           "left shift" },
     { KSYM_Shift_R,    "XK_Shift_R",           "right shift" },
     { KSYM_Control_L,  "XK_Control_L",         "left control" },
@@ -1437,14 +1552,10 @@ 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 */
+    { KSYM_Mode_switch,        "XK_Mode_switch",       "mode switch" }, // Alt-R
+    { KSYM_Multi_key,  "XK_Multi_key",         "multi key" },   // Ctrl-R
 
-    /* some special keys */
+    // some special keys
     { KSYM_BackSpace,  "XK_BackSpace",         "backspace" },
     { KSYM_Delete,     "XK_Delete",            "delete" },
     { KSYM_Insert,     "XK_Insert",            "insert" },
@@ -1454,10 +1565,16 @@ 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" },
 
-    { KSYM_Menu,       "XK_Menu",              "menu" },        /* menu key */
-    { KSYM_Back,       "XK_Back",              "back" },        /* back key */
+    { KSYM_Select,     "XK_Select",            "select" },
+    { KSYM_Menu,       "XK_Menu",              "menu" },        // menu key
+    { KSYM_Back,       "XK_Back",              "back" },        // back key
+    { KSYM_PlayPause,  "XK_PlayPause",         "play/pause" },
+#if defined(PLATFORM_ANDROID)
+    { KSYM_Rewind,     "XK_Rewind",            "rewind" },
+    { KSYM_FastForward,        "XK_FastForward",       "fast forward" },
+#endif
 
-    /* ASCII 0x20 to 0x40 keys (except numbers) */
+    // ASCII 0x20 to 0x40 keys (except numbers)
     { KSYM_space,      "XK_space",             "space" },
     { KSYM_exclam,     "XK_exclam",            "!" },
     { KSYM_quotedbl,   "XK_quotedbl",          "\"" },
@@ -1482,7 +1599,7 @@ void translate_keyname(Key *keysym, char **x11name, char **name, int mode)
     { KSYM_question,   "XK_question",          "?" },
     { KSYM_at,         "XK_at",                "@" },
 
-    /* more ASCII keys */
+    // more ASCII keys
     { KSYM_bracketleft,        "XK_bracketleft",       "[" },
     { KSYM_backslash,  "XK_backslash",         "\\" },
     { KSYM_bracketright,"XK_bracketright",     "]" },
@@ -1495,19 +1612,42 @@ void translate_keyname(Key *keysym, char **x11name, char **name, int mode)
     { KSYM_braceright, "XK_braceright",        "brace right" },
     { KSYM_asciitilde, "XK_asciitilde",        "~" },
 
-#if !defined(TARGET_SDL2)
-    /* special (non-ASCII) keys */
-    { KSYM_degree,     "XK_degree",            "°" },
-    { KSYM_Adiaeresis, "XK_Adiaeresis",        "Ä" },
-    { KSYM_Odiaeresis, "XK_Odiaeresis",        "Ö" },
-    { KSYM_Udiaeresis, "XK_Udiaeresis",        "Ãœ" },
-    { KSYM_adiaeresis, "XK_adiaeresis",        "ä" },
-    { KSYM_odiaeresis, "XK_odiaeresis",        "ö" },
-    { KSYM_udiaeresis, "XK_udiaeresis",        "ü" },
+    // special (non-ASCII) keys
+    { KSYM_degree,     "XK_degree",            "degree" },
+    { KSYM_Adiaeresis, "XK_Adiaeresis",        "A umlaut" },
+    { KSYM_Odiaeresis, "XK_Odiaeresis",        "O umlaut" },
+    { KSYM_Udiaeresis, "XK_Udiaeresis",        "U umlaut" },
+    { KSYM_adiaeresis, "XK_adiaeresis",        "a umlaut" },
+    { KSYM_odiaeresis, "XK_odiaeresis",        "o umlaut" },
+    { KSYM_udiaeresis, "XK_udiaeresis",        "u umlaut" },
     { KSYM_ssharp,     "XK_ssharp",            "sharp s" },
-#endif
 
-    /* end-of-array identifier */
+    // special (non-ASCII) keys (UTF-8, for reverse mapping only)
+    { KSYM_degree,     "XK_degree",            "\xc2\xb0" },
+    { KSYM_Adiaeresis, "XK_Adiaeresis",        "\xc3\x84" },
+    { KSYM_Odiaeresis, "XK_Odiaeresis",        "\xc3\x96" },
+    { KSYM_Udiaeresis, "XK_Udiaeresis",        "\xc3\x9c" },
+    { KSYM_adiaeresis, "XK_adiaeresis",        "\xc3\xa4" },
+    { KSYM_odiaeresis, "XK_odiaeresis",        "\xc3\xb6" },
+    { KSYM_udiaeresis, "XK_udiaeresis",        "\xc3\xbc" },
+    { KSYM_ssharp,     "XK_ssharp",            "\xc3\x9f" },
+
+    // other keys (for reverse mapping only)
+    { KSYM_space,      "XK_space",             " " },
+
+    // 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" },
+    { KSYM_KP_2,       "XK_KP_2",              "keypad 2" },
+    { KSYM_KP_3,       "XK_KP_3",              "keypad 3" },
+    { KSYM_KP_4,       "XK_KP_4",              "keypad 4" },
+    { KSYM_KP_5,       "XK_KP_5",              "keypad 5" },
+    { KSYM_KP_6,       "XK_KP_6",              "keypad 6" },
+    { KSYM_KP_7,       "XK_KP_7",              "keypad 7" },
+    { KSYM_KP_8,       "XK_KP_8",              "keypad 8" },
+    { KSYM_KP_9,       "XK_KP_9",              "keypad 9" },
+
+    // end-of-array identifier
     { 0,                NULL,                  NULL }
   };
 
@@ -1524,8 +1664,6 @@ 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));
-    else if (key >= KSYM_KP_0 && key <= KSYM_KP_9)
-      sprintf(name_buffer, "keypad %c", '0' + (char)(key - KSYM_KP_0));
     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)
@@ -1561,8 +1699,6 @@ 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));
-    else if (key >= KSYM_KP_0 && key <= KSYM_KP_9)
-      sprintf(name_buffer, "XK_KP_%c", '0' + (char)(key - KSYM_KP_0));
     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)
@@ -1590,17 +1726,34 @@ void translate_keyname(Key *keysym, char **x11name, char **name, int mode)
   else if (mode == TRANSLATE_KEYNAME_TO_KEYSYM)
   {
     Key key = KSYM_UNDEFINED;
+    char *name_ptr = *name;
 
-    i = 0;
-    do
+    if (strlen(*name) == 1)
     {
-      if (strEqual(translate_key[i].name, *name))
+      char c = name_ptr[0];
+
+      if (c >= 'A' && c <= 'Z')
+       key = KSYM_A + (Key)(c - 'A');
+      else if (c >= 'a' && c <= 'z')
+       key = KSYM_a + (Key)(c - 'a');
+      else if (c >= '0' && c <= '9')
+       key = KSYM_0 + (Key)(c - '0');
+    }
+
+    if (key == KSYM_UNDEFINED)
+    {
+      i = 0;
+
+      do
       {
-       key = translate_key[i].key;
-       break;
+       if (strEqual(translate_key[i].name, *name))
+       {
+         key = translate_key[i].key;
+         break;
+       }
       }
+      while (translate_key[++i].x11name);
     }
-    while (translate_key[++i].x11name);
 
     if (key == KSYM_UNDEFINED)
       Error(ERR_WARN, "getKeyFromKeyName(): not completely implemented");
@@ -1623,13 +1776,6 @@ void translate_keyname(Key *keysym, char **x11name, char **name, int mode)
       else if (c >= '0' && c <= '9')
        key = KSYM_0 + (Key)(c - '0');
     }
-    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');
-    }
     else if (strPrefix(name_ptr, "XK_F") && strlen(name_ptr) <= 6)
     {
       char c1 = name_ptr[4];
@@ -1726,6 +1872,26 @@ Key getKeyFromX11KeyName(char *x11name)
 
 char getCharFromKey(Key key)
 {
+  static struct
+  {
+    Key key;
+    byte key_char;
+  } translate_key_char[] =
+  {
+    // special (non-ASCII) keys (ISO-8859-1)
+    { KSYM_degree,     CHAR_BYTE_DEGREE        },
+    { KSYM_Adiaeresis, CHAR_BYTE_UMLAUT_A      },
+    { KSYM_Odiaeresis, CHAR_BYTE_UMLAUT_O      },
+    { KSYM_Udiaeresis, CHAR_BYTE_UMLAUT_U      },
+    { KSYM_adiaeresis, CHAR_BYTE_UMLAUT_a      },
+    { KSYM_odiaeresis, CHAR_BYTE_UMLAUT_o      },
+    { KSYM_udiaeresis, CHAR_BYTE_UMLAUT_u      },
+    { KSYM_ssharp,     CHAR_BYTE_SHARP_S       },
+
+    // end-of-array identifier
+    { 0,                0                      }
+  };
+
   char *keyname = getKeyNameFromKey(key);
   char c = 0;
 
@@ -1733,23 +1899,38 @@ char getCharFromKey(Key key)
     c = keyname[0];
   else if (strEqual(keyname, "space"))
     c = ' ';
+  else
+  {
+    int i = 0;
+
+    do
+    {
+      if (key == translate_key_char[i].key)
+      {
+       c = translate_key_char[i].key_char;
+
+       break;
+      }
+    }
+    while (translate_key_char[++i].key_char);
+  }
 
   return c;
 }
 
 char getValidConfigValueChar(char c)
 {
-  if (c == '#' ||      /* used to mark comments */
-      c == '\\')       /* used to mark continued lines */
+  if (c == '#' ||      // used to mark comments
+      c == '\\')       // used to mark continued lines
     c = 0;
 
   return c;
 }
 
 
-/* ------------------------------------------------------------------------- */
-/* functions to translate string identifiers to integer or boolean value     */
-/* ------------------------------------------------------------------------- */
+// ----------------------------------------------------------------------------
+// functions to translate string identifiers to integer or boolean value
+// ----------------------------------------------------------------------------
 
 int get_integer_from_string(char *s)
 {
@@ -1834,12 +2015,37 @@ int get_switch3_from_string(char *s)
   return result;
 }
 
+int get_player_nr_from_string(char *s)
+{
+  static char *player_text[] =
+  {
+    "player_1",
+    "player_2",
+    "player_3",
+    "player_4",
 
-/* ------------------------------------------------------------------------- */
-/* functions for generic lists                                               */
-/* ------------------------------------------------------------------------- */
+    NULL
+  };
+
+  char *s_lower = getStringToLower(s);
+  int result = 0;
+  int i;
+
+  for (i = 0; player_text[i] != NULL; i++)
+    if (strEqual(s_lower, player_text[i]))
+      result = i;
+
+  free(s_lower);
 
-ListNode *newListNode()
+  return result;
+}
+
+
+// ----------------------------------------------------------------------------
+// functions for generic lists
+// ----------------------------------------------------------------------------
+
+ListNode *newListNode(void)
 {
   return checked_calloc(sizeof(ListNode));
 }
@@ -1851,6 +2057,10 @@ void addNodeToList(ListNode **node_first, char *key, void *content)
   node_new->key = getStringCopy(key);
   node_new->content = content;
   node_new->next = *node_first;
+
+  if (*node_first)
+    (*node_first)->prev = node_new;
+
   *node_first = node_new;
 }
 
@@ -1862,13 +2072,33 @@ void deleteNodeFromList(ListNode **node_first, char *key,
 
   if (strEqual((*node_first)->key, key))
   {
-    checked_free((*node_first)->key);
+    // after first recursion, (*node_first)->prev->next == *node_first,
+    // so *node_first would be overwritten with (*node_first)->next
+    // => use a copy of *node_first (and later of (*node_first)->next)
+    ListNode *node = *node_first;
+    ListNode *node_next = node->next;
+
+    checked_free(node->key);
+
     if (destructor_function)
-      destructor_function((*node_first)->content);
-    *node_first = (*node_first)->next;
+      destructor_function(node->content);
+
+    if (node->prev)
+      node->prev->next = node->next;
+
+    if (node->next)
+      node->next->prev = node->prev;
+
+    checked_free(node);
+
+    // after removing node, set list pointer to next valid list node
+    // (this is important if the first node of the list was deleted)
+    *node_first = node_next;
   }
   else
+  {
     deleteNodeFromList(&(*node_first)->next, key, destructor_function);
+  }
 }
 
 ListNode *getNodeFromKey(ListNode *node_first, char *key)
@@ -1887,7 +2117,8 @@ int getNumNodes(ListNode *node_first)
   return (node_first ? 1 + getNumNodes(node_first->next) : 0);
 }
 
-void dumpList(ListNode *node_first)
+#if 0
+static void dumpList(ListNode *node_first)
 {
   ListNode *node = node_first;
 
@@ -1900,11 +2131,14 @@ void dumpList(ListNode *node_first)
 
   printf("[%d nodes]\n", getNumNodes(node_first));
 }
+#endif
+
 
+// ----------------------------------------------------------------------------
+// functions for file handling
+// ----------------------------------------------------------------------------
 
-/* ------------------------------------------------------------------------- */
-/* functions for file handling                                               */
-/* ------------------------------------------------------------------------- */
+#define MAX_BUFFER_SIZE                        4096
 
 File *openFile(char *filename, char *mode)
 {
@@ -1927,44 +2161,107 @@ File *openFile(char *filename, char *mode)
     file->file_is_asset = TRUE;
     file->filename = getStringCopy(filename);
 
-    return file;
+    return file;
+  }
+#endif
+
+  checked_free(file);
+
+  return NULL;
+}
+
+int closeFile(File *file)
+{
+  if (file == NULL)
+    return -1;
+
+  int result = 0;
+
+#if defined(PLATFORM_ANDROID)
+  if (file->asset_file)
+    result = SDL_RWclose(file->asset_file);
+#endif
+
+  if (file->file)
+    result = fclose(file->file);
+
+  checked_free(file->filename);
+  checked_free(file);
+
+  return result;
+}
+
+int checkEndOfFile(File *file)
+{
+#if defined(PLATFORM_ANDROID)
+  if (file->file_is_asset)
+    return file->end_of_file;
+#endif
+
+  return feof(file->file);
+}
+
+size_t readFile(File *file, void *buffer, size_t item_size, size_t num_items)
+{
+#if defined(PLATFORM_ANDROID)
+  if (file->file_is_asset)
+  {
+    if (file->end_of_file)
+      return 0;
+
+    size_t num_items_read =
+      SDL_RWread(file->asset_file, buffer, item_size, num_items);
+
+    if (num_items_read < num_items)
+      file->end_of_file = TRUE;
+
+    return num_items_read;
   }
 #endif
 
-  checked_free(file);
-
-  return NULL;
+  return fread(buffer, item_size, num_items, file->file);
 }
 
-int closeFile(File *file)
+size_t writeFile(File *file, void *buffer, size_t item_size, size_t num_items)
 {
-  if (file == NULL)
-    return -1;
-
-  int result;
+  return fwrite(buffer, item_size, num_items, file->file);
+}
 
+int seekFile(File *file, long offset, int whence)
+{
 #if defined(PLATFORM_ANDROID)
-  if (file->asset_file)
-    result = SDL_RWclose(file->asset_file);
-#endif
-
-  if (file->file)
-    result = fclose(file->file);
+  if (file->file_is_asset)
+  {
+    int sdl_whence = (whence == SEEK_SET ? RW_SEEK_SET :
+                     whence == SEEK_CUR ? RW_SEEK_CUR :
+                     whence == SEEK_END ? RW_SEEK_END : 0);
 
-  checked_free(file->filename);
-  checked_free(file);
+    return (SDL_RWseek(file->asset_file, offset, sdl_whence) == -1 ? -1 : 0);
+  }
+#endif
 
-  return result;
+  return fseek(file->file, offset, whence);
 }
 
-int checkEndOfFile(File *file)
+int getByteFromFile(File *file)
 {
 #if defined(PLATFORM_ANDROID)
   if (file->file_is_asset)
-    return file->end_of_file;
+  {
+    if (file->end_of_file)
+      return EOF;
+
+    byte c;
+    size_t num_bytes_read = SDL_RWread(file->asset_file, &c, 1, 1);
+
+    if (num_bytes_read < 1)
+      file->end_of_file = TRUE;
+
+    return (file->end_of_file ? EOF : (int)c);
+  }
 #endif
 
-  return feof(file->file);
+  return fgetc(file->file);
 }
 
 char *getStringFromFile(File *file, char *line, int size)
@@ -1999,10 +2296,40 @@ char *getStringFromFile(File *file, char *line, int size)
   return fgets(line, size, file->file);
 }
 
+int copyFile(char *filename_from, char *filename_to)
+{
+  File *file_from, *file_to;
+
+  if ((file_from = openFile(filename_from, MODE_READ)) == NULL)
+  {
+    return -1;
+  }
+
+  if ((file_to = openFile(filename_to, MODE_WRITE)) == NULL)
+  {
+    closeFile(file_from);
+
+    return -1;
+  }
+
+  while (!checkEndOfFile(file_from))
+  {
+    byte buffer[MAX_BUFFER_SIZE];
+    size_t bytes_read = readFile(file_from, buffer, 1, MAX_BUFFER_SIZE);
+
+    writeFile(file_to, buffer, 1, bytes_read);
+  }
+
+  closeFile(file_from);
+  closeFile(file_to);
+
+  return 0;
+}
+
 
-/* ------------------------------------------------------------------------- */
-/* functions for directory handling                                          */
-/* ------------------------------------------------------------------------- */
+// ----------------------------------------------------------------------------
+// functions for directory handling
+// ----------------------------------------------------------------------------
 
 Directory *openDirectory(char *dir_name)
 {
@@ -2043,7 +2370,7 @@ int closeDirectory(Directory *dir)
   if (dir == NULL)
     return -1;
 
-  int result;
+  int result = 0;
 
 #if defined(PLATFORM_ANDROID)
   if (dir->asset_toc_file)
@@ -2120,13 +2447,7 @@ DirectoryEntry *readDirectory(Directory *dir)
 
   dir->dir_entry->is_directory =
     (stat(dir->dir_entry->filename, &file_status) == 0 &&
-     (file_status.st_mode & S_IFMT) == S_IFDIR);
-
-#if 0
-  Error(ERR_INFO, "::: '%s' is directory: %d",
-       dir->dir_entry->basename,
-       dir->dir_entry->is_directory);
-#endif
+     S_ISDIR(file_status.st_mode));
 
   return dir->dir_entry;
 }
@@ -2142,16 +2463,18 @@ void freeDirectoryEntry(DirectoryEntry *dir_entry)
 }
 
 
-/* ------------------------------------------------------------------------- */
-/* functions for checking files and filenames                                */
-/* ------------------------------------------------------------------------- */
+// ----------------------------------------------------------------------------
+// functions for checking files and filenames
+// ----------------------------------------------------------------------------
 
 boolean directoryExists(char *dir_name)
 {
   if (dir_name == NULL)
     return FALSE;
 
-  boolean success = (access(dir_name, F_OK) == 0);
+  struct stat file_status;
+  boolean success = (stat(dir_name, &file_status) == 0 &&
+                    S_ISDIR(file_status.st_mode));
 
 #if defined(PLATFORM_ANDROID)
   if (!success)
@@ -2195,7 +2518,8 @@ boolean fileExists(char *filename)
   return success;
 }
 
-boolean fileHasPrefix(char *basename, char *prefix)
+#if 0
+static boolean fileHasPrefix(char *basename, char *prefix)
 {
   static char *basename_lower = NULL;
   int basename_length, prefix_length;
@@ -2216,8 +2540,9 @@ boolean fileHasPrefix(char *basename, char *prefix)
 
   return FALSE;
 }
+#endif
 
-boolean fileHasSuffix(char *basename, char *suffix)
+static boolean fileHasSuffix(char *basename, char *suffix)
 {
   static char *basename_lower = NULL;
   int basename_length, suffix_length;
@@ -2239,81 +2564,64 @@ boolean fileHasSuffix(char *basename, char *suffix)
   return FALSE;
 }
 
-boolean FileIsGraphic(char *filename)
+static boolean FileCouldBeArtwork(char *filename)
 {
   char *basename = getBaseNamePtr(filename);
 
-#if defined(TARGET_SDL)
-  return (!fileHasSuffix(basename, "txt") &&
-         !fileHasSuffix(basename, "conf"));
-#else
-  return fileHasSuffix(basename, "pcx");
-#endif
+  return (!strEqual(basename, ".") &&
+         !strEqual(basename, "..") &&
+         !fileHasSuffix(basename, "txt") &&
+         !fileHasSuffix(basename, "conf") &&
+         !directoryExists(filename));
 }
 
-boolean FileIsSound(char *filename)
+boolean FileIsGraphic(char *filename)
 {
-  char *basename = getBaseNamePtr(filename);
+  return FileCouldBeArtwork(filename);
+}
 
-#if defined(TARGET_SDL)
-  return (!fileHasSuffix(basename, "txt") &&
-         !fileHasSuffix(basename, "conf"));
-#else
-  return fileHasSuffix(basename, "wav");
-#endif
+boolean FileIsSound(char *filename)
+{
+  return FileCouldBeArtwork(filename);
 }
 
 boolean FileIsMusic(char *filename)
 {
-  char *basename = getBaseNamePtr(filename);
-
-#if defined(TARGET_SDL)
-  return (!fileHasSuffix(basename, "txt") &&
-         !fileHasSuffix(basename, "conf"));
-#else
-  if (FileIsSound(basename))
-    return TRUE;
-
-#if 0
-#if defined(TARGET_SDL)
-  if ((fileHasPrefix(basename, "mod") && !fileHasSuffix(basename, "txt")) ||
-      fileHasSuffix(basename, "mod") ||
-      fileHasSuffix(basename, "s3m") ||
-      fileHasSuffix(basename, "it") ||
-      fileHasSuffix(basename, "xm") ||
-      fileHasSuffix(basename, "midi") ||
-      fileHasSuffix(basename, "mid") ||
-      fileHasSuffix(basename, "mp3") ||
-      fileHasSuffix(basename, "ogg"))
-    return TRUE;
-#endif
-#endif
-
-  return FALSE;
-#endif
+  return FileCouldBeArtwork(filename);
 }
 
-boolean FileIsArtworkType(char *basename, int type)
+boolean FileIsArtworkType(char *filename, int type)
 {
-  if ((type == TREE_TYPE_GRAPHICS_DIR && FileIsGraphic(basename)) ||
-      (type == TREE_TYPE_SOUNDS_DIR && FileIsSound(basename)) ||
-      (type == TREE_TYPE_MUSIC_DIR && FileIsMusic(basename)))
+  if ((type == TREE_TYPE_GRAPHICS_DIR && FileIsGraphic(filename)) ||
+      (type == TREE_TYPE_SOUNDS_DIR && FileIsSound(filename)) ||
+      (type == TREE_TYPE_MUSIC_DIR && FileIsMusic(filename)))
     return TRUE;
 
   return FALSE;
 }
 
-/* ------------------------------------------------------------------------- */
-/* functions for loading artwork configuration information                   */
-/* ------------------------------------------------------------------------- */
+// ----------------------------------------------------------------------------
+// functions for loading artwork configuration information
+// ----------------------------------------------------------------------------
 
 char *get_mapped_token(char *token)
 {
-  /* !!! make this dynamically configurable (init.c:InitArtworkConfig) !!! */
+  // !!! 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;
 
@@ -2325,180 +2633,90 @@ char *get_mapped_token(char *token)
       return getStringCat2(map_token_prefix[i][1], &token[len_token_prefix]);
   }
 
-  return NULL;
-}
-
-/* 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);
-}
-
-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, ".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"))
+  // change tokens containing ".gfx" by moving the "gfx" part to the very left
+  char *gfx_substring = ".gfx";
+  char *gfx_prefix = "gfx.";
+  if (strstr(token, gfx_substring) != NULL)
   {
-    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 :
-             ANIM_DEFAULT);
+    char *token_prefix = getStringCopy(token);
+    char *token_gfx_pos = strstr(token_prefix, gfx_substring);
+    char *token_suffix = &token_gfx_pos[strlen(gfx_substring)];
+    char *mapped_token;
 
-    if (string_has_parameter(value, "reverse"))
-      result |= ANIM_REVERSE;
+    // cut off token string at ".gfx" substring position
+    *token_gfx_pos = '\0';
 
-    if (string_has_parameter(value, "opaque_player"))
-      result |= ANIM_OPAQUE_PLAYER;
+    // put together prefix "gfx." and token prefix and suffix without ".gfx"
+    mapped_token = getStringCat3(gfx_prefix, token_prefix, token_suffix);
 
-    if (string_has_parameter(value, "static_panel"))
-      result |= ANIM_STATIC_PANEL;
-  }
-  else if (strEqual(suffix, ".class"))
-  {
-    result = get_hash_from_key(value);
-  }
-  else if (strEqual(suffix, ".style"))
-  {
-    result = STYLE_DEFAULT;
+    free(token_prefix);
 
-    if (string_has_parameter(value, "accurate_borders"))
-      result |= STYLE_ACCURATE_BORDERS;
-
-    if (string_has_parameter(value, "inner_corners"))
-      result |= STYLE_INNER_CORNERS;
-  }
-  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 :
-             FADE_MODE_DEFAULT);
-  }
-#if 1
-  else if (strPrefix(suffix, ".font"))         /* (may also be ".font_xyz") */
-#else
-  else if (strEqualN(suffix, ".font", 5))      /* (may also be ".font_xyz") */
-#endif
-  {
-    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);
+    return mapped_token;
   }
 
-  free(value);
-
-  return result;
+  return NULL;
 }
 
-struct ScreenModeInfo *get_screen_mode_from_string(char *screen_mode_string)
-{
-  static struct ScreenModeInfo screen_mode;
-  char *screen_mode_string_x = strchr(screen_mode_string, 'x');
-  char *screen_mode_string_copy;
-  char *screen_mode_string_pos_w;
-  char *screen_mode_string_pos_h;
-
-  if (screen_mode_string_x == NULL)    /* invalid screen mode format */
-    return NULL;
-
-  screen_mode_string_copy = getStringCopy(screen_mode_string);
-
-  screen_mode_string_pos_w = screen_mode_string_copy;
-  screen_mode_string_pos_h = strchr(screen_mode_string_copy, 'x');
-  *screen_mode_string_pos_h++ = '\0';
-
-  screen_mode.width  = atoi(screen_mode_string_pos_w);
-  screen_mode.height = atoi(screen_mode_string_pos_h);
-
-  return &screen_mode;
-}
+static char *get_special_base_token(struct ArtworkListInfo *artwork_info,
+                                   char *token)
+{
+  // !!! make this dynamically configurable (init.c:InitArtworkConfig) !!!
+  static struct ConfigTypeInfo prefix_list[] =
+  {
+    { "global.anim_1"  },
+    { "global.anim_2"  },
+    { "global.anim_3"  },
+    { "global.anim_4"  },
+    { "global.anim_5"  },
+    { "global.anim_6"  },
+    { "global.anim_7"  },
+    { "global.anim_8"  },
+    { "global.anim_9"  },
+    { "global.anim_10" },
+    { "global.anim_11" },
+    { "global.anim_12" },
+    { "global.anim_13" },
+    { "global.anim_14" },
+    { "global.anim_15" },
+    { "global.anim_16" },
+    { "global.anim_17" },
+    { "global.anim_18" },
+    { "global.anim_19" },
+    { "global.anim_20" },
+    { "global.anim_21" },
+    { "global.anim_22" },
+    { "global.anim_23" },
+    { "global.anim_24" },
+    { "global.anim_25" },
+    { "global.anim_26" },
+    { "global.anim_27" },
+    { "global.anim_28" },
+    { "global.anim_29" },
+    { "global.anim_30" },
+    { "global.anim_31" },
+    { "global.anim_32" },
+
+    { NULL             }
+  };
+  struct ConfigTypeInfo *suffix_list = artwork_info->suffix_list;
+  boolean prefix_found = FALSE;
+  int len_suffix = 0;
+  int i;
 
-void get_aspect_ratio_from_screen_mode(struct ScreenModeInfo *screen_mode,
-                                      int *x, int *y)
-{
-  float aspect_ratio = (float)screen_mode->width / (float)screen_mode->height;
-  float aspect_ratio_new;
-  int i = 1;
+  // search for prefix to check if base token has to be created
+  for (i = 0; prefix_list[i].token != NULL; i++)
+    if (strPrefix(token, prefix_list[i].token))
+      prefix_found = TRUE;
 
-  do
-  {
-    *x = i * aspect_ratio + 0.000001;
-    *y = i;
+  if (!prefix_found)
+    return NULL;
 
-    aspect_ratio_new = (float)*x / (float)*y;
+  // search for suffix (parameter) to determine base token length
+  for (i = 0; suffix_list[i].token != NULL; i++)
+    if (strSuffix(token, suffix_list[i].token))
+      len_suffix = strlen(suffix_list[i].token);
 
-    i++;
-  }
-  while (aspect_ratio_new != aspect_ratio && *y < screen_mode->height);
+  return getStringCopyN(token, strlen(token) - len_suffix);
 }
 
 static void FreeCustomArtworkList(struct ArtworkListInfo *,
@@ -2509,18 +2727,24 @@ struct FileInfo *getFileListFromConfigList(struct ConfigInfo *config_list,
                                           char **ignore_tokens,
                                           int num_file_list_entries)
 {
+  SetupFileHash *ignore_tokens_hash;
   struct FileInfo *file_list;
   int num_file_list_entries_found = 0;
   int num_suffix_list_entries = 0;
   int list_pos;
   int i, j;
 
+  // create hash from list of tokens to be ignored (for quick access)
+  ignore_tokens_hash = newSetupFileHash();
+  for (i = 0; ignore_tokens[i] != NULL; i++)
+    setHashEntry(ignore_tokens_hash, ignore_tokens[i], "");
+
   file_list = checked_calloc(num_file_list_entries * sizeof(struct FileInfo));
 
   for (i = 0; suffix_list[i].token != NULL; i++)
     num_suffix_list_entries++;
 
-  /* always start with reliable default values */
+  // always start with reliable default values
   for (i = 0; i < num_file_list_entries; i++)
   {
     file_list[i].token = NULL;
@@ -2552,9 +2776,6 @@ struct FileInfo *getFileListFromConfigList(struct ConfigInfo *config_list,
   for (i = 0; config_list[i].token != NULL; i++)
   {
     int len_config_token = strlen(config_list[i].token);
-#if 0
-    int len_config_value = strlen(config_list[i].value);
-#endif
     boolean is_file_entry = TRUE;
 
     for (j = 0; suffix_list[j].token != NULL; j++)
@@ -2574,10 +2795,9 @@ struct FileInfo *getFileListFromConfigList(struct ConfigInfo *config_list,
       }
     }
 
-    /* the following tokens are no file definitions, but other config tokens */
-    for (j = 0; ignore_tokens[j] != NULL; j++)
-      if (strEqual(config_list[i].token, ignore_tokens[j]))
-       is_file_entry = FALSE;
+    // the following tokens are no file definitions, but other config tokens
+    if (getHashEntry(ignore_tokens_hash, config_list[i].token) != NULL)
+      is_file_entry = FALSE;
 
     if (is_file_entry)
     {
@@ -2587,24 +2807,8 @@ struct FileInfo *getFileListFromConfigList(struct ConfigInfo *config_list,
       if (list_pos >= num_file_list_entries)
        break;
 
-#if 0
-      /* simple sanity check if this is really a file definition */
-      if (!strEqual(&config_list[i].value[len_config_value - 4], ".pcx") &&
-         !strEqual(&config_list[i].value[len_config_value - 4], ".wav") &&
-         !strEqual(config_list[i].value, UNDEFINED_FILENAME))
-      {
-       Error(ERR_INFO, "Configuration directive '%s' -> '%s':",
-             config_list[i].token, config_list[i].value);
-       Error(ERR_EXIT, "This seems to be no valid definition -- please fix");
-      }
-#endif
-
       file_list[list_pos].token = config_list[i].token;
       file_list[list_pos].default_filename = config_list[i].value;
-
-#if 0
-      printf("::: '%s' => '%s'\n", config_list[i].token, config_list[i].value);
-#endif
     }
 
     if (strSuffix(config_list[i].token, ".clone_from"))
@@ -2623,9 +2827,7 @@ struct FileInfo *getFileListFromConfigList(struct ConfigInfo *config_list,
     Error(ERR_EXIT,   "please fix");
   }
 
-#if 0
-  printf("::: ---------- DONE ----------\n");
-#endif
+  freeSetupFileHash(ignore_tokens_hash);
 
   return file_list;
 }
@@ -2635,7 +2837,7 @@ static boolean token_suffix_match(char *token, char *suffix, int start_pos)
   int len_token = strlen(token);
   int len_suffix = strlen(suffix);
 
-  if (start_pos < 0)   /* compare suffix from end of string */
+  if (start_pos < 0)   // compare suffix from end of string
     start_pos += len_token;
 
   if (start_pos < 0 || start_pos + len_suffix > len_token)
@@ -2659,7 +2861,7 @@ static void read_token_parameters(SetupFileHash *setup_file_hash,
                                  struct ConfigTypeInfo *suffix_list,
                                  struct FileInfo *file_list_entry)
 {
-  /* check for config token that is the base token without any suffixes */
+  // check for config token that is the base token without any suffixes
   char *filename = getHashEntry(setup_file_hash, file_list_entry->token);
   char *known_token_value = KNOWN_TOKEN_VALUE;
   int i;
@@ -2668,17 +2870,17 @@ static void read_token_parameters(SetupFileHash *setup_file_hash,
   {
     setString(&file_list_entry->filename, filename);
 
-    /* when file definition found, set all parameters to default values */
+    // when file definition found, set all parameters to default values
     for (i = 0; suffix_list[i].token != NULL; i++)
       setString(&file_list_entry->parameter[i], suffix_list[i].value);
 
     file_list_entry->redefined = TRUE;
 
-    /* mark config file token as well known from default config */
+    // mark config file token as well known from default config
     setHashEntry(setup_file_hash, file_list_entry->token, known_token_value);
   }
 
-  /* check for config tokens that can be build by base token and suffixes */
+  // check for config tokens that can be build by base token and suffixes
   for (i = 0; suffix_list[i].token != NULL; i++)
   {
     char *token = getStringCat2(file_list_entry->token, suffix_list[i].token);
@@ -2688,7 +2890,7 @@ static void read_token_parameters(SetupFileHash *setup_file_hash,
     {
       setString(&file_list_entry->parameter[i], value);
 
-      /* mark config file token as well known from default config */
+      // mark config file token as well known from default config
       setHashEntry(setup_file_hash, token, known_token_value);
     }
 
@@ -2760,22 +2962,19 @@ static void LoadArtworkConfigFromFilename(struct ArtworkListInfo *artwork_info,
   int num_ext2_suffixes = artwork_info->num_ext2_suffixes;
   int num_ext3_suffixes = artwork_info->num_ext3_suffixes;
   int num_ignore_tokens = artwork_info->num_ignore_tokens;
-  SetupFileHash *setup_file_hash, *valid_file_hash;
+  SetupFileHash *setup_file_hash, *valid_file_hash, *valid_file_hash_tmp;
   SetupFileHash *extra_file_hash, *empty_file_hash;
   char *known_token_value = KNOWN_TOKEN_VALUE;
+  char *base_token_value = UNDEFINED_FILENAME;
   int i, j, k, l;
 
   if (filename == NULL)
     return;
 
-#if 0
-  printf("LoadArtworkConfigFromFilename '%s' ...\n", filename);
-#endif
-
   if ((setup_file_hash = loadSetupFileHash(filename)) == NULL)
     return;
 
-  /* separate valid (defined) from empty (undefined) config token values */
+  // separate valid (defined) from empty (undefined) config token values
   valid_file_hash = newSetupFileHash();
   empty_file_hash = newSetupFileHash();
   BEGIN_HASH_ITERATION(setup_file_hash, itr)
@@ -2787,11 +2986,23 @@ static void LoadArtworkConfigFromFilename(struct ArtworkListInfo *artwork_info,
   }
   END_HASH_ITERATION(setup_file_hash, itr)
 
-  /* at this point, we do not need the setup file hash anymore -- free it */
+  // at this point, we do not need the setup file hash anymore -- free it
   freeSetupFileHash(setup_file_hash);
 
-  /* map deprecated to current tokens (using prefix match and replace) */
+  // prevent changing hash while iterating over it by using a temporary copy
+  valid_file_hash_tmp = newSetupFileHash();
   BEGIN_HASH_ITERATION(valid_file_hash, itr)
+  {
+    setHashEntry(valid_file_hash_tmp,
+                HASH_ITERATION_TOKEN(itr),
+                HASH_ITERATION_VALUE(itr));
+  }
+  END_HASH_ITERATION(valid_file_hash, itr)
+
+  // (iterate over same temporary hash, as modifications are independent)
+
+  // map deprecated to current tokens (using prefix match and replace)
+  BEGIN_HASH_ITERATION(valid_file_hash_tmp, itr)
   {
     char *token = HASH_ITERATION_TOKEN(itr);
     char *mapped_token = get_mapped_token(token);
@@ -2800,26 +3011,46 @@ static void LoadArtworkConfigFromFilename(struct ArtworkListInfo *artwork_info,
     {
       char *value = HASH_ITERATION_VALUE(itr);
 
-      /* add mapped token */
+      // add mapped token
       setHashEntry(valid_file_hash, mapped_token, value);
 
-      /* ignore old token (by setting it to "known" keyword) */
+      // ignore old token (by setting it to "known" keyword)
       setHashEntry(valid_file_hash, token, known_token_value);
 
       free(mapped_token);
     }
   }
-  END_HASH_ITERATION(valid_file_hash, itr)
+  END_HASH_ITERATION(valid_file_hash_tmp, itr)
+
+  // add special base tokens (using prefix match and replace)
+  BEGIN_HASH_ITERATION(valid_file_hash_tmp, itr)
+  {
+    char *token = HASH_ITERATION_TOKEN(itr);
+    char *base_token = get_special_base_token(artwork_info, token);
+
+    if (base_token != NULL)
+    {
+      // add base token only if it does not already exist
+      if (getHashEntry(valid_file_hash, base_token) == NULL)
+       setHashEntry(valid_file_hash, base_token, base_token_value);
+
+      free(base_token);
+    }
+  }
+  END_HASH_ITERATION(valid_file_hash_tmp, itr)
 
-  /* read parameters for all known config file tokens */
+  // free temporary hash used for iteration
+  freeSetupFileHash(valid_file_hash_tmp);
+
+  // read parameters for all known config file tokens
   for (i = 0; i < num_file_list_entries; i++)
     read_token_parameters(valid_file_hash, suffix_list, &file_list[i]);
 
-  /* set all tokens that can be ignored here to "known" keyword */
+  // set all tokens that can be ignored here to "known" keyword
   for (i = 0; i < num_ignore_tokens; i++)
     setHashEntry(valid_file_hash, ignore_tokens[i], known_token_value);
 
-  /* copy all unknown config file tokens to extra config hash */
+  // copy all unknown config file tokens to extra config hash
   extra_file_hash = newSetupFileHash();
   BEGIN_HASH_ITERATION(valid_file_hash, itr)
   {
@@ -2830,10 +3061,10 @@ static void LoadArtworkConfigFromFilename(struct ArtworkListInfo *artwork_info,
   }
   END_HASH_ITERATION(valid_file_hash, itr)
 
-  /* at this point, we do not need the valid file hash anymore -- free it */
+  // at this point, we do not need the valid file hash anymore -- free it
   freeSetupFileHash(valid_file_hash);
 
-  /* now try to determine valid, dynamically defined config tokens */
+  // now try to determine valid, dynamically defined config tokens
 
   BEGIN_HASH_ITERATION(extra_file_hash, itr)
   {
@@ -2854,11 +3085,7 @@ static void LoadArtworkConfigFromFilename(struct ArtworkListInfo *artwork_info,
     boolean base_prefix_found = FALSE;
     boolean parameter_suffix_found = FALSE;
 
-#if 0
-    printf("::: examining '%s' -> '%s'\n", token, HASH_ITERATION_VALUE(itr));
-#endif
-
-    /* skip all parameter definitions (handled by read_token_parameters()) */
+    // skip all parameter definitions (handled by read_token_parameters())
     for (i = 0; i < num_suffix_list_entries && !parameter_suffix_found; i++)
     {
       int len_suffix = strlen(suffix_list[i].token);
@@ -2870,7 +3097,7 @@ static void LoadArtworkConfigFromFilename(struct ArtworkListInfo *artwork_info,
     if (parameter_suffix_found)
       continue;
 
-    /* ---------- step 0: search for matching base prefix ---------- */
+    // ---------- step 0: search for matching base prefix ----------
 
     start_pos = 0;
     for (i = 0; i < num_base_prefixes && !base_prefix_found; i++)
@@ -2893,20 +3120,10 @@ static void LoadArtworkConfigFromFilename(struct ArtworkListInfo *artwork_info,
 
       base_index = i;
 
-#if 0
-      if (IS_PARENT_PROCESS())
-       printf("===> MATCH: '%s', '%s'\n", token, base_prefix);
-#endif
-
-      if (start_pos + len_base_prefix == len_token)    /* exact match */
+      if (start_pos + len_base_prefix == len_token)    // exact match
       {
        exact_match = TRUE;
 
-#if 0
-       if (IS_PARENT_PROCESS())
-         printf("===> EXACT MATCH: '%s', '%s'\n", token, base_prefix);
-#endif
-
        add_dynamic_file_list_entry(dynamic_file_list,
                                    num_dynamic_file_list_entries,
                                    extra_file_hash,
@@ -2920,12 +3137,7 @@ static void LoadArtworkConfigFromFilename(struct ArtworkListInfo *artwork_info,
        continue;
       }
 
-#if 0
-      if (IS_PARENT_PROCESS())
-       printf("---> examining token '%s': search 1st suffix ...\n", token);
-#endif
-
-      /* ---------- step 1: search for matching first suffix ---------- */
+      // ---------- step 1: search for matching first suffix ----------
 
       start_pos += len_base_prefix;
       for (j = 0; j < num_ext1_suffixes && !ext1_suffix_found; j++)
@@ -2940,20 +3152,10 @@ static void LoadArtworkConfigFromFilename(struct ArtworkListInfo *artwork_info,
 
        ext1_index = j;
 
-#if 0
-       if (IS_PARENT_PROCESS())
-         printf("===> MATCH: '%s', '%s'\n", token, ext1_suffix);
-#endif
-
-       if (start_pos + len_ext1_suffix == len_token)   /* exact match */
+       if (start_pos + len_ext1_suffix == len_token)   // exact match
        {
          exact_match = TRUE;
 
-#if 0
-       if (IS_PARENT_PROCESS())
-         printf("===> EXACT MATCH: '%s', '%s'\n", token, ext1_suffix);
-#endif
-
          add_dynamic_file_list_entry(dynamic_file_list,
                                      num_dynamic_file_list_entries,
                                      extra_file_hash,
@@ -2973,12 +3175,7 @@ static void LoadArtworkConfigFromFilename(struct ArtworkListInfo *artwork_info,
       if (exact_match)
        break;
 
-#if 0
-      if (IS_PARENT_PROCESS())
-       printf("---> examining token '%s': search 2nd suffix ...\n", token);
-#endif
-
-      /* ---------- step 2: search for matching second suffix ---------- */
+      // ---------- step 2: search for matching second suffix ----------
 
       for (k = 0; k < num_ext2_suffixes && !ext2_suffix_found; k++)
       {
@@ -2992,20 +3189,10 @@ static void LoadArtworkConfigFromFilename(struct ArtworkListInfo *artwork_info,
 
        ext2_index = k;
 
-#if 0
-       if (IS_PARENT_PROCESS())
-         printf("===> MATCH: '%s', '%s'\n", token, ext2_suffix);
-#endif
-
-       if (start_pos + len_ext2_suffix == len_token)   /* exact match */
+       if (start_pos + len_ext2_suffix == len_token)   // exact match
        {
          exact_match = TRUE;
 
-#if 0
-         if (IS_PARENT_PROCESS())
-           printf("===> EXACT MATCH: '%s', '%s'\n", token, ext2_suffix);
-#endif
-
          add_dynamic_file_list_entry(dynamic_file_list,
                                      num_dynamic_file_list_entries,
                                      extra_file_hash,
@@ -3025,12 +3212,7 @@ static void LoadArtworkConfigFromFilename(struct ArtworkListInfo *artwork_info,
       if (exact_match)
        break;
 
-#if 0
-      if (IS_PARENT_PROCESS())
-       printf("---> examining token '%s': search 3rd suffix ...\n",token);
-#endif
-
-      /* ---------- step 3: search for matching third suffix ---------- */
+      // ---------- step 3: search for matching third suffix ----------
 
       for (l = 0; l < num_ext3_suffixes && !ext3_suffix_found; l++)
       {
@@ -3044,20 +3226,10 @@ static void LoadArtworkConfigFromFilename(struct ArtworkListInfo *artwork_info,
 
        ext3_index = l;
 
-#if 0
-       if (IS_PARENT_PROCESS())
-         printf("===> MATCH: '%s', '%s'\n", token, ext3_suffix);
-#endif
-
-       if (start_pos + len_ext3_suffix == len_token) /* exact match */
+       if (start_pos + len_ext3_suffix == len_token) // exact match
        {
          exact_match = TRUE;
 
-#if 0
-         if (IS_PARENT_PROCESS())
-           printf("===> EXACT MATCH: '%s', '%s'\n", token, ext3_suffix);
-#endif
-
          add_dynamic_file_list_entry(dynamic_file_list,
                                      num_dynamic_file_list_entries,
                                      extra_file_hash,
@@ -3089,8 +3261,8 @@ static void LoadArtworkConfigFromFilename(struct ArtworkListInfo *artwork_info,
     boolean unknown_tokens_found = FALSE;
     boolean undefined_values_found = (hashtable_count(empty_file_hash) != 0);
 
-    if ((setup_file_list = loadSetupFileList(filename)) == NULL)
-      Error(ERR_EXIT, "loadSetupFileHash works, but loadSetupFileList fails");
+    // list may be NULL for empty artwork config files
+    setup_file_list = loadSetupFileList(filename);
 
     BEGIN_HASH_ITERATION(extra_file_hash, itr)
     {
@@ -3157,17 +3329,6 @@ static void LoadArtworkConfigFromFilename(struct ArtworkListInfo *artwork_info,
 
   freeSetupFileHash(extra_file_hash);
   freeSetupFileHash(empty_file_hash);
-
-#if 0
-  for (i = 0; i < num_file_list_entries; i++)
-  {
-    printf("'%s' ", file_list[i].token);
-    if (file_list[i].filename)
-      printf("-> '%s'\n", file_list[i].filename);
-    else
-      printf("-> UNDEFINED [-> '%s']\n", file_list[i].default_filename);
-  }
-#endif
 }
 
 void LoadArtworkConfig(struct ArtworkListInfo *artwork_info)
@@ -3181,7 +3342,7 @@ void LoadArtworkConfig(struct ArtworkListInfo *artwork_info)
   DrawInitText("Loading artwork config", 120, FC_GREEN);
   DrawInitText(ARTWORKINFO_FILENAME(artwork_info->type), 150, FC_YELLOW);
 
-  /* always start with reliable default values */
+  // always start with reliable default values
   for (i = 0; i < num_file_list_entries; i++)
   {
     setString(&file_list[i].filename, file_list[i].default_filename);
@@ -3193,7 +3354,7 @@ void LoadArtworkConfig(struct ArtworkListInfo *artwork_info)
     file_list[i].fallback_to_default = FALSE;
   }
 
-  /* free previous dynamic artwork file array */
+  // free previous dynamic artwork file array
   if (artwork_info->dynamic_file_list != NULL)
   {
     for (i = 0; i < artwork_info->num_dynamic_file_list_entries; i++)
@@ -3210,7 +3371,7 @@ void LoadArtworkConfig(struct ArtworkListInfo *artwork_info)
                          &artwork_info->num_dynamic_file_list_entries);
   }
 
-  /* free previous property mapping */
+  // free previous property mapping
   if (artwork_info->property_mapping != NULL)
   {
     free(artwork_info->property_mapping);
@@ -3219,21 +3380,11 @@ void LoadArtworkConfig(struct ArtworkListInfo *artwork_info)
     artwork_info->num_property_mapping_entries = 0;
   }
 
-#if 1
   if (!GFX_OVERRIDE_ARTWORK(artwork_info->type))
-#else
-  if (!SETUP_OVERRIDE_ARTWORK(setup, artwork_info->type))
-#endif
   {
-    /* first look for special artwork configured in level series config */
+    // first look for special artwork configured in level series config
     filename_base = getCustomArtworkLevelConfigFilename(artwork_info->type);
 
-#if 0
-    printf("::: filename_base == '%s' [%s, %s]\n", filename_base,
-          leveldir_current->graphics_set,
-          leveldir_current->graphics_path);
-#endif
-
     if (fileExists(filename_base))
       LoadArtworkConfigFromFilename(artwork_info, filename_base);
   }
@@ -3280,13 +3431,13 @@ static void replaceArtworkListEntry(struct ArtworkListInfo *artwork_info,
 
     basename = file_list_entry->default_filename;
 
-    /* fail for cloned default artwork that has no default filename defined */
+    // fail for cloned default artwork that has no default filename defined
     if (file_list_entry->default_is_cloned &&
        strEqual(basename, UNDEFINED_FILENAME))
     {
       int error_mode = ERR_WARN;
 
-      /* we can get away without sounds and music, but not without graphics */
+      // 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;
 
@@ -3296,7 +3447,7 @@ static void replaceArtworkListEntry(struct ArtworkListInfo *artwork_info,
       return;
     }
 
-    /* dynamic artwork has no default filename / skip empty default artwork */
+    // dynamic artwork has no default filename / skip empty default artwork
     if (basename == NULL || strEqual(basename, UNDEFINED_FILENAME))
       return;
 
@@ -3310,7 +3461,7 @@ static void replaceArtworkListEntry(struct ArtworkListInfo *artwork_info,
     {
       int error_mode = ERR_WARN;
 
-      /* we can get away without sounds and music, but not without graphics */
+      // 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;
 
@@ -3320,30 +3471,22 @@ static void replaceArtworkListEntry(struct ArtworkListInfo *artwork_info,
     }
   }
 
-  /* check if the old and the new artwork file are the same */
+  // check if the old and the new artwork file are the same
   if (*listnode && strEqual((*listnode)->source_filename, filename))
   {
-    /* The old and new artwork are the same (have the same filename and path).
-       This usually means that this artwork does not exist in this artwork set
-       and a fallback to the existing artwork is done. */
-
-#if 0
-    printf("[artwork '%s' already exists (same list entry)]\n", filename);
-#endif
+    // The old and new artwork are the same (have the same filename and path).
+    // This usually means that this artwork does not exist in this artwork set
+    // and a fallback to the existing artwork is done.
 
     return;
   }
 
-  /* delete existing artwork file entry */
+  // delete existing artwork file entry
   deleteArtworkListEntry(artwork_info, listnode);
 
-  /* check if the new artwork file already exists in the list of artworks */
+  // check if the new artwork file already exists in the list of artwork
   if ((node = getNodeFromKey(artwork_info->content_list, filename)) != NULL)
   {
-#if 0
-      printf("[artwork '%s' already exists (other list entry)]\n", filename);
-#endif
-
       *listnode = (struct ListNodeInfo *)node->content;
       (*listnode)->num_references++;
 
@@ -3355,10 +3498,7 @@ static void replaceArtworkListEntry(struct ArtworkListInfo *artwork_info,
 
   if ((*listnode = artwork_info->load_artwork(filename)) != NULL)
   {
-#if 0
-      printf("[adding new artwork '%s']\n", filename);
-#endif
-
+    // add new artwork file entry to the list of artwork files
     (*listnode)->num_references = 1;
     addNodeToList(&artwork_info->content_list, (*listnode)->source_filename,
                  *listnode);
@@ -3367,7 +3507,7 @@ static void replaceArtworkListEntry(struct ArtworkListInfo *artwork_info,
   {
     int error_mode = ERR_WARN;
 
-    /* we can get away without sounds and music, but not without graphics */
+    // we can get away without sounds and music, but not without graphics
     if (artwork_info->type == ARTWORK_TYPE_GRAPHICS)
       error_mode = ERR_EXIT;
 
@@ -3381,10 +3521,6 @@ static void LoadCustomArtwork(struct ArtworkListInfo *artwork_info,
                              struct ListNodeInfo **listnode,
                              struct FileInfo *file_list_entry)
 {
-#if 0
-  printf("GOT CUSTOM ARTWORK FILE '%s'\n", file_list_entry->filename);
-#endif
-
   if (strEqual(file_list_entry->filename, UNDEFINED_FILENAME))
   {
     deleteArtworkListEntry(artwork_info, listnode);
@@ -3451,76 +3587,89 @@ void FreeCustomArtworkLists(struct ArtworkListInfo *artwork_info)
 }
 
 
-/* ------------------------------------------------------------------------- */
-/* functions only needed for non-Unix (non-command-line) systems             */
-/* (MS-DOS only; SDL/Windows creates files "stdout.txt" and "stderr.txt")    */
-/* (now also added for Windows, to create files in user data directory)      */
-/* ------------------------------------------------------------------------- */
+// ----------------------------------------------------------------------------
+// functions only needed for non-Unix (non-command-line) systems
+// (MS-DOS only; SDL/Windows creates files "stdout.txt" and "stderr.txt")
+// (now also added for Windows, to create files in user data directory)
+// ----------------------------------------------------------------------------
 
-char *getErrorFilename(char *basename)
+char *getLogFilename(char *basename)
 {
   return getPath2(getUserGameDataDir(), basename);
 }
 
-void openErrorFile()
+void OpenLogFiles(void)
 {
+  int i;
+
   InitUserDataDirectory();
 
-  if ((program.error_file = fopen(program.error_filename, MODE_WRITE)) == NULL)
+  for (i = 0; i < NUM_LOGS; i++)
   {
-    program.error_file = stderr;
+    if ((program.log_file[i] = fopen(program.log_filename[i], MODE_WRITE))
+       == NULL)
+    {
+      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));
+    }
 
-    Error(ERR_WARN, "cannot open file '%s' for writing: %s",
-         program.error_filename, strerror(errno));
+    // output should be unbuffered so it is not truncated in a crash
+    setbuf(program.log_file[i], NULL);
   }
 }
 
-void closeErrorFile()
+void CloseLogFiles(void)
 {
-  if (program.error_file != stderr)    /* do not close stream 'stderr' */
-    fclose(program.error_file);
+  int i;
+
+  for (i = 0; i < NUM_LOGS; i++)
+    if (program.log_file[i] != program.log_file_default[i])
+      fclose(program.log_file[i]);
 }
 
-void dumpErrorFile()
+void DumpLogFile(int nr)
 {
-  FILE *error_file = fopen(program.error_filename, MODE_READ);
+  FILE *log_file = fopen(program.log_filename[nr], MODE_READ);
 
-  if (error_file != NULL)
-  {
-    while (!feof(error_file))
-      fputc(fgetc(error_file), stderr);
+  if (log_file == NULL)
+    return;
 
-    fclose(error_file);
-  }
+  while (!feof(log_file))
+    fputc(fgetc(log_file), stdout);
+
+  fclose(log_file);
 }
 
-void NotifyUserAboutErrorFile()
+void NotifyUserAboutErrorFile(void)
 {
 #if defined(PLATFORM_WIN32)
   char *title_text = getStringCat2(program.program_title, " Error Message");
   char *error_text = getStringCat2("The program was aborted due to an error; "
                                   "for details, see the following error file:"
-                                  STRING_NEWLINE, program.error_filename);
+                                  STRING_NEWLINE,
+                                  program.log_filename[LOG_ERR_ID]);
 
   MessageBox(NULL, error_text, title_text, MB_OK);
 #endif
 }
 
 
-/* ------------------------------------------------------------------------- */
-/* the following is only for debugging purpose and normally not used         */
-/* ------------------------------------------------------------------------- */
+// ----------------------------------------------------------------------------
+// the following is only for debugging purpose and normally not used
+// ----------------------------------------------------------------------------
 
 #if DEBUG
 
-#define DEBUG_PRINT_INIT_TIMESTAMPS            TRUE
+#define DEBUG_PRINT_INIT_TIMESTAMPS            FALSE
 #define DEBUG_PRINT_INIT_TIMESTAMPS_DEPTH      10
 
 #define DEBUG_NUM_TIMESTAMPS                   10
 #define DEBUG_TIME_IN_MICROSECONDS             0
 
 #if DEBUG_TIME_IN_MICROSECONDS
-static double Counter_Microseconds()
+static double Counter_Microseconds(void)
 {
   static struct timeval base_time = { 0, 0 };
   struct timeval current_time;
@@ -3528,7 +3677,7 @@ static double Counter_Microseconds()
 
   gettimeofday(&current_time, NULL);
 
-  /* reset base time in case of wrap-around */
+  // reset base time in case of wrap-around
   if (current_time.tv_sec < base_time.tv_sec)
     base_time = current_time;
 
@@ -3536,11 +3685,11 @@ static double Counter_Microseconds()
     ((double)(current_time.tv_sec  - base_time.tv_sec)) * 1000000 +
     ((double)(current_time.tv_usec - base_time.tv_usec));
 
-  return counter;              /* return microseconds since last init */
+  return counter;              // return microseconds since last init
 }
 #endif
 
-char *debug_print_timestamp_get_padding(int padding_size)
+static char *debug_print_timestamp_get_padding(int padding_size)
 {
   static char *padding = NULL;
   int max_padding_size = 100;
@@ -3581,11 +3730,7 @@ void debug_print_timestamp(int counter_nr, char *message)
   counter[counter_nr][1] = counter[counter_nr][0];
 
   if (message)
-#if 1
     Error(ERR_DEBUG, "%s%s%s %.3f %s",
-#else
-    printf("%s%s%s %.3f %s\n",
-#endif
           debug_print_timestamp_get_padding(counter_nr * indent_size),
           message,
           debug_print_timestamp_get_padding(padding_size - strlen(message)),
@@ -3593,7 +3738,8 @@ void debug_print_timestamp(int counter_nr, char *message)
           unit);
 }
 
-void debug_print_parent_only(char *format, ...)
+#if 0
+static void debug_print_parent_only(char *format, ...)
 {
   if (!IS_PARENT_PROCESS())
     return;
@@ -3609,8 +3755,11 @@ void debug_print_parent_only(char *format, ...)
     printf("\n");
   }
 }
+#endif
 
-void print_timestamp_ext(char *message, char *mode)
+#endif // DEBUG
+
+static void print_timestamp_ext(char *message, char *mode)
 {
 #if DEBUG_PRINT_INIT_TIMESTAMPS
   static char *debug_message = NULL;
@@ -3673,5 +3822,3 @@ void print_timestamp_done(char *message)
 {
   print_timestamp_ext(message, "DONE");
 }
-
-#endif /* DEBUG */