cleanup of unnecessarily convoluted function call
[rocksndiamonds.git] / src / libgame / misc.c
index a6bef9b878af3869cedb0d7272927584606c7093..ed4bd5d62c991d7e94f72cb150ab904ee8c2935f 100644 (file)
@@ -41,8 +41,7 @@
 // logging functions
 // ----------------------------------------------------------------------------
 
-#define DUPLICATE_LOG_OUT_TO_STDOUT            TRUE
-#define DUPLICATE_LOG_ERR_TO_STDERR            TRUE
+#define DUPLICATE_LOGGING_TO_STDOUT            TRUE
 
 
 #if defined(PLATFORM_ANDROID)
@@ -94,15 +93,15 @@ static void vprintf_log(char *format, va_list ap)
 
 static void vprintf_log_nonewline(char *format, va_list ap)
 {
-  FILE *file = program.log_file[LOG_ERR_ID];
+  FILE *file = program.log_file;
 
-#if DUPLICATE_LOG_ERR_TO_STDERR
-  if (file != program.log_file_default[LOG_ERR_ID])
+#if DUPLICATE_LOGGING_TO_STDOUT
+  if (file != program.log_file_default)
   {
     va_list ap2;
     va_copy(ap2, ap);
 
-    vfprintf(program.log_file_default[LOG_ERR_ID], format, ap2);
+    vfprintf(program.log_file_default, format, ap2);
 
     va_end(ap2);
   }
@@ -113,17 +112,17 @@ static void vprintf_log_nonewline(char *format, va_list ap)
 
 static void vprintf_log(char *format, va_list ap)
 {
-  FILE *file = program.log_file[LOG_ERR_ID];
+  FILE *file = program.log_file;
   char *newline = STRING_NEWLINE;
 
-#if DUPLICATE_LOG_ERR_TO_STDERR
-  if (file != program.log_file_default[LOG_ERR_ID])
+#if DUPLICATE_LOGGING_TO_STDOUT
+  if (file != program.log_file_default)
   {
     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);
+    vfprintf(program.log_file_default, format, ap2);
+    fprintf(program.log_file_default, "%s", newline);
 
     va_end(ap2);
   }
@@ -196,15 +195,15 @@ void printf_line_with_prefix(char *prefix, char *line_chars, int line_length)
 
 static void vPrint(char *format, va_list ap)
 {
-  FILE *file = program.log_file[LOG_OUT_ID];
+  FILE *file = program.log_file;
 
-#if DUPLICATE_LOG_OUT_TO_STDOUT
-  if (file != program.log_file_default[LOG_OUT_ID])
+#if DUPLICATE_LOGGING_TO_STDOUT
+  if (file != program.log_file_default)
   {
     va_list ap2;
     va_copy(ap2, ap);
 
-    vfprintf(program.log_file_default[LOG_OUT_ID], format, ap2);
+    vfprintf(program.log_file_default, format, ap2);
 
     va_end(ap2);
   }
@@ -224,7 +223,7 @@ void Print(char *format, ...)
 
 void PrintNoLog(char *format, ...)
 {
-  FILE *file = program.log_file_default[LOG_OUT_ID];
+  FILE *file = program.log_file_default;
   va_list ap;
 
   va_start(ap, format);
@@ -549,6 +548,50 @@ boolean getTokenValueFromString(char *string, char **token, char **value)
 }
 
 
+// ----------------------------------------------------------------------------
+// UUID functions
+// ----------------------------------------------------------------------------
+
+#define UUID_BYTES             16
+#define UUID_CHARS             (UUID_BYTES * 2)
+#define UUID_LENGTH            (UUID_CHARS + 4)
+
+static unsigned int uuid_random_function(int max)
+{
+  return GetBetterRandom(max);
+}
+
+char *getUUIDExt(unsigned int (*random_function)(int max))
+{
+  static char uuid[UUID_LENGTH + 1];
+  int data[UUID_BYTES];
+  int count = 0;
+  int i;
+
+  for (i = 0; i < UUID_BYTES; i++)
+    data[i] = random_function(256);
+
+  data[6] = 0x40 | (data[6] & 0x0f);
+  data[8] = 0x80 | (data[8] & 0x3f);
+
+  for (i = 0; i < UUID_BYTES; i++)
+  {
+    sprintf(&uuid[count], "%02x", data[i]);
+    count += 2;
+
+    if (i == 3 || i == 5 || i == 7 || i == 9)
+      strcat(&uuid[count++], "-");
+  }
+
+  return uuid;
+}
+
+char *getUUID(void)
+{
+  return getUUIDExt(uuid_random_function);
+}
+
+
 // ----------------------------------------------------------------------------
 // counter functions
 // ----------------------------------------------------------------------------
@@ -577,12 +620,12 @@ static unsigned int mainCounter(int mode)
   return current_ms - base_ms;
 }
 
-void InitCounter()             // set counter back to zero
+void InitCounter(void)         // set counter back to zero
 {
   mainCounter(INIT_COUNTER);
 }
 
-unsigned int Counter() // get milliseconds since last call of InitCounter()
+unsigned int Counter(void)     // get milliseconds since last call of InitCounter()
 {
   return mainCounter(READ_COUNTER);
 }
@@ -597,8 +640,8 @@ void Delay(unsigned int delay)      // Sleep specified number of milliseconds
   sleep_milliseconds(delay);
 }
 
