+ va_list ap;
+
+ va_start(ap, format);
+ vLog(LOG_ERROR, NULL, format, ap);
+ va_end(ap);
+}
+
+void Fail(char *format, ...)
+{
+ va_list ap;
+
+ va_start(ap, format);
+ vLog(LOG_FATAL, NULL, format, ap);
+ va_end(ap);
+
+ if (!network.is_server_thread)
+ {
+ va_start(ap, format);
+ program.exit_message_function(format, ap);
+ va_end(ap);
+ }
+
+ Log(LOG_FATAL, NULL, "aborting");
+
+ // network server thread: normal exit
+ if (network.is_server_thread)
+ exit(1);
+
+ // main process: clean up stuff
+ program.exit_function(1);
+}
+
+void FailWithHelp(char *format, ...)
+{
+ va_list ap;
+
+ va_start(ap, format);
+ vLog(LOG_FATAL, NULL, format, ap);
+ va_end(ap);
+
+ Log(LOG_FATAL, NULL, "try option '--help' for more information");
+ Log(LOG_FATAL, NULL, "aborting");
+
+ // main process: clean up stuff
+ program.exit_function(1);
+}
+
+
+// ----------------------------------------------------------------------------
+// string functions
+// ----------------------------------------------------------------------------
+
+/* 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. */
+
+char *int2str(int number, int size)
+{
+ static char shift_array[10][40];
+ static int shift_counter = 0;
+ char *s = shift_array[shift_counter];
+
+ shift_counter = (shift_counter + 1) % 10;
+
+ if (size > 20)
+ size = 20;
+
+ if (size > 0)
+ {
+ sprintf(s, " %09d", number);
+ return &s[strlen(s) - size];
+ }
+ else
+ {
+ sprintf(s, "%d", number);
+ return s;
+ }
+}
+
+
+// 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)
+{
+ static char *a = NULL;
+
+ checked_free(a);
+
+ if (i > 2147483647) // yes, this is a kludge
+ i = 2147483647;
+
+ a = checked_malloc(10 + 1);
+
+ sprintf(a, "%d", i);
+
+ return a;
+}
+
+
+// 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)
+{
+ int e = 0;
+
+ while ((1 << e) < x)
+ {
+ x -= (1 << e); // for rounding down (rounding up: remove this line)
+ e++;
+ }
+
+ return e;
+}
+
+boolean getTokenValueFromString(char *string, char **token, char **value)
+{
+ return getTokenValueFromSetupLine(string, token, 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
+// ----------------------------------------------------------------------------
+
+// maximal allowed length of a command line option
+#define MAX_OPTION_LEN 256
+
+static unsigned int getCurrentMS(void)
+{
+ return SDL_GetTicks();
+}
+
+static unsigned int mainCounter(int mode)
+{
+ static unsigned int base_ms = 0;
+ unsigned int current_ms;
+
+ // get current system milliseconds
+ current_ms = getCurrentMS();
+
+ // 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 current_ms - base_ms;
+}
+
+void InitCounter(void) // set counter back to zero
+{
+ mainCounter(INIT_COUNTER);
+}
+
+unsigned int Counter(void) // get milliseconds since last call of InitCounter()
+{
+ return mainCounter(READ_COUNTER);
+}
+
+static void sleep_milliseconds(unsigned int milliseconds_delay)
+{
+ SDL_Delay(milliseconds_delay);
+}
+
+void Delay(unsigned int delay) // Sleep specified number of milliseconds
+{
+ sleep_milliseconds(delay);
+}
+
+boolean DelayReachedExt2(unsigned int *counter_var, unsigned int delay,
+ unsigned int actual_counter)
+{
+ if (actual_counter >= *counter_var &&
+ actual_counter < *counter_var + delay)
+ return FALSE;
+
+ *counter_var = actual_counter;
+
+ return TRUE;
+}
+
+boolean DelayReachedExt(DelayCounter *counter, unsigned int actual_counter)
+{
+ return DelayReachedExt2(&counter->count, counter->value, actual_counter);
+}
+
+boolean FrameReached(DelayCounter *counter)
+{
+ return DelayReachedExt(counter, FrameCounter);
+}
+
+boolean DelayReached(DelayCounter *counter)
+{
+ return DelayReachedExt(counter, Counter());
+}
+
+void ResetDelayCounterExt(DelayCounter *counter, unsigned int actual_counter)
+{
+ DelayReachedExt2(&counter->count, 0, actual_counter);
+}
+
+void ResetFrameCounter(DelayCounter *counter)
+{
+ ResetDelayCounterExt(counter, FrameCounter);
+}
+
+void ResetDelayCounter(DelayCounter *counter)
+{
+ ResetDelayCounterExt(counter, Counter());
+}
+
+int WaitUntilDelayReached(DelayCounter *counter)
+{
+ unsigned int *counter_var = &counter->count;
+ unsigned int delay = counter->value;
+ unsigned int actual_counter;
+ int skip_frames = 0;
+
+ while (1)
+ {
+ actual_counter = Counter();
+
+ if (actual_counter >= *counter_var &&
+ actual_counter < *counter_var + delay)
+ sleep_milliseconds((*counter_var + delay - actual_counter) / 2);
+ else
+ 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(DelayCounter *counter,
+ int *loop_var, int last_loop_value)
+{
+ 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, counter->value,
+ skip_frames, skip_frames * counter->value);
+ else
+ Debug("internal:SkipUntilDelayReached",
+ "%d: %d ms",
+ *loop_var, counter->value);
+#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
+// ----------------------------------------------------------------------------
+
+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_WINDOWS)
+ // add some more randomness
+ struct timeval current_time;
+
+ gettimeofday(¤t_time, NULL);
+
+ seed += (int)current_time.tv_usec; // microseconds since the epoch
+#endif
+
+ // add some more randomness
+ seed += (int)SDL_GetTicks(); // milliseconds since SDL init
+
+ // add some more randomness
+ seed += GetSimpleRandom(1000000);
+ }
+
+ srandom_linux_libc(nr, (unsigned int) seed);
+
+ return (unsigned int) seed;
+}
+
+static unsigned int prng_seed_gettimeofday(void)
+{
+ struct timeval current_time;
+
+ gettimeofday(¤t_time, NULL);
+
+ prng_seed_bytes(¤t_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 ? get_random_number_ext(nr) % max : 0);
+}
+
+
+// ----------------------------------------------------------------------------
+// system info functions
+// ----------------------------------------------------------------------------
+
+#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
+ 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
+ if (*from_ptr == ',')
+ break;
+
+ // 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';
+ *to_ptr++ = 's';
+ }
+ else
+ *to_ptr++ = *from_ptr++;
+ }
+
+ *to_ptr = '\0';
+
+ return real_name_new;
+}
+#endif
+
+#if defined(PLATFORM_UNIX)
+static struct passwd *getPasswdEntry(void)
+{
+#if defined(PLATFORM_EMSCRIPTEN)
+ // currently not fully supported; force fallback to default values
+ return NULL;
+#else
+ return getpwuid(getuid());
+#endif
+}
+
+char *getUnixLoginName(void)
+{
+ struct passwd *pwd = getPasswdEntry();
+
+ if (pwd != NULL && strlen(pwd->pw_name) != 0)
+ return pwd->pw_name;
+
+ return NULL;
+}
+
+char *getUnixRealName(void)
+{
+ struct passwd *pwd = getPasswdEntry();
+
+ if (pwd != NULL && strlen(pwd->pw_gecos) != 0)
+ return pwd->pw_gecos;
+
+ return NULL;
+}
+
+char *getUnixHomeDir(void)
+{
+ struct passwd *pwd = getPasswdEntry();
+
+ if (pwd != NULL && strlen(pwd->pw_dir) != 0)
+ return pwd->pw_dir;
+
+ return NULL;
+}
+#endif
+
+char *getLoginName(void)
+{
+ static char *login_name = NULL;
+
+#if defined(PLATFORM_WINDOWS)
+ if (login_name == NULL)
+ {
+ unsigned long buffer_size = MAX_USERNAME_LEN + 1;
+
+ login_name = checked_malloc(buffer_size);
+
+ if (GetUserName(login_name, &buffer_size) == 0)
+ strcpy(login_name, ANONYMOUS_NAME);
+ }
+#elif defined(PLATFORM_UNIX) && !defined(PLATFORM_ANDROID)
+ if (login_name == NULL)
+ {
+ char *name = getUnixLoginName();
+
+ if (name != NULL)
+ login_name = getStringCopy(name);
+ else
+ login_name = ANONYMOUS_NAME;
+ }
+#else
+ login_name = ANONYMOUS_NAME;
+#endif
+
+ return login_name;
+}
+
+char *getRealName(void)
+{
+ static char *real_name = NULL;
+
+#if defined(PLATFORM_WINDOWS)
+ if (real_name == NULL)
+ {
+ static char buffer[MAX_USERNAME_LEN + 1];
+ unsigned long buffer_size = MAX_USERNAME_LEN + 1;
+
+ if (GetUserName(buffer, &buffer_size) != 0)
+ real_name = get_corrected_real_name(buffer);
+ else
+ real_name = ANONYMOUS_NAME;
+ }
+#elif defined(PLATFORM_UNIX) && !defined(PLATFORM_ANDROID)
+ if (real_name == NULL)
+ {
+ char *name = getUnixRealName();
+
+ if (name != NULL)
+ real_name = get_corrected_real_name(name);
+ else
+ real_name = ANONYMOUS_NAME;
+ }
+#else
+ real_name = ANONYMOUS_NAME;
+#endif
+
+ return real_name;
+}
+
+char *getFixedUserName(char *name)
+{
+ // needed because player name must be a fixed length string
+ char *name_new = checked_malloc(MAX_PLAYER_NAME_LEN + 1);
+
+ strncpy(name_new, name, MAX_PLAYER_NAME_LEN);
+ name_new[MAX_PLAYER_NAME_LEN] = '\0';
+
+ if (strlen(name) > MAX_PLAYER_NAME_LEN) // name has been cut
+ if (strchr(name_new, ' '))
+ *strchr(name_new, ' ') = '\0';
+
+ return name_new;
+}
+
+char *getDefaultUserName(int nr)
+{
+ static char *user_name[MAX_PLAYER_NAMES] = { NULL };
+
+ nr = MIN(MAX(0, nr), MAX_PLAYER_NAMES - 1);
+
+ if (user_name[nr] == NULL)
+ {
+ user_name[nr] = (nr == 0 ? getLoginName() : EMPTY_PLAYER_NAME);
+ user_name[nr] = getFixedUserName(user_name[nr]);
+ }
+
+ return user_name[nr];
+}
+
+char *getTimestampFromEpoch(time_t epoch_seconds)
+{
+ struct tm *now = localtime(&epoch_seconds);
+ static char timestamp[20];
+
+ sprintf(timestamp, "%04d%02d%02d-%02d%02d%02d",
+ now->tm_year + 1900, now->tm_mon + 1, now->tm_mday,
+ now->tm_hour, now->tm_min, now->tm_sec);
+
+ return timestamp;
+}
+
+char *getCurrentTimestamp(void)
+{
+ return getTimestampFromEpoch(time(NULL));
+}
+
+time_t getFileTimestampEpochSeconds(char *filename)
+{
+ struct stat file_status;
+
+ if (stat(filename, &file_status) != 0) // cannot stat file
+ return 0;
+
+ return file_status.st_mtime;
+}
+
+
+// ----------------------------------------------------------------------------
+// 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
+ last_separator = strrchr(filename, CHAR_PATH_SEPARATOR_DOS);
+
+ return last_separator;
+}
+
+char *getBaseNamePtr(char *filename)
+{
+ char *last_separator = getLastPathSeparatorPtr(filename);
+
+ if (last_separator != NULL)
+ return last_separator + 1; // separator found: strip base path
+ else
+ return filename; // no separator found: filename has no path
+}
+
+char *getBaseName(char *filename)
+{
+ return getStringCopy(getBaseNamePtr(filename));
+}
+
+char *getBaseNameNoSuffix(char *filename)
+{
+ char *basename = getStringCopy(getBaseNamePtr(filename));
+
+ // remove trailing suffix (separated by dot or hyphen)
+ if (basename[0] != '.' && basename[0] != '-')
+ {
+ if (strchr(basename, '.') != NULL)
+ *strchr(basename, '.') = '\0';
+
+ if (strchr(basename, '-') != NULL)
+ *strchr(basename, '-') = '\0';
+ }
+
+ return basename;
+}
+
+char *getBasePath(char *filename)
+{
+ char *basepath = getStringCopy(filename);
+ char *last_separator = getLastPathSeparatorPtr(basepath);
+
+ // if no separator was found, use current directory
+ if (last_separator == NULL)
+ {
+ free(basepath);
+
+ return getStringCopy(".");
+ }
+
+ // separator found: strip basename
+ *last_separator = '\0';
+
+ return basepath;
+}
+
+
+// ----------------------------------------------------------------------------
+// various string functions
+// ----------------------------------------------------------------------------
+
+char *getStringCat2WithSeparator(const char *s1,
+ const char *s2,
+ const char *sep)
+{
+ if (s1 == NULL || s2 == NULL || sep == NULL)
+ return NULL;
+
+ char *complete_string = checked_malloc(strlen(s1) + strlen(sep) +
+ strlen(s2) + 1);
+
+ sprintf(complete_string, "%s%s%s", s1, sep, s2);
+
+ return complete_string;
+}
+
+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;
+
+ char *complete_string = checked_malloc(strlen(s1) + strlen(sep) +
+ strlen(s2) + strlen(sep) +
+ strlen(s3) + 1);
+
+ sprintf(complete_string, "%s%s%s%s%s", s1, sep, s2, sep, s3);
+
+ return complete_string;
+}
+
+char *getStringCat2(const char *s1, const char *s2)
+{
+ return getStringCat2WithSeparator(s1, s2, "");
+}
+
+char *getStringCat3(const char *s1, const char *s2, const char *s3)
+{
+ return getStringCat3WithSeparator(s1, s2, s3, "");
+}
+
+char *getPath2(const char *path1, const char *path2)
+{
+#if defined(PLATFORM_ANDROID)
+ // 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);
+}
+
+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
+ 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);
+}
+
+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(const char *path1, const char *path2)
+{
+ return getPngOrPcxIfNotExists(getPath2(path1, path2));
+}
+
+char *getImg3(const char *path1, const char *path2, const char *path3)
+{
+ return getPngOrPcxIfNotExists(getPath3(path1, path2, path3));
+}
+
+char *getStringCopy(const char *s)
+{
+ char *s_copy;
+
+ if (s == NULL)
+ return NULL;
+
+ s_copy = checked_malloc(strlen(s) + 1);
+ strcpy(s_copy, s);
+
+ return s_copy;
+}
+
+char *getStringCopyN(const char *s, int n)
+{
+ char *s_copy;
+ int s_len = MAX(0, n);
+
+ if (s == NULL)
+ return NULL;
+
+ s_copy = checked_malloc(s_len + 1);
+ strncpy(s_copy, s, s_len);
+ s_copy[s_len] = '\0';
+
+ return s_copy;
+}
+
+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;