-boolean DelayReachedExt(unsigned int *counter_var, unsigned int delay,
-                       unsigned int actual_counter)
+boolean DelayReachedExt2(unsigned int *counter_var, unsigned int delay,
+                        unsigned int actual_counter)
 {
   if (actual_counter >= *counter_var &&
       actual_counter < *counter_var + delay)
@@ -609,34 +652,40 @@ boolean DelayReachedExt(unsigned int *counter_var, unsigned int delay,
   return TRUE;
 }
 
-boolean FrameReached(unsigned int *frame_counter_var, unsigned int frame_delay)
+boolean DelayReachedExt(DelayCounter *counter, unsigned int actual_counter)
+{
+  return DelayReachedExt2(&counter->count, counter->value, actual_counter);
+}
+
+boolean FrameReached(DelayCounter *counter)
 {
-  return DelayReachedExt(frame_counter_var, frame_delay, FrameCounter);
+  return DelayReachedExt(counter, FrameCounter);
 }
 
-boolean DelayReached(unsigned int *counter_var, unsigned int delay)
+boolean DelayReached(DelayCounter *counter)
 {
-  return DelayReachedExt(counter_var, delay, Counter());
+  return DelayReachedExt(counter, Counter());
 }
 
-void ResetDelayCounterExt(unsigned int *counter_var,
-                         unsigned int actual_counter)
+void ResetDelayCounterExt(DelayCounter *counter, unsigned int actual_counter)
 {
-  DelayReachedExt(counter_var, 0, actual_counter);
+  DelayReachedExt2(&counter->count, 0, actual_counter);
 }
 
-void ResetFrameCounter(unsigned int *frame_counter_var)
+void ResetFrameCounter(DelayCounter *counter)
 {
-  FrameReached(frame_counter_var, 0);
+  ResetDelayCounterExt(counter, FrameCounter);
 }
 
-void ResetDelayCounter(unsigned int *counter_var)
+void ResetDelayCounter(DelayCounter *counter)
 {
-  DelayReached(counter_var, 0);
+  ResetDelayCounterExt(counter, Counter());
 }
 
-int WaitUntilDelayReached(unsigned int *counter_var, unsigned int delay)
+int WaitUntilDelayReached(DelayCounter *counter)
 {
+  unsigned int *counter_var = &counter->count;
+  unsigned int delay = counter->value;
   unsigned int actual_counter;
   int skip_frames = 0;
 
@@ -667,22 +716,22 @@ int WaitUntilDelayReached(unsigned int *counter_var, unsigned int delay)
   return skip_frames;
 }
 
-void SkipUntilDelayReached(unsigned int *counter_var, unsigned int delay,
+void SkipUntilDelayReached(DelayCounter *counter,
                           int *loop_var, int last_loop_value)
 {
-  int skip_frames = WaitUntilDelayReached(counter_var, delay);
+  int skip_frames = WaitUntilDelayReached(counter);
 
 #if 0
 #if DEBUG
   if (skip_frames)
     Debug("internal:SkipUntilDelayReached",
          "%d: %d ms -> SKIP %d FRAME(S) [%d ms]",
-         *loop_var, delay,
-         skip_frames, skip_frames * delay);
+         *loop_var, counter->value,
+         skip_frames, skip_frames * counter->value);
   else
     Debug("internal:SkipUntilDelayReached",
          "%d: %d ms",
-         *loop_var, delay);
+         *loop_var, counter->value);
 #endif
 #endif
 
@@ -712,14 +761,14 @@ void SkipUntilDelayReached(unsigned int *counter_var, unsigned int delay,
 // random generator functions
 // ----------------------------------------------------------------------------
 
-unsigned int init_random_number(int nr, int seed)
+static unsigned int init_random_number_ext(int nr, int seed)
 {
   if (seed == NEW_RANDOMIZE)
   {
     // default random seed
     seed = (int)time(NULL);                    // seconds since the epoch
 
-#if !defined(PLATFORM_WIN32)
+#if !defined(PLATFORM_WINDOWS)
     // add some more randomness
     struct timeval current_time;
 
@@ -740,9 +789,32 @@ unsigned int init_random_number(int nr, int seed)
   return (unsigned int) seed;
 }
 
+static unsigned int prng_seed_gettimeofday(void)
+{
+  struct timeval current_time;
+
+  gettimeofday(&current_time, NULL);
+
+  prng_seed_bytes(&current_time, sizeof(current_time));
+
+  return 0;
+}
+
+unsigned int init_random_number(int nr, int seed)
+{
+  return (nr == RANDOM_BETTER ? prng_seed_gettimeofday() :
+         init_random_number_ext(nr, seed));
+}
+
+static unsigned int get_random_number_ext(int nr)
+{
+  return (nr == RANDOM_BETTER ? prng_get_uint() :
+         random_linux_libc(nr));
+}
+
 unsigned int get_random_number(int nr, int max)
 {
-  return (max > 0 ? random_linux_libc(nr) % max : 0);
+  return (max > 0 ? get_random_number_ext(nr) % max : 0);
 }
 
 
@@ -829,7 +901,7 @@ char *getLoginName(void)
 {
   static char *login_name = NULL;
 
-#if defined(PLATFORM_WIN32)
+#if defined(PLATFORM_WINDOWS)
   if (login_name == NULL)
   {
     unsigned long buffer_size = MAX_USERNAME_LEN + 1;
@@ -860,7 +932,7 @@ char *getRealName(void)
 {
   static char *real_name = NULL;
 
-#if defined(PLATFORM_WIN32)
+#if defined(PLATFORM_WINDOWS)
   if (real_name == NULL)
   {
     static char buffer[MAX_USERNAME_LEN + 1];
@@ -903,6 +975,38 @@ char *getFixedUserName(char *name)
   return name_new;
 }
 
+char *getDefaultUserName(int nr)
+{
+  static char *user_name[MAX_PLAYER_NAMES] = { NULL };
+
+  nr = MIN(MAX(0, nr), MAX_PLAYER_NAMES - 1);
+
+  if (user_name[nr] == NULL)
+  {
+    user_name[nr] = (nr == 0 ? getLoginName() : EMPTY_PLAYER_NAME);
+    user_name[nr] = getFixedUserName(user_name[nr]);
+  }
+
+  return user_name[nr];
+}
+
+char *getTimestampFromEpoch(time_t epoch_seconds)
+{
+  struct tm *now = localtime(&epoch_seconds);
+  static char timestamp[20];
+
+  sprintf(timestamp, "%04d%02d%02d-%02d%02d%02d",
+         now->tm_year + 1900, now->tm_mon + 1, now->tm_mday,
+         now->tm_hour, now->tm_min, now->tm_sec);
+
+  return timestamp;
+}
+
+char *getCurrentTimestamp(void)
+{
+  return getTimestampFromEpoch(time(NULL));
+}
+
 time_t getFileTimestampEpochSeconds(char *filename)
 {
   struct stat file_status;
@@ -984,7 +1088,9 @@ char *getBasePath(char *filename)
 // various string functions
 // ----------------------------------------------------------------------------
 
-char *getStringCat2WithSeparator(char *s1, char *s2, char *sep)
+char *getStringCat2WithSeparator(const char *s1,
+                                const char *s2,
+                                const char *sep)
 {
   if (s1 == NULL || s2 == NULL || sep == NULL)
     return NULL;
@@ -997,7 +1103,10 @@ char *getStringCat2WithSeparator(char *s1, char *s2, char *sep)
   return complete_string;
 }
 
-char *getStringCat3WithSeparator(char *s1, char *s2, char *s3, char *sep)
+char *getStringCat3WithSeparator(const char *s1,
+                                const char *s2,
+                                const char *s3,
+                                const char *sep)
 {
   if (s1 == NULL || s2 == NULL || s3 == NULL || sep == NULL)
     return NULL;
@@ -1011,17 +1120,17 @@ char *getStringCat3WithSeparator(char *s1, char *s2, char *s3, char *sep)
   return complete_string;
 }
 
-char *getStringCat2(char *s1, char *s2)
+char *getStringCat2(const char *s1, const char *s2)
 {
   return getStringCat2WithSeparator(s1, s2, "");
 }
 
-char *getStringCat3(char *s1, char *s2, char *s3)
+char *getStringCat3(const char *s1, const char *s2, const char *s3)
 {
   return getStringCat3WithSeparator(s1, s2, s3, "");
 }
 
-char *getPath2(char *path1, char *path2)
+char *getPath2(const char *path1, const char *path2)
 {
 #if defined(PLATFORM_ANDROID)
   // workaround for reading from assets directory -- skip "." subdirs in path
@@ -1034,7 +1143,7 @@ char *getPath2(char *path1, char *path2)
   return getStringCat2WithSeparator(path1, path2, STRING_PATH_SEPARATOR);
 }
 
-char *getPath3(char *path1, char *path2, char *path3)
+char *getPath3(const char *path1, const char *path2, const char *path3)
 {
 #if defined(PLATFORM_ANDROID)
   // workaround for reading from assets directory -- skip "." subdirs in path
@@ -1062,12 +1171,12 @@ static char *getPngOrPcxIfNotExists(char *filename)
   return filename;
 }
 
-char *getImg2(char *path1, char *path2)
+char *getImg2(const char *path1, const char *path2)
 {
   return getPngOrPcxIfNotExists(getPath2(path1, path2));
 }
 
-char *getImg3(char *path1, char *path2, char *path3)
+char *getImg3(const char *path1, const char *path2, const char *path3)
 {
   return getPngOrPcxIfNotExists(getPath3(path1, path2, path3));
 }
@@ -1111,6 +1220,18 @@ char *getStringCopyNStatic(const char *s, int n)
   return s_copy;
 }
 
+char *getStringToUpper(const char *s)
+{
+  char *s_copy = checked_malloc(strlen(s) + 1);
+  char *s_ptr = s_copy;
+
+  while (*s)
+    *s_ptr++ = toupper(*s++);
+  *s_ptr = '\0';
+
+  return s_copy;
+}
+
 char *getStringToLower(const char *s)
 {
   char *s_copy = checked_malloc(strlen(s) + 1);
@@ -1123,14 +1244,353 @@ char *getStringToLower(const char *s)
   return s_copy;
 }
 
-void setString(char **old_value, char *new_value)
+static char *getStringVPrint(char *format, va_list ap)
+{
+  char s[MAX_LINE_LEN];
+
+  vsnprintf(s, MAX_LINE_LEN, format, ap);      // may truncate output string
+
+  return getStringCopy(s);
+}
+
+char *getStringPrint(char *format, ...)
+{
+  va_list ap;
+  char *s;
+
+  va_start(ap, format);
+  s = getStringVPrint(format, ap);
+  va_end(ap);
+
+  return s;
+}
+
+void setStringPrint(char **s, char *format, ...)
+{
+  va_list ap;
+
+  checked_free(*s);
+
+  va_start(ap, format);
+  *s = getStringVPrint(format, ap);
+  va_end(ap);
+}
+
+void appendStringPrint(char **s_old, char *format, ...)
+{
+  va_list ap;
+  char *s_new;
+
+  va_start(ap, format);
+  s_new = getStringVPrint(format, ap);
+  va_end(ap);
+
+  char *s_combined = getStringCat2(*s_old, s_new);
+
+  checked_free(*s_old);
+  checked_free(s_new);
+
+  *s_old = s_combined;
+}
+
+void setString(char **old_value, const char *new_value)
 {
   checked_free(*old_value);
 
   *old_value = getStringCopy(new_value);
 }
 
-boolean strEqual(char *s1, char *s2)
+char **getSplitStringArray(const char *s, const char *separators, int max_tokens)
+{
+  const char *s_ptr, *s_last = s;
+  byte separator_table[256] = { FALSE };
+  int num_tokens;
+  char **tokens = NULL;
+
+  if (s == NULL)
+    return NULL;
+
+  if (separators == NULL)
+    return NULL;
+
+  if (max_tokens < 1)
+    max_tokens = INT_MAX;
+
+  // if string is empty, return empty array
+  if (*s == '\0')
+  {
+    tokens = checked_malloc(sizeof(char *));
+    tokens[0] = NULL;
+
+    return tokens;
+  }
+
+  // initialize separator table for all characters in separators string
+  for (s_ptr = separators; *s_ptr != '\0'; s_ptr++)
+    separator_table[*(byte *)s_ptr] = TRUE;
+
+  // count number of tokens in string
+  for (num_tokens = 1, s_ptr = s; *s_ptr != '\0'; s_ptr++)
+    if (separator_table[*(byte *)s_ptr] && num_tokens < max_tokens)
+      num_tokens++;
+
+  // allocate array for determined number of tokens
+  tokens = checked_malloc((num_tokens + 1) * sizeof(char *));
+
+  // copy all but last separated sub-strings to array
+  for (num_tokens = 0, s_ptr = s; *s_ptr != '\0'; s_ptr++)
+  {
+    if (separator_table[*(byte *)s_ptr] && num_tokens + 1 < max_tokens)
+    {
+      tokens[num_tokens++] = getStringCopyN(s_last, s_ptr - s_last);
+      s_last = s_ptr + 1;
+    }
+  }
+
+  // copy last separated sub-string to array
+  tokens[num_tokens++] = getStringCopyN(s_last, s_ptr - s_last);
+
+  // terminate array
+  tokens[num_tokens] = NULL;
+
+  return tokens;
+}
+
+int getStringArrayLength(char **s_array)
+{
+  int num_strings = 0;
+
+  if (s_array == NULL)
+    return 0;
+
+  while (s_array[num_strings] != NULL)
+    num_strings++;
+
+  return num_strings;
+}
+
+void freeStringArray(char **s_array)
+{
+  int i;
+
+  if (s_array == NULL)
+    return;
+
+  for (i = 0; s_array[i] != NULL; i++)
+    checked_free(s_array[i]);
+
+  checked_free(s_array);
+}
+
+char *getEscapedString(const char *s)
+{
+  const unsigned char *s_ptr = (unsigned char *)s;
+  char *s_escaped;
+  char *s_escaped_ptr;
+
+  if (s == NULL)
+    return NULL;
+
+  /* Each source byte needs maximally four target chars (\777) */
+  s_escaped = checked_malloc(strlen(s) * 4 + 1);
+  s_escaped_ptr = s_escaped;
+
+  while (*s_ptr != '\0')
+  {
+    switch (*s_ptr)
+    {
+      case '\b':
+       *s_escaped_ptr++ = '\\';
+       *s_escaped_ptr++ = 'b';
+       break;
+
+      case '\f':
+       *s_escaped_ptr++ = '\\';
+       *s_escaped_ptr++ = 'f';
+       break;
+
+      case '\n':
+       *s_escaped_ptr++ = '\\';
+       *s_escaped_ptr++ = 'n';
+       break;
+
+      case '\r':
+       *s_escaped_ptr++ = '\\';
+       *s_escaped_ptr++ = 'r';
+       break;
+
+      case '\t':
+       *s_escaped_ptr++ = '\\';
+       *s_escaped_ptr++ = 't';
+       break;
+
+      case '\v':
+       *s_escaped_ptr++ = '\\';
+       *s_escaped_ptr++ = 'v';
+       break;
+
+      case '\\':
+       *s_escaped_ptr++ = '\\';
+       *s_escaped_ptr++ = '\\';
+       break;
+
+      case '"':
+       *s_escaped_ptr++ = '\\';
+       *s_escaped_ptr++ = '"';
+       break;
+
+      default:
+       if ((*s_ptr < ' ') || (*s_ptr >= 0177))
+       {
+         *s_escaped_ptr++ = '\\';
+         *s_escaped_ptr++ = '0' + (((*s_ptr) >> 6) & 07);
+         *s_escaped_ptr++ = '0' + (((*s_ptr) >> 3) & 07);
+         *s_escaped_ptr++ = '0' + ( (*s_ptr)       & 07);
+       }
+       else
+       {
+         *s_escaped_ptr++ = *s_ptr;
+       }
+       break;
+    }
+
+    s_ptr++;
+  }
+
+  *s_escaped_ptr = '\0';
+
+  return s_escaped;
+}
+
+char *getUnescapedString(const char *s)
+{
+  const char *s_ptr = s;
+  const char *octal_ptr;
+  char *s_unescaped;
+  char *s_unescaped_ptr;
+
+  if (s == NULL)
+    return NULL;
+
+  s_unescaped = checked_malloc(strlen(s) + 1);
+  s_unescaped_ptr = s_unescaped;
+
+  while (*s_ptr != '\0')
+  {
+    if (*s_ptr == '\\')
+    {
+      s_ptr++;
+
+      switch (*s_ptr)
+      {
+       case '\0':
+         Warn("getUnescapedString: trailing \\");
+         goto out;
+
+       case '0':
+       case '1':
+       case '2':
+       case '3':
+       case '4':
+       case '5':
+       case '6':
+       case '7':
+         *s_unescaped_ptr = 0;
+         octal_ptr = s_ptr;
+
+         while (s_ptr < octal_ptr + 3 && *s_ptr >= '0' && *s_ptr <= '7')
+         {
+           *s_unescaped_ptr = (*s_unescaped_ptr * 8) + (*s_ptr - '0');
+           s_ptr++;
+         }
+
+         s_unescaped_ptr++;
+         s_ptr--;
+         break;
+
+       case 'b':
+         *s_unescaped_ptr++ = '\b';
+         break;
+
+       case 'f':
+         *s_unescaped_ptr++ = '\f';
+         break;
+
+       case 'n':
+         *s_unescaped_ptr++ = '\n';
+         break;
+
+       case 'r':
+         *s_unescaped_ptr++ = '\r';
+         break;
+
+       case 't':
+         *s_unescaped_ptr++ = '\t';
+         break;
+
+       case 'v':
+         *s_unescaped_ptr++ = '\v';
+         break;
+
+       default:
+         /* also handles \" and \\ */
+         *s_unescaped_ptr++ = *s_ptr;
+         break;
+      }
+    }
+    else
+    {
+      *s_unescaped_ptr++ = *s_ptr;
+    }
+
+    s_ptr++;
+  }
+
+ out:
+  *s_unescaped_ptr = '\0';
+
+  return s_unescaped;
+}
+
+char *chugString(char *s)
+{
+  if (s == NULL)
+    return NULL;
+
+  char *start;
+
+  for (start = (char *)s; *start && isspace(*start); start++)
+    ;
+
+  memmove(s, start, strlen(start) + 1);
+
+  return s;
+}
+
+char *chompString(char *s)
+{
+  if (s == NULL)
+    return NULL;
+
+  int len = strlen(s);
+
+  while (len--)
+  {
+    if (isspace(s[len]))
+      s[len] = '\0';
+    else
+      break;
+  }
+
+  return s;
+}
+
+char *stripString(char *s)
+{
+  return chugString(chompString(s));
+}
+
+boolean strEqual(const char *s1, const char *s2)
 {
   return (s1 == NULL && s2 == NULL ? TRUE  :
          s1 == NULL && s2 != NULL ? FALSE :
@@ -1138,7 +1598,7 @@ boolean strEqual(char *s1, char *s2)
          strcmp(s1, s2) == 0);
 }
 
-boolean strEqualN(char *s1, char *s2, int n)
+boolean strEqualN(const char *s1, const char *s2, int n)
 {
   return (s1 == NULL && s2 == NULL ? TRUE  :
          s1 == NULL && s2 != NULL ? FALSE :
@@ -1146,7 +1606,23 @@ boolean strEqualN(char *s1, char *s2, int n)
          strncmp(s1, s2, n) == 0);
 }
 
-boolean strPrefix(char *s, char *prefix)
+boolean strEqualCase(const char *s1, const char *s2)
+{
+  return (s1 == NULL && s2 == NULL ? TRUE  :
+         s1 == NULL && s2 != NULL ? FALSE :
+         s1 != NULL && s2 == NULL ? FALSE :
+         strcasecmp(s1, s2) == 0);
+}
+
+boolean strEqualCaseN(const char *s1, const char *s2, int n)
+{
+  return (s1 == NULL && s2 == NULL ? TRUE  :
+         s1 == NULL && s2 != NULL ? FALSE :
+         s1 != NULL && s2 == NULL ? FALSE :
+         strncasecmp(s1, s2, n) == 0);
+}
+
+boolean strPrefix(const char *s, const char *prefix)
 {
   return (s == NULL && prefix == NULL ? TRUE  :
          s == NULL && prefix != NULL ? FALSE :
@@ -1154,7 +1630,7 @@ boolean strPrefix(char *s, char *prefix)
          strncmp(s, prefix, strlen(prefix)) == 0);
 }
 
-boolean strSuffix(char *s, char *suffix)
+boolean strSuffix(const char *s, const char *suffix)
 {
   return (s == NULL && suffix == NULL ? TRUE  :
          s == NULL && suffix != NULL ? FALSE :
@@ -1163,7 +1639,7 @@ boolean strSuffix(char *s, char *suffix)
          strcmp(&s[strlen(s) - strlen(suffix)], suffix) == 0);
 }
 
-boolean strPrefixLower(char *s, char *prefix)
+boolean strPrefixLower(const char *s, const char *prefix)
 {
   char *s_lower = getStringToLower(s);
   boolean match = strPrefix(s_lower, prefix);
@@ -1173,7 +1649,7 @@ boolean strPrefixLower(char *s, char *prefix)
   return match;
 }
 
-boolean strSuffixLower(char *s, char *suffix)
+boolean strSuffixLower(const char *s, const char *suffix)
 {
   char *s_lower = getStringToLower(s);
   boolean match = strSuffix(s_lower, suffix);
@@ -1183,6 +1659,14 @@ boolean strSuffixLower(char *s, char *suffix)
   return match;
 }
 
+boolean isURL(const char *s)
+{
+  while (*s && *s >= 'a' && *s <= 'z')
+    s++;
+
+  return strPrefix(s, "://");
+}
+
 
 // ----------------------------------------------------------------------------
 // command line option handling functions
@@ -1192,8 +1676,7 @@ void GetOptions(int argc, char *argv[],
                void (*print_usage_function)(void),
                void (*print_version_function)(void))
 {
-  char *ro_base_path = getProgramMainDataPath(argv[0], RO_BASE_PATH);
-  char *rw_base_path = getProgramMainDataPath(argv[0], RW_BASE_PATH);
+  char *base_path = getProgramMainDataPath(argv[0], BASE_PATH);
   char **argvplus = checked_calloc((argc + 1) * sizeof(char **));
   char **options_left = &argvplus[1];
 
@@ -1205,18 +1688,25 @@ void GetOptions(int argc, char *argv[],
   options.server_host = NULL;
   options.server_port = 0;
 
-  options.ro_base_directory = ro_base_path;
-  options.rw_base_directory = rw_base_path;
-  options.level_directory    = getPath2(ro_base_path, LEVELS_DIRECTORY);
-  options.graphics_directory = getPath2(ro_base_path, GRAPHICS_DIRECTORY);
-  options.sounds_directory   = getPath2(ro_base_path, SOUNDS_DIRECTORY);
-  options.music_directory    = getPath2(ro_base_path, MUSIC_DIRECTORY);
-  options.docs_directory     = getPath2(ro_base_path, DOCS_DIRECTORY);
-  options.conf_directory     = getPath2(ro_base_path, CONF_DIRECTORY);
+  options.base_directory = base_path;
+
+  options.level_directory    = getPath2(base_path, LEVELS_DIRECTORY);
+  options.graphics_directory = getPath2(base_path, GRAPHICS_DIRECTORY);
+  options.sounds_directory   = getPath2(base_path, SOUNDS_DIRECTORY);
+  options.music_directory    = getPath2(base_path, MUSIC_DIRECTORY);
+  options.docs_directory     = getPath2(base_path, DOCS_DIRECTORY);
+  options.conf_directory     = getPath2(base_path, CONF_DIRECTORY);
 
   options.execute_command = NULL;
+  options.tape_log_filename = NULL;
   options.special_flags = NULL;
   options.debug_mode = NULL;
+  options.player_name = NULL;
+  options.identifier = NULL;
+  options.level_nr = NULL;
+  options.drop_file = NULL;
+
+  options.display_nr = 0;
 
   options.mytapes = FALSE;
   options.serveronly = FALSE;
@@ -1286,19 +1776,17 @@ void GetOptions(int argc, char *argv[],
       if (option_arg == NULL)
        FailWithHelp("option '%s' requires an argument", option_str);
 
-      // this should be extended to separate options for ro and rw data
-      options.ro_base_directory = ro_base_path = getStringCopy(option_arg);
-      options.rw_base_directory = rw_base_path = getStringCopy(option_arg);
+      options.base_directory = base_path = getStringCopy(option_arg);
       if (option_arg == next_option)
        options_left++;
 
       // adjust paths for sub-directories in base directory accordingly
-      options.level_directory    = getPath2(ro_base_path, LEVELS_DIRECTORY);
-      options.graphics_directory = getPath2(ro_base_path, GRAPHICS_DIRECTORY);
-      options.sounds_directory   = getPath2(ro_base_path, SOUNDS_DIRECTORY);
-      options.music_directory    = getPath2(ro_base_path, MUSIC_DIRECTORY);
-      options.docs_directory     = getPath2(ro_base_path, DOCS_DIRECTORY);
-      options.conf_directory     = getPath2(ro_base_path, CONF_DIRECTORY);
+      options.level_directory    = getPath2(base_path, LEVELS_DIRECTORY);
+      options.graphics_directory = getPath2(base_path, GRAPHICS_DIRECTORY);
+      options.sounds_directory   = getPath2(base_path, SOUNDS_DIRECTORY);
+      options.music_directory    = getPath2(base_path, MUSIC_DIRECTORY);
+      options.docs_directory     = getPath2(base_path, DOCS_DIRECTORY);
+      options.conf_directory     = getPath2(base_path, CONF_DIRECTORY);
     }
     else if (strncmp(option, "-levels", option_len) == 0)
     {
@@ -1356,6 +1844,42 @@ void GetOptions(int argc, char *argv[],
       if (option_arg != next_option)
        options.debug_mode = getStringCopy(option_arg);
     }
+    else if (strncmp(option, "-player-name", option_len) == 0)
+    {
+      if (option_arg == NULL)
+       FailWithHelp("option '%s' requires an argument", option_str);
+
+      options.player_name = getStringCopy(option_arg);
+      if (option_arg == next_option)
+       options_left++;
+    }
+    else if (strncmp(option, "-identifier", option_len) == 0)
+    {
+      if (option_arg == NULL)
+       FailWithHelp("option '%s' requires an argument", option_str);
+
+      options.identifier = getStringCopy(option_arg);
+      if (option_arg == next_option)
+       options_left++;
+    }
+    else if (strncmp(option, "-level-nr", option_len) == 0)
+    {
+      if (option_arg == NULL)
+       FailWithHelp("option '%s' requires an argument", option_str);
+
+      options.level_nr = getStringCopy(option_arg);
+      if (option_arg == next_option)
+       options_left++;
+    }
+    else if (strncmp(option, "-drop-file", option_len) == 0)
+    {
+      if (option_arg == NULL)
+       FailWithHelp("option '%s' requires an argument", option_str);
+
+      options.drop_file = getStringCopy(option_arg);
+      if (option_arg == next_option)
+       options_left++;
+    }
     else if (strncmp(option, "-verbose", option_len) == 0)
     {
       options.verbose = TRUE;
@@ -1383,7 +1907,38 @@ void GetOptions(int argc, char *argv[],
       // when doing batch processing, always enable verbose mode (warnings)
       options.verbose = TRUE;
     }
-#if defined(PLATFORM_MACOSX)
+    else if (strncmp(option, "-tape_logfile", option_len) == 0)
+    {
+      if (option_arg == NULL)
+       FailWithHelp("option '%s' requires an argument", option_str);
+
+      options.tape_log_filename = getStringCopy(option_arg);
+      if (option_arg == next_option)
+       options_left++;
+    }
+    else if (strncmp(option, "-display", option_len) == 0)
+    {
+      if (option_arg == NULL)
+       FailWithHelp("option '%s' requires an argument", option_str);
+
+      if (option_arg == next_option)
+       options_left++;
+
+      int display_nr = atoi(option_arg);
+
+#if 1
+      // dirty hack: SDL_GetNumVideoDisplays() seems broken on some systems
+      options.display_nr = display_nr;
+#else
+      options.display_nr =
+       MAX(0, MIN(display_nr, SDL_GetNumVideoDisplays() - 1));
+
+      if (display_nr != options.display_nr)
+       Warn("invalid display %d -- using display %d",
+            display_nr, options.display_nr);
+#endif
+    }
+#if defined(PLATFORM_MAC)
     else if (strPrefix(option, "-psn"))
     {
       // ignore process serial number when launched via GUI on Mac OS X
@@ -1457,7 +2012,7 @@ void checked_free(void *ptr)
 
 void clear_mem(void *ptr, unsigned int size)
 {
-#if defined(PLATFORM_WIN32)
+#if defined(PLATFORM_WINDOWS)
   // for unknown reason, memset() sometimes crashes when compiled with MinGW
   char *cptr = (char *)ptr;
 
@@ -1468,6 +2023,19 @@ void clear_mem(void *ptr, unsigned int size)
 #endif
 }
 
+void *get_memcpy(const void *m, size_t size)
+{
+  void *m_copy;
+
+  if (m == NULL)
+    return NULL;
+
+  m_copy = checked_malloc(size);
+  memcpy(m_copy, m, size);
+
+  return m_copy;
+}
+
 
 // ----------------------------------------------------------------------------
 // various helper functions
@@ -1493,6 +2061,31 @@ void swap_number_pairs(int *x1, int *y1, int *x2, int *y2)
   *y2 = help_y;
 }
 
+int get_number_of_bits(int bits)
+{
+  /*
+    Counting bits set, Brian Kernighan's way
+
+    Brian Kernighan's method goes through as many iterations as there are set
+    bits. So if we have a 32-bit word with only the high bit set, then it will
+    only go once through the loop.
+
+    Published in 1988, the C Programming Language 2nd Ed. (by Brian W. Kernighan
+    and Dennis M. Ritchie) mentions this in exercise 2-9.
+    First published by Peter Wegner in CACM 3 (1960), 322.
+  */
+
+  int num_bits = 0;
+
+  while (bits)
+  {
+    bits &= bits - 1;  // clear the least significant bit set
+    num_bits++;
+  }
+
+  return num_bits;
+}
+
 /* the "put" variants of the following file access functions check for the file
    pointer being != NULL and return the number of bytes they have or would have
    written; this allows for chunk writing functions to first determine the size
@@ -1677,6 +2270,225 @@ void WriteUnusedBytesToFile(FILE *file, unsigned int bytes)
 }
 
 
+// ----------------------------------------------------------------------------
+// functions to convert between ISO-8859-1 and UTF-8
+// ----------------------------------------------------------------------------
+
+char *getUTF8FromLatin1(char *latin1)
+{
+  int max_utf8_size = 2 * strlen(latin1) + 1;
+  char *utf8 = checked_calloc(max_utf8_size);
+  unsigned char *src = (unsigned char *)latin1;
+  unsigned char *dst = (unsigned char *)utf8;
+
+  while (*src)
+  {
+    if (*src < 128)            // pure 7-bit ASCII
+    {
+      *dst++ = *src;
+    }
+    else if (*src >= 160)      // non-ASCII characters
+    {
+      *dst++ = 194 + (*src >= 192);
+      *dst++ = 128 + (*src & 63);
+    }
+    else                       // undefined in ISO-8859-1
+    {
+      *dst++ = '?';
+    }
+
+    src++;
+  }
+
+  // only use the smallest possible string buffer size
+  utf8 = checked_realloc(utf8, strlen(utf8) + 1);
+
+  return utf8;
+}
+
+char *getLatin1FromUTF8(char *utf8)
+{
+  int max_latin1_size = strlen(utf8) + 1;
+  char *latin1 = checked_calloc(max_latin1_size);
+  unsigned char *src = (unsigned char *)utf8;
+  unsigned char *dst = (unsigned char *)latin1;
+
+  while (*src)
+  {
+    if (*src < 128)                            // pure 7-bit ASCII
+    {
+      *dst++ = *src++;
+    }
+    else if (src[0] == 194 &&
+            src[1] >= 128 && src[1] < 192)     // non-ASCII characters
+    {
+      *dst++ = src[1];
+      src += 2;
+    }
+    else if (src[0] == 195 &&
+            src[1] >= 128 && src[1] < 192)     // non-ASCII characters
+    {
+      *dst++ = src[1] + 64;
+      src += 2;
+    }
+
+    // all other UTF-8 characters are undefined in ISO-8859-1
+
+    else if (src[0] >= 192 && src[0] < 224 &&
+            src[1] >= 128 && src[1] < 192)
+    {
+      *dst++ = '?';
+      src += 2;
+    }
+    else if (src[0] >= 224 && src[0] < 240 &&
+            src[1] >= 128 && src[1] < 192 &&
+            src[2] >= 128 && src[2] < 192)
+    {
+      *dst++ = '?';
+      src += 3;
+    }
+    else if (src[0] >= 240 && src[0] < 248 &&
+            src[1] >= 128 && src[1] < 192 &&
+            src[2] >= 128 && src[2] < 192 &&
+            src[3] >= 128 && src[3] < 192)
+    {
+      *dst++ = '?';
+      src += 4;
+    }
+    else if (src[0] >= 248 && src[0] < 252 &&
+            src[1] >= 128 && src[1] < 192 &&
+            src[2] >= 128 && src[2] < 192 &&
+            src[3] >= 128 && src[3] < 192 &&
+            src[4] >= 128 && src[4] < 192)
+    {
+      *dst++ = '?';
+      src += 5;
+    }
+    else if (src[0] >= 252 && src[0] < 254 &&
+            src[1] >= 128 && src[1] < 192 &&
+            src[2] >= 128 && src[2] < 192 &&
+            src[3] >= 128 && src[3] < 192 &&
+            src[4] >= 128 && src[4] < 192 &&
+            src[5] >= 128 && src[5] < 192)
+    {
+      *dst++ = '?';
+      src += 6;
+    }
+    else
+    {
+      *dst++ = '?';
+      src++;
+    }
+  }
+
+  // only use the smallest possible string buffer size
+  latin1 = checked_realloc(latin1, strlen(latin1) + 1);
+
+  return latin1;
+}
+
+int getTextEncoding(char *text)
+{
+  unsigned char *src = (unsigned char *)text;
+  int encoding = TEXT_ENCODING_ASCII;  // default: assume encoding is ASCII
+
+  while (*src)
+  {
+    if (*src >= 128)
+      encoding = TEXT_ENCODING_UTF_8;  // non-ASCII character: assume UTF-8
+
+    if (*src < 128)
+    {
+      src++;
+    }
+    else if (src[0] >= 192 && src[0] < 224 &&
+            src[1] >= 128 && src[1] < 192)
+    {
+      src += 2;
+    }
+    else if (src[0] >= 224 && src[0] < 240 &&
+            src[1] >= 128 && src[1] < 192 &&
+            src[2] >= 128 && src[2] < 192)
+    {
+      src += 3;
+    }
+    else if (src[0] >= 240 && src[0] < 248 &&
+            src[1] >= 128 && src[1] < 192 &&
+            src[2] >= 128 && src[2] < 192 &&
+            src[3] >= 128 && src[3] < 192)
+    {
+      src += 4;
+    }
+    else if (src[0] >= 248 && src[0] < 252 &&
+            src[1] >= 128 && src[1] < 192 &&
+            src[2] >= 128 && src[2] < 192 &&
+            src[3] >= 128 && src[3] < 192 &&
+            src[4] >= 128 && src[4] < 192)
+    {
+      src += 5;
+    }
+    else if (src[0] >= 252 && src[0] < 254 &&
+            src[1] >= 128 && src[1] < 192 &&
+            src[2] >= 128 && src[2] < 192 &&
+            src[3] >= 128 && src[3] < 192 &&
+            src[4] >= 128 && src[4] < 192 &&
+            src[5] >= 128 && src[5] < 192)
+    {
+      src += 6;
+    }
+    else
+    {
+      return TEXT_ENCODING_UNKNOWN;    // non-UTF-8 character: unknown encoding
+    }
+  }
+
+  return encoding;
+}
+
+
+// ----------------------------------------------------------------------------
+// functions for JSON handling
+// ----------------------------------------------------------------------------
+
+char *getEscapedJSON(char *s)
+{
+  int max_json_size = 2 * strlen(s) + 1;
+  char *json = checked_calloc(max_json_size);
+  unsigned char *src = (unsigned char *)s;
+  unsigned char *dst = (unsigned char *)json;
+  char *escaped[256] =
+  {
+    ['\b'] = "\\b",
+    ['\f'] = "\\f",
+    ['\n'] = "\\n",
+    ['\r'] = "\\r",
+    ['\t'] = "\\t",
+    ['\"'] = "\\\"",
+    ['\\'] = "\\\\",
+  };
+
+  while (*src)
+  {
+    if (escaped[*src] != NULL)
+    {
+      char *esc = escaped[*src++];
+
+      while (*esc)
+       *dst++ = *esc++;
+    }
+    else
+    {
+      *dst++ = *src++;
+    }
+  }
+
+  // only use the smallest possible string buffer size
+  json = checked_realloc(json, strlen(json) + 1);
+
+  return json;
+}
+
+
 // ----------------------------------------------------------------------------
 // functions to translate key identifiers between different format
 // ----------------------------------------------------------------------------
@@ -2114,47 +2926,23 @@ char getValidConfigValueChar(char c)
 
 int get_integer_from_string(char *s)
 {
-  static char *number_text[][3] =
-  {
-    { "0",     "zero",         "null",         },
-    { "1",     "one",          "first"         },
-    { "2",     "two",          "second"        },
-    { "3",     "three",        "third"         },
-    { "4",     "four",         "fourth"        },
-    { "5",     "five",         "fifth"         },
-    { "6",     "six",          "sixth"         },
-    { "7",     "seven",        "seventh"       },
-    { "8",     "eight",        "eighth"        },
-    { "9",     "nine",         "ninth"         },
-    { "10",    "ten",          "tenth"         },
-    { "11",    "eleven",       "eleventh"      },
-    { "12",    "twelve",       "twelfth"       },
-
-    { NULL,    NULL,           NULL            },
-  };
+  // check for the most common case first
+  if (s[0] >= '0' && s[0] <= '9')
+    return atoi(s);
 
-  int i, j;
   char *s_lower = getStringToLower(s);
   int result = -1;
 
-  for (i = 0; number_text[i][0] != NULL; i++)
-    for (j = 0; j < 3; j++)
-      if (strEqual(s_lower, number_text[i][j]))
-       result = i;
-
-  if (result == -1)
-  {
-    if (strEqual(s_lower, "false") ||
-       strEqual(s_lower, "no") ||
-       strEqual(s_lower, "off"))
-      result = 0;
-    else if (strEqual(s_lower, "true") ||
-            strEqual(s_lower, "yes") ||
-            strEqual(s_lower, "on"))
-      result = 1;
-    else
-      result = atoi(s);
-  }
+  if (strEqual(s_lower, "false") ||
+      strEqual(s_lower, "no") ||
+      strEqual(s_lower, "off"))
+    result = 0;
+  else if (strEqual(s_lower, "true") ||
+          strEqual(s_lower, "yes") ||
+          strEqual(s_lower, "on"))
+    result = 1;
+  else
+    result = atoi(s);
 
   free(s_lower);
 
@@ -2177,7 +2965,7 @@ boolean get_boolean_from_string(char *s)
   return result;
 }
 
-int get_switch3_from_string(char *s)
+int get_switch_3_state_from_string(char *s)
 {
   char *s_lower = getStringToLower(s);
   int result = FALSE;
@@ -2188,7 +2976,9 @@ int get_switch3_from_string(char *s)
       get_integer_from_string(s) == 1)
     result = TRUE;
   else if (strEqual(s_lower, "auto"))
-    result = AUTO;
+    result = STATE_AUTO;
+  else if (strEqual(s_lower, "ask"))
+    result = STATE_ASK;
 
   free(s_lower);
 
@@ -2320,7 +3110,7 @@ static void dumpList(ListNode *node_first)
 
 #define MAX_BUFFER_SIZE                        4096
 
-File *openFile(char *filename, char *mode)
+File *openFile(const char *filename, const char *mode)
 {
   File *file = checked_calloc(sizeof(File));
 
@@ -2476,7 +3266,7 @@ char *getStringFromFile(File *file, char *line, int size)
   return fgets(line, size, file->file);
 }
 
-int copyFile(char *filename_from, char *filename_to)
+int copyFile(const char *filename_from, const char *filename_to)
 {
   File *file_from, *file_to;
 
@@ -2506,6 +3296,22 @@ int copyFile(char *filename_from, char *filename_to)
   return 0;
 }
 
+boolean touchFile(const char *filename)
+{
+  FILE *file;
+
+  if (!(file = fopen(filename, MODE_WRITE)))
+  {
+    Warn("cannot touch file '%s'", filename);
+
+    return FALSE;
+  }
+
+  fclose(file);
+
+  return TRUE;
+}
+
 
 // ----------------------------------------------------------------------------
 // functions for directory handling
@@ -2647,7 +3453,7 @@ void freeDirectoryEntry(DirectoryEntry *dir_entry)
 // functions for checking files and filenames
 // ----------------------------------------------------------------------------
 
-boolean directoryExists(char *dir_name)
+boolean directoryExists(const char *dir_name)
 {
   if (dir_name == NULL)
     return FALSE;
@@ -2675,7 +3481,7 @@ boolean directoryExists(char *dir_name)
   return success;
 }
 
-boolean fileExists(char *filename)
+boolean fileExists(const char *filename)
 {
   if (filename == NULL)
     return FALSE;
@@ -2699,7 +3505,7 @@ boolean fileExists(char *filename)
 }
 
 #if 0
-static boolean fileHasPrefix(char *basename, char *prefix)
+static boolean fileHasPrefix(const char *basename, const char *prefix)
 {
   static char *basename_lower = NULL;
   int basename_length, prefix_length;
@@ -2722,7 +3528,7 @@ static boolean fileHasPrefix(char *basename, char *prefix)
 }
 #endif
 
-static boolean fileHasSuffix(char *basename, char *suffix)
+static boolean fileHasSuffix(const char *basename, const char *suffix)
 {
   static char *basename_lower = NULL;
   int basename_length, suffix_length;
@@ -3520,8 +4326,8 @@ void LoadArtworkConfig(struct ArtworkListInfo *artwork_info)
   char *filename_base = UNDEFINED_FILENAME, *filename_local;
   int i, j;
 
-  DrawInitText("Loading artwork config", 120, FC_GREEN);
-  DrawInitText(ARTWORKINFO_FILENAME(artwork_info->type), 150, FC_YELLOW);
+  DrawInitTextHead("Loading artwork config");
+  DrawInitTextItem(ARTWORKINFO_FILENAME(artwork_info->type));
 
   // always start with reliable default values
   for (i = 0; i < num_file_list_entries; i++)
@@ -3606,6 +4412,11 @@ static void replaceArtworkListEntry(struct ArtworkListInfo *artwork_info,
   char *basename = file_list_entry->filename;
   char *filename = getCustomArtworkFilename(basename, artwork_info->type);
 
+  // mark all images from non-default graphics directory as "redefined"
+  if (artwork_info->type == ARTWORK_TYPE_GRAPHICS &&
+      !strPrefix(filename, options.graphics_directory))
+    file_list_entry->redefined = TRUE;
+
   if (filename == NULL)
   {
     Warn("cannot find artwork file '%s'", basename);
@@ -3674,8 +4485,8 @@ static void replaceArtworkListEntry(struct ArtworkListInfo *artwork_info,
       return;
   }
 
-  DrawInitText(init_text[artwork_info->type], 120, FC_GREEN);
-  DrawInitText(basename, 150, FC_YELLOW);
+  DrawInitTextHead(init_text[artwork_info->type]);
+  DrawInitTextItem(basename);
 
   if ((*listnode = artwork_info->load_artwork(filename)) != NULL)
   {
@@ -3774,45 +4585,41 @@ void FreeCustomArtworkLists(struct ArtworkListInfo *artwork_info)
 // (now also added for Windows, to create files in user data directory)
 // ----------------------------------------------------------------------------
 
+char *getLogBasename(char *basename)
+{
+  return getStringCat2(basename, ".log");
+}
+
 char *getLogFilename(char *basename)
 {
   return getPath2(getMainUserGameDataDir(), basename);
 }
 
-void OpenLogFiles(void)
+void OpenLogFile(void)
 {
-  int i;
-
   InitMainUserDataDirectory();
 
-  for (i = 0; i < NUM_LOGS; i++)
+  if ((program.log_file = fopen(program.log_filename, MODE_WRITE)) == NULL)
   {
-    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
-
-      Warn("cannot open file '%s' for writing: %s",
-          program.log_filename[i], strerror(errno));
-    }
+    program.log_file = program.log_file_default;   // reset to default
 
-    // output should be unbuffered so it is not truncated in a crash
-    setbuf(program.log_file[i], NULL);
+    Warn("cannot open file '%s' for writing: %s",
+        program.log_filename, strerror(errno));
   }
+
+  // output should be unbuffered so it is not truncated in a crash
+  setbuf(program.log_file, NULL);
 }
 
-void CloseLogFiles(void)
+void CloseLogFile(void)
 {
-  int i;
-
-  for (i = 0; i < NUM_LOGS; i++)
-    if (program.log_file[i] != program.log_file_default[i])
-      fclose(program.log_file[i]);
+  if (program.log_file != program.log_file_default)
+    fclose(program.log_file);
 }
 
-void DumpLogFile(int nr)
+void DumpLogFile(void)
 {
-  FILE *log_file = fopen(program.log_filename[nr], MODE_READ);
+  FILE *log_file = fopen(program.log_filename, MODE_READ);
 
   if (log_file == NULL)
     return;
@@ -3825,12 +4632,12 @@ void DumpLogFile(int nr)
 
 void NotifyUserAboutErrorFile(void)
 {
-#if defined(PLATFORM_WIN32)
+#if defined(PLATFORM_WINDOWS)
   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.log_filename[LOG_ERR_ID]);
+                                  program.log_filename);
 
   MessageBox(NULL, error_text, title_text, MB_OK);
 #endif