fixed loading custom PCX image files with default artwork file names
[rocksndiamonds.git] / src / libgame / misc.c
1 // ============================================================================
2 // Artsoft Retro-Game Library
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
5 //                  Holger Schemel
6 //                  info@artsoft.org
7 //                  http://www.artsoft.org/
8 // ----------------------------------------------------------------------------
9 // misc.c
10 // ============================================================================
11
12 #include <time.h>
13 #include <sys/time.h>
14 #include <sys/types.h>
15 #include <sys/stat.h>
16 #include <stdarg.h>
17 #include <ctype.h>
18 #include <string.h>
19 #include <unistd.h>
20 #include <errno.h>
21
22 #include "platform.h"
23
24 #if !defined(PLATFORM_WIN32)
25 #include <pwd.h>
26 #include <sys/param.h>
27 #endif
28
29 #include "misc.h"
30 #include "setup.h"
31 #include "random.h"
32 #include "text.h"
33 #include "image.h"
34
35
36 /* ========================================================================= */
37 /* some generic helper functions                                             */
38 /* ========================================================================= */
39
40 /* ------------------------------------------------------------------------- */
41 /* platform independent wrappers for printf() et al. (newline aware)         */
42 /* ------------------------------------------------------------------------- */
43
44 #if defined(PLATFORM_ANDROID)
45 static int android_log_prio = ANDROID_LOG_INFO;
46 #endif
47
48 #if 0
49 static void vfPrintLog(FILE *stream, char *format, va_list ap)
50 {
51 }
52
53 static void vfPrintLog(FILE *stream, char *format, va_list ap)
54 {
55 }
56
57 static void fPrintLog(FILE *stream, char *format, va_list ap)
58 {
59 }
60
61 static void fPrintLog(FILE *stream, char *format, va_list ap)
62 {
63 }
64 #endif
65
66 static void vfprintf_nonewline(FILE *stream, char *format, va_list ap)
67 {
68 #if defined(PLATFORM_ANDROID)
69   // (prefix text of logging output is currently skipped on Android)
70   //__android_log_vprint(android_log_prio, program.program_title, format, ap);
71 #else
72   va_list ap2;
73   va_copy(ap2, ap);
74
75   vfprintf(stream, format, ap);
76   vfprintf(stderr, format, ap2);
77
78   va_end(ap2);
79 #endif
80 }
81
82 static void vfprintf_newline(FILE *stream, char *format, va_list ap)
83 {
84 #if defined(PLATFORM_ANDROID)
85   __android_log_vprint(android_log_prio, program.program_title, format, ap);
86 #else
87   char *newline = STRING_NEWLINE;
88
89   va_list ap2;
90   va_copy(ap2, ap);
91
92   vfprintf(stream, format, ap);
93   fprintf(stream, "%s", newline);
94
95   vfprintf(stderr, format, ap2);
96   fprintf(stderr, "%s", newline);
97
98   va_end(ap2);
99 #endif
100 }
101
102 static void fprintf_nonewline(FILE *stream, char *format, ...)
103 {
104   va_list ap;
105
106   va_start(ap, format);
107   vfprintf_nonewline(stream, format, ap);
108   va_end(ap);
109 }
110
111 static void fprintf_newline(FILE *stream, char *format, ...)
112 {
113   va_list ap;
114
115   va_start(ap, format);
116   vfprintf_newline(stream, format, ap);
117   va_end(ap);
118 }
119
120 void fprintf_line(FILE *stream, char *line_chars, int line_length)
121 {
122   int i;
123
124   for (i = 0; i < line_length; i++)
125     fprintf_nonewline(stream, "%s", line_chars);
126
127   fprintf_newline(stream, "");
128 }
129
130 void printf_line(char *line_chars, int line_length)
131 {
132   fprintf_line(stdout, line_chars, line_length);
133 }
134
135 void printf_line_with_prefix(char *prefix, char *line_chars, int line_length)
136 {
137   fprintf(stdout, "%s", prefix);
138   fprintf_line(stdout, line_chars, line_length);
139 }
140
141
142 /* ------------------------------------------------------------------------- */
143 /* string functions                                                          */
144 /* ------------------------------------------------------------------------- */
145
146 /* int2str() returns a number converted to a string;
147    the used memory is static, but will be overwritten by later calls,
148    so if you want to save the result, copy it to a private string buffer;
149    there can be 10 local calls of int2str() without buffering the result --
150    the 11th call will then destroy the result from the first call and so on.
151 */
152
153 char *int2str(int number, int size)
154 {
155   static char shift_array[10][40];
156   static int shift_counter = 0;
157   char *s = shift_array[shift_counter];
158
159   shift_counter = (shift_counter + 1) % 10;
160
161   if (size > 20)
162     size = 20;
163
164   if (size > 0)
165   {
166     sprintf(s, "                    %09d", number);
167     return &s[strlen(s) - size];
168   }
169   else
170   {
171     sprintf(s, "%d", number);
172     return s;
173   }
174 }
175
176
177 /* something similar to "int2str()" above, but allocates its own memory
178    and has a different interface; we cannot use "itoa()", because this
179    seems to be already defined when cross-compiling to the win32 target */
180
181 char *i_to_a(unsigned int i)
182 {
183   static char *a = NULL;
184
185   checked_free(a);
186
187   if (i > 2147483647)   /* yes, this is a kludge */
188     i = 2147483647;
189
190   a = checked_malloc(10 + 1);
191
192   sprintf(a, "%d", i);
193
194   return a;
195 }
196
197
198 /* calculate base-2 logarithm of argument (rounded down to integer;
199    this function returns the number of the highest bit set in argument) */
200
201 int log_2(unsigned int x)
202 {
203   int e = 0;
204
205   while ((1 << e) < x)
206   {
207     x -= (1 << e);      /* for rounding down (rounding up: remove this line) */
208     e++;
209   }
210
211   return e;
212 }
213
214 boolean getTokenValueFromString(char *string, char **token, char **value)
215 {
216   return getTokenValueFromSetupLine(string, token, value);
217 }
218
219
220 /* ------------------------------------------------------------------------- */
221 /* counter functions                                                         */
222 /* ------------------------------------------------------------------------- */
223
224 /* maximal allowed length of a command line option */
225 #define MAX_OPTION_LEN          256
226
227 static unsigned int getCurrentMS()
228 {
229   return SDL_GetTicks();
230 }
231
232 static unsigned int mainCounter(int mode)
233 {
234   static unsigned int base_ms = 0;
235   unsigned int current_ms;
236
237   /* get current system milliseconds */
238   current_ms = getCurrentMS();
239
240   /* reset base timestamp in case of counter reset or wrap-around */
241   if (mode == INIT_COUNTER || current_ms < base_ms)
242     base_ms = current_ms;
243
244   /* return milliseconds since last counter reset */
245   return current_ms - base_ms;
246 }
247
248 void InitCounter()              /* set counter back to zero */
249 {
250   mainCounter(INIT_COUNTER);
251 }
252
253 unsigned int Counter()  /* get milliseconds since last call of InitCounter() */
254 {
255   return mainCounter(READ_COUNTER);
256 }
257
258 static void sleep_milliseconds(unsigned int milliseconds_delay)
259 {
260   boolean do_busy_waiting = (milliseconds_delay < 5 ? TRUE : FALSE);
261
262   if (do_busy_waiting)
263   {
264     /* we want to wait only a few ms -- if we assume that we have a
265        kernel timer resolution of 10 ms, we would wait far to long;
266        therefore it's better to do a short interval of busy waiting
267        to get our sleeping time more accurate */
268
269     unsigned int base_counter = Counter(), actual_counter = Counter();
270
271     while (actual_counter < base_counter + milliseconds_delay &&
272            actual_counter >= base_counter)
273       actual_counter = Counter();
274   }
275   else
276   {
277     SDL_Delay(milliseconds_delay);
278   }
279 }
280
281 void Delay(unsigned int delay)  /* Sleep specified number of milliseconds */
282 {
283   sleep_milliseconds(delay);
284 }
285
286 boolean FrameReached(unsigned int *frame_counter_var,
287                      unsigned int frame_delay)
288 {
289   unsigned int actual_frame_counter = FrameCounter;
290
291   if (actual_frame_counter >= *frame_counter_var &&
292       actual_frame_counter < *frame_counter_var + frame_delay)
293     return FALSE;
294
295   *frame_counter_var = actual_frame_counter;
296
297   return TRUE;
298 }
299
300 boolean DelayReached(unsigned int *counter_var,
301                      unsigned int delay)
302 {
303   unsigned int actual_counter = Counter();
304
305   if (actual_counter >= *counter_var &&
306       actual_counter < *counter_var + delay)
307     return FALSE;
308
309   *counter_var = actual_counter;
310
311   return TRUE;
312 }
313
314 void WaitUntilDelayReached(unsigned int *counter_var, unsigned int delay)
315 {
316   unsigned int actual_counter;
317
318   while (1)
319   {
320     actual_counter = Counter();
321
322     if (actual_counter >= *counter_var &&
323         actual_counter < *counter_var + delay)
324       sleep_milliseconds((*counter_var + delay - actual_counter) / 2);
325     else
326       break;
327   }
328
329   *counter_var = actual_counter;
330 }
331
332
333 /* ------------------------------------------------------------------------- */
334 /* random generator functions                                                */
335 /* ------------------------------------------------------------------------- */
336
337 unsigned int init_random_number(int nr, int seed)
338 {
339   if (seed == NEW_RANDOMIZE)
340   {
341     /* default random seed */
342     seed = (int)time(NULL);                     // seconds since the epoch
343
344 #if !defined(PLATFORM_WIN32)
345     /* add some more randomness */
346     struct timeval current_time;
347
348     gettimeofday(&current_time, NULL);
349
350     seed += (int)current_time.tv_usec;          // microseconds since the epoch
351 #endif
352
353     /* add some more randomness */
354     seed += (int)SDL_GetTicks();                // milliseconds since SDL init
355
356     /* add some more randomness */
357     seed += GetSimpleRandom(1000000);
358   }
359
360   srandom_linux_libc(nr, (unsigned int) seed);
361
362   return (unsigned int) seed;
363 }
364
365 unsigned int get_random_number(int nr, int max)
366 {
367   return (max > 0 ? random_linux_libc(nr) % max : 0);
368 }
369
370
371 /* ------------------------------------------------------------------------- */
372 /* system info functions                                                     */
373 /* ------------------------------------------------------------------------- */
374
375 #if !defined(PLATFORM_ANDROID)
376 static char *get_corrected_real_name(char *real_name)
377 {
378   char *real_name_new = checked_malloc(MAX_USERNAME_LEN + 1);
379   char *from_ptr = real_name;
380   char *to_ptr   = real_name_new;
381
382   /* copy the name string, but not more than MAX_USERNAME_LEN characters */
383   while (*from_ptr && (int)(to_ptr - real_name_new) < MAX_USERNAME_LEN - 1)
384   {
385     /* the name field read from "passwd" file may also contain additional
386        user information, separated by commas, which will be removed here */
387     if (*from_ptr == ',')
388       break;
389
390     /* the user's real name may contain 'german sharp s' characters,
391        which have no equivalent in upper case letters (used by our fonts) */
392     if (*from_ptr == CHAR_BYTE_SHARP_S)
393     {
394       from_ptr++;
395       *to_ptr++ = 's';
396       *to_ptr++ = 's';
397     }
398     else
399       *to_ptr++ = *from_ptr++;
400   }
401
402   *to_ptr = '\0';
403
404   return real_name_new;
405 }
406 #endif
407
408 char *getLoginName()
409 {
410   static char *login_name = NULL;
411
412 #if defined(PLATFORM_WIN32)
413   if (login_name == NULL)
414   {
415     unsigned long buffer_size = MAX_USERNAME_LEN + 1;
416     login_name = checked_malloc(buffer_size);
417
418     if (GetUserName(login_name, &buffer_size) == 0)
419       strcpy(login_name, ANONYMOUS_NAME);
420   }
421 #else
422   if (login_name == NULL)
423   {
424     struct passwd *pwd;
425
426     if ((pwd = getpwuid(getuid())) == NULL)
427       login_name = ANONYMOUS_NAME;
428     else
429       login_name = getStringCopy(pwd->pw_name);
430   }
431 #endif
432
433   return login_name;
434 }
435
436 char *getRealName()
437 {
438   static char *real_name = NULL;
439
440 #if defined(PLATFORM_WIN32)
441   if (real_name == NULL)
442   {
443     static char buffer[MAX_USERNAME_LEN + 1];
444     unsigned long buffer_size = MAX_USERNAME_LEN + 1;
445
446     if (GetUserName(buffer, &buffer_size) != 0)
447       real_name = get_corrected_real_name(buffer);
448     else
449       real_name = ANONYMOUS_NAME;
450   }
451 #elif defined(PLATFORM_UNIX) && !defined(PLATFORM_ANDROID)
452   if (real_name == NULL)
453   {
454     struct passwd *pwd;
455
456     if ((pwd = getpwuid(getuid())) != NULL && strlen(pwd->pw_gecos) != 0)
457       real_name = get_corrected_real_name(pwd->pw_gecos);
458     else
459       real_name = ANONYMOUS_NAME;
460   }
461 #else
462   real_name = ANONYMOUS_NAME;
463 #endif
464
465   return real_name;
466 }
467
468 time_t getFileTimestampEpochSeconds(char *filename)
469 {
470   struct stat file_status;
471
472   if (stat(filename, &file_status) != 0)        /* cannot stat file */
473     return 0;
474
475   return file_status.st_mtime;
476 }
477
478
479 /* ------------------------------------------------------------------------- */
480 /* path manipulation functions                                               */
481 /* ------------------------------------------------------------------------- */
482
483 static char *getLastPathSeparatorPtr(char *filename)
484 {
485   char *last_separator = strrchr(filename, CHAR_PATH_SEPARATOR_UNIX);
486
487   if (last_separator == NULL)   /* also try DOS/Windows variant */
488     last_separator = strrchr(filename, CHAR_PATH_SEPARATOR_DOS);
489
490   return last_separator;
491 }
492
493 char *getBaseNamePtr(char *filename)
494 {
495   char *last_separator = getLastPathSeparatorPtr(filename);
496
497   if (last_separator != NULL)
498     return last_separator + 1;  /* separator found: strip base path */
499   else
500     return filename;            /* no separator found: filename has no path */
501 }
502
503 char *getBaseName(char *filename)
504 {
505   return getStringCopy(getBaseNamePtr(filename));
506 }
507
508 char *getBasePath(char *filename)
509 {
510   char *basepath = getStringCopy(filename);
511   char *last_separator = getLastPathSeparatorPtr(basepath);
512
513   if (last_separator != NULL)
514     *last_separator = '\0';     /* separator found: strip basename */
515   else
516     basepath = ".";             /* no separator found: use current path */
517
518   return basepath;
519 }
520
521 static char *getProgramMainDataPath()
522 {
523   char *main_data_path = getStringCopy(program.command_basepath);
524
525 #if defined(PLATFORM_MACOSX)
526   static char *main_data_binary_subdir = NULL;
527
528   if (main_data_binary_subdir == NULL)
529   {
530     main_data_binary_subdir = checked_malloc(strlen(program.program_title) + 1 +
531                                              strlen("app") + 1 +
532                                              strlen(MAC_APP_BINARY_SUBDIR) + 1);
533
534     sprintf(main_data_binary_subdir, "%s.app/%s",
535             program.program_title, MAC_APP_BINARY_SUBDIR);
536   }
537
538   // cut relative path to Mac OS X application binary directory from path
539   if (strSuffix(main_data_path, main_data_binary_subdir))
540     main_data_path[strlen(main_data_path) -
541                    strlen(main_data_binary_subdir)] = '\0';
542
543   // cut trailing path separator from path (but not if path is root directory)
544   if (strSuffix(main_data_path, "/") && !strEqual(main_data_path, "/"))
545     main_data_path[strlen(main_data_path) - 1] = '\0';
546 #endif
547
548   return main_data_path;
549 }
550
551
552 /* ------------------------------------------------------------------------- */
553 /* various string functions                                                  */
554 /* ------------------------------------------------------------------------- */
555
556 char *getStringCat2WithSeparator(char *s1, char *s2, char *sep)
557 {
558   char *complete_string = checked_malloc(strlen(s1) + strlen(sep) +
559                                          strlen(s2) + 1);
560
561   sprintf(complete_string, "%s%s%s", s1, sep, s2);
562
563   return complete_string;
564 }
565
566 char *getStringCat3WithSeparator(char *s1, char *s2, char *s3, char *sep)
567 {
568   char *complete_string = checked_malloc(strlen(s1) + strlen(sep) +
569                                          strlen(s2) + strlen(sep) +
570                                          strlen(s3) + 1);
571
572   sprintf(complete_string, "%s%s%s%s%s", s1, sep, s2, sep, s3);
573
574   return complete_string;
575 }
576
577 char *getStringCat2(char *s1, char *s2)
578 {
579   return getStringCat2WithSeparator(s1, s2, "");
580 }
581
582 char *getStringCat3(char *s1, char *s2, char *s3)
583 {
584   return getStringCat3WithSeparator(s1, s2, s3, "");
585 }
586
587 char *getPath2(char *path1, char *path2)
588 {
589 #if defined(PLATFORM_ANDROID)
590   // workaround for reading from APK assets directory -- skip leading "./"
591   if (strEqual(path1, "."))
592     return getStringCopy(path2);
593 #endif
594
595   return getStringCat2WithSeparator(path1, path2, STRING_PATH_SEPARATOR);
596 }
597
598 char *getPath3(char *path1, char *path2, char *path3)
599 {
600 #if defined(PLATFORM_ANDROID)
601   // workaround for reading from APK assets directory -- skip leading "./"
602   if (strEqual(path1, "."))
603     return getStringCat2WithSeparator(path2, path3, STRING_PATH_SEPARATOR);
604 #endif
605
606   return getStringCat3WithSeparator(path1, path2, path3, STRING_PATH_SEPARATOR);
607 }
608
609 char *getImg2(char *path1, char *path2)
610 {
611   char *filename = getPath2(path1, path2);
612
613   if (!fileExists(filename) && strSuffix(path2, ".png"))
614   {
615     // backward compatibility: if PNG file not found, check for PCX file
616     char *path2pcx = getStringCopy(path2);
617
618     strcpy(&path2pcx[strlen(path2pcx) - 3], "pcx");
619
620     free(filename);
621
622     filename = getPath2(path1, path2pcx);
623
624     free(path2pcx);
625   }
626
627   return filename;
628 }
629
630 char *getImg3(char *path1, char *path2, char *path3)
631 {
632   char *filename = getPath3(path1, path2, path3);
633
634   if (!fileExists(filename) && strSuffix(path3, ".png"))
635   {
636     // backward compatibility: if PNG file not found, check for PCX file
637     char *path3pcx = getStringCopy(path3);
638
639     strcpy(&path3pcx[strlen(path3pcx) - 3], "pcx");
640
641     free(filename);
642
643     filename = getPath3(path1, path2, path3pcx);
644
645     free(path3pcx);
646   }
647
648   return filename;
649 }
650
651 char *getStringCopy(const char *s)
652 {
653   char *s_copy;
654
655   if (s == NULL)
656     return NULL;
657
658   s_copy = checked_malloc(strlen(s) + 1);
659   strcpy(s_copy, s);
660
661   return s_copy;
662 }
663
664 char *getStringCopyN(const char *s, int n)
665 {
666   char *s_copy;
667   int s_len = MAX(0, n);
668
669   if (s == NULL)
670     return NULL;
671
672   s_copy = checked_malloc(s_len + 1);
673   strncpy(s_copy, s, s_len);
674   s_copy[s_len] = '\0';
675
676   return s_copy;
677 }
678
679 char *getStringCopyNStatic(const char *s, int n)
680 {
681   static char *s_copy = NULL;
682
683   checked_free(s_copy);
684
685   s_copy = getStringCopyN(s, n);
686
687   return s_copy;
688 }
689
690 char *getStringToLower(const char *s)
691 {
692   char *s_copy = checked_malloc(strlen(s) + 1);
693   char *s_ptr = s_copy;
694
695   while (*s)
696     *s_ptr++ = tolower(*s++);
697   *s_ptr = '\0';
698
699   return s_copy;
700 }
701
702 void setString(char **old_value, char *new_value)
703 {
704   checked_free(*old_value);
705
706   *old_value = getStringCopy(new_value);
707 }
708
709 boolean strEqual(char *s1, char *s2)
710 {
711   return (s1 == NULL && s2 == NULL ? TRUE  :
712           s1 == NULL && s2 != NULL ? FALSE :
713           s1 != NULL && s2 == NULL ? FALSE :
714           strcmp(s1, s2) == 0);
715 }
716
717 boolean strEqualN(char *s1, char *s2, int n)
718 {
719   return (s1 == NULL && s2 == NULL ? TRUE  :
720           s1 == NULL && s2 != NULL ? FALSE :
721           s1 != NULL && s2 == NULL ? FALSE :
722           strncmp(s1, s2, n) == 0);
723 }
724
725 boolean strPrefix(char *s, char *prefix)
726 {
727   return (s == NULL && prefix == NULL ? TRUE  :
728           s == NULL && prefix != NULL ? FALSE :
729           s != NULL && prefix == NULL ? FALSE :
730           strncmp(s, prefix, strlen(prefix)) == 0);
731 }
732
733 boolean strSuffix(char *s, char *suffix)
734 {
735   return (s == NULL && suffix == NULL ? TRUE  :
736           s == NULL && suffix != NULL ? FALSE :
737           s != NULL && suffix == NULL ? FALSE :
738           strlen(s) < strlen(suffix)  ? FALSE :
739           strncmp(&s[strlen(s) - strlen(suffix)], suffix, strlen(suffix)) == 0);
740 }
741
742 boolean strPrefixLower(char *s, char *prefix)
743 {
744   char *s_lower = getStringToLower(s);
745   boolean match = strPrefix(s_lower, prefix);
746
747   free(s_lower);
748
749   return match;
750 }
751
752 boolean strSuffixLower(char *s, char *suffix)
753 {
754   char *s_lower = getStringToLower(s);
755   boolean match = strSuffix(s_lower, suffix);
756
757   free(s_lower);
758
759   return match;
760 }
761
762
763 /* ------------------------------------------------------------------------- */
764 /* command line option handling functions                                    */
765 /* ------------------------------------------------------------------------- */
766
767 void GetOptions(char *argv[],
768                 void (*print_usage_function)(void),
769                 void (*print_version_function)(void))
770 {
771   char *ro_base_path = RO_BASE_PATH;
772   char *rw_base_path = RW_BASE_PATH;
773   char **options_left = &argv[1];
774
775   /* if the program is configured to start from current directory (default),
776      determine program package directory from program binary (some versions
777      of KDE/Konqueror and Mac OS X (especially "Mavericks") apparently do not
778      set the current working directory to the program package directory) */
779
780   if (strEqual(ro_base_path, "."))
781     ro_base_path = getProgramMainDataPath();
782   if (strEqual(rw_base_path, "."))
783     rw_base_path = getProgramMainDataPath();
784
785   /* initialize global program options */
786   options.display_name = NULL;
787   options.server_host = NULL;
788   options.server_port = 0;
789
790   options.ro_base_directory = ro_base_path;
791   options.rw_base_directory = rw_base_path;
792   options.level_directory    = getPath2(ro_base_path, LEVELS_DIRECTORY);
793   options.graphics_directory = getPath2(ro_base_path, GRAPHICS_DIRECTORY);
794   options.sounds_directory   = getPath2(ro_base_path, SOUNDS_DIRECTORY);
795   options.music_directory    = getPath2(ro_base_path, MUSIC_DIRECTORY);
796   options.docs_directory     = getPath2(ro_base_path, DOCS_DIRECTORY);
797
798   options.execute_command = NULL;
799   options.special_flags = NULL;
800
801   options.serveronly = FALSE;
802   options.network = FALSE;
803   options.verbose = FALSE;
804   options.debug = FALSE;
805
806 #if 1
807   options.verbose = TRUE;
808 #else
809 #if !defined(PLATFORM_UNIX)
810   if (*options_left == NULL)    /* no options given -- enable verbose mode */
811     options.verbose = TRUE;
812 #endif
813 #endif
814
815   while (*options_left)
816   {
817     char option_str[MAX_OPTION_LEN];
818     char *option = options_left[0];
819     char *next_option = options_left[1];
820     char *option_arg = NULL;
821     int option_len = strlen(option);
822
823     if (option_len >= MAX_OPTION_LEN)
824       Error(ERR_EXIT_HELP, "unrecognized option '%s'", option);
825
826     strcpy(option_str, option);                 /* copy argument into buffer */
827     option = option_str;
828
829     if (strEqual(option, "--"))                 /* stop scanning arguments */
830       break;
831
832     if (strPrefix(option, "--"))                /* treat '--' like '-' */
833       option++;
834
835     option_arg = strchr(option, '=');
836     if (option_arg == NULL)                     /* no '=' in option */
837       option_arg = next_option;
838     else
839     {
840       *option_arg++ = '\0';                     /* cut argument from option */
841       if (*option_arg == '\0')                  /* no argument after '=' */
842         Error(ERR_EXIT_HELP, "option '%s' has invalid argument", option_str);
843     }
844
845     option_len = strlen(option);
846
847     if (strEqual(option, "-"))
848     {
849       Error(ERR_EXIT_HELP, "unrecognized option '%s'", option);
850     }
851     else if (strncmp(option, "-help", option_len) == 0)
852     {
853       print_usage_function();
854
855       exit(0);
856     }
857     else if (strncmp(option, "-display", option_len) == 0)
858     {
859       if (option_arg == NULL)
860         Error(ERR_EXIT_HELP, "option '%s' requires an argument", option_str);
861
862       options.display_name = option_arg;
863       if (option_arg == next_option)
864         options_left++;
865     }
866     else if (strncmp(option, "-basepath", option_len) == 0)
867     {
868       if (option_arg == NULL)
869         Error(ERR_EXIT_HELP, "option '%s' requires an argument", option_str);
870
871       /* this should be extended to separate options for ro and rw data */
872       options.ro_base_directory = ro_base_path = option_arg;
873       options.rw_base_directory = rw_base_path = option_arg;
874       if (option_arg == next_option)
875         options_left++;
876
877       /* adjust paths for sub-directories in base directory accordingly */
878       options.level_directory    = getPath2(ro_base_path, LEVELS_DIRECTORY);
879       options.graphics_directory = getPath2(ro_base_path, GRAPHICS_DIRECTORY);
880       options.sounds_directory   = getPath2(ro_base_path, SOUNDS_DIRECTORY);
881       options.music_directory    = getPath2(ro_base_path, MUSIC_DIRECTORY);
882       options.docs_directory     = getPath2(ro_base_path, DOCS_DIRECTORY);
883     }
884     else if (strncmp(option, "-levels", option_len) == 0)
885     {
886       if (option_arg == NULL)
887         Error(ERR_EXIT_HELP, "option '%s' requires an argument", option_str);
888
889       options.level_directory = option_arg;
890       if (option_arg == next_option)
891         options_left++;
892     }
893     else if (strncmp(option, "-graphics", option_len) == 0)
894     {
895       if (option_arg == NULL)
896         Error(ERR_EXIT_HELP, "option '%s' requires an argument", option_str);
897
898       options.graphics_directory = option_arg;
899       if (option_arg == next_option)
900         options_left++;
901     }
902     else if (strncmp(option, "-sounds", option_len) == 0)
903     {
904       if (option_arg == NULL)
905         Error(ERR_EXIT_HELP, "option '%s' requires an argument", option_str);
906
907       options.sounds_directory = option_arg;
908       if (option_arg == next_option)
909         options_left++;
910     }
911     else if (strncmp(option, "-music", option_len) == 0)
912     {
913       if (option_arg == NULL)
914         Error(ERR_EXIT_HELP, "option '%s' requires an argument", option_str);
915
916       options.music_directory = option_arg;
917       if (option_arg == next_option)
918         options_left++;
919     }
920     else if (strncmp(option, "-network", option_len) == 0)
921     {
922       options.network = TRUE;
923     }
924     else if (strncmp(option, "-serveronly", option_len) == 0)
925     {
926       options.serveronly = TRUE;
927     }
928     else if (strncmp(option, "-debug", option_len) == 0)
929     {
930       options.debug = TRUE;
931     }
932     else if (strncmp(option, "-verbose", option_len) == 0)
933     {
934       options.verbose = TRUE;
935     }
936     else if (strncmp(option, "-version", option_len) == 0 ||
937              strncmp(option, "-V", option_len) == 0)
938     {
939       print_version_function();
940
941       exit(0);
942     }
943     else if (strPrefix(option, "-D"))
944     {
945       options.special_flags = getStringCopy(&option[2]);
946     }
947     else if (strncmp(option, "-execute", option_len) == 0)
948     {
949       if (option_arg == NULL)
950         Error(ERR_EXIT_HELP, "option '%s' requires an argument", option_str);
951
952       options.execute_command = option_arg;
953       if (option_arg == next_option)
954         options_left++;
955
956       /* when doing batch processing, always enable verbose mode (warnings) */
957       options.verbose = TRUE;
958     }
959     else if (*option == '-')
960     {
961       Error(ERR_EXIT_HELP, "unrecognized option '%s'", option_str);
962     }
963     else if (options.server_host == NULL)
964     {
965       options.server_host = *options_left;
966     }
967     else if (options.server_port == 0)
968     {
969       options.server_port = atoi(*options_left);
970       if (options.server_port < 1024)
971         Error(ERR_EXIT_HELP, "bad port number '%d'", options.server_port);
972     }
973     else
974       Error(ERR_EXIT_HELP, "too many arguments");
975
976     options_left++;
977   }
978 }
979
980
981 /* ------------------------------------------------------------------------- */
982 /* error handling functions                                                  */
983 /* ------------------------------------------------------------------------- */
984
985 #define MAX_INTERNAL_ERROR_SIZE         1024
986
987 /* used by SetError() and GetError() to store internal error messages */
988 static char internal_error[MAX_INTERNAL_ERROR_SIZE];
989
990 void SetError(char *format, ...)
991 {
992   va_list ap;
993
994   va_start(ap, format);
995   vsnprintf(internal_error, MAX_INTERNAL_ERROR_SIZE, format, ap);
996   va_end(ap);
997 }
998
999 char *GetError()
1000 {
1001   return internal_error;
1002 }
1003
1004 void Error(int mode, char *format, ...)
1005 {
1006   static boolean last_line_was_separator = FALSE;
1007   char *process_name = "";
1008
1009 #if defined(PLATFORM_ANDROID)
1010   android_log_prio = (mode & ERR_DEBUG ? ANDROID_LOG_DEBUG :
1011                       mode & ERR_INFO ? ANDROID_LOG_INFO :
1012                       mode & ERR_WARN ? ANDROID_LOG_WARN :
1013                       mode & ERR_EXIT ? ANDROID_LOG_FATAL :
1014                       ANDROID_LOG_UNKNOWN);
1015 #endif
1016
1017   /* display warnings only when running in verbose mode */
1018   if (mode & ERR_WARN && !options.verbose)
1019     return;
1020
1021   if (mode == ERR_INFO_LINE)
1022   {
1023     if (!last_line_was_separator)
1024       fprintf_line(program.error_file, format, 79);
1025
1026     last_line_was_separator = TRUE;
1027
1028     return;
1029   }
1030
1031   last_line_was_separator = FALSE;
1032
1033   if (mode & ERR_SOUND_SERVER)
1034     process_name = " sound server";
1035   else if (mode & ERR_NETWORK_SERVER)
1036     process_name = " network server";
1037   else if (mode & ERR_NETWORK_CLIENT)
1038     process_name = " network client **";
1039
1040   if (format)
1041   {
1042     va_list ap;
1043
1044     fprintf_nonewline(program.error_file, "%s%s: ", program.command_basename,
1045                       process_name);
1046
1047     if (mode & ERR_WARN)
1048       fprintf_nonewline(program.error_file, "warning: ");
1049
1050     if (mode & ERR_EXIT)
1051       fprintf_nonewline(program.error_file, "fatal error: ");
1052
1053     va_start(ap, format);
1054     vfprintf_newline(program.error_file, format, ap);
1055     va_end(ap);
1056
1057     if ((mode & ERR_EXIT) && !(mode & ERR_FROM_SERVER))
1058     {
1059       va_start(ap, format);
1060       program.exit_message_function(format, ap);
1061       va_end(ap);
1062     }
1063   }
1064   
1065   if (mode & ERR_HELP)
1066     fprintf_newline(program.error_file,
1067                     "%s: Try option '--help' for more information.",
1068                     program.command_basename);
1069
1070   if (mode & ERR_EXIT)
1071     fprintf_newline(program.error_file, "%s%s: aborting",
1072                     program.command_basename, process_name);
1073
1074   if (mode & ERR_EXIT)
1075   {
1076     if (mode & ERR_FROM_SERVER)
1077       exit(1);                          /* child process: normal exit */
1078     else
1079       program.exit_function(1);         /* main process: clean up stuff */
1080   }
1081 }
1082
1083
1084 /* ------------------------------------------------------------------------- */
1085 /* checked memory allocation and freeing functions                           */
1086 /* ------------------------------------------------------------------------- */
1087
1088 void *checked_malloc(unsigned int size)
1089 {
1090   void *ptr;
1091
1092   ptr = malloc(size);
1093
1094   if (ptr == NULL)
1095     Error(ERR_EXIT, "cannot allocate %d bytes -- out of memory", size);
1096
1097   return ptr;
1098 }
1099
1100 void *checked_calloc(unsigned int size)
1101 {
1102   void *ptr;
1103
1104   ptr = calloc(1, size);
1105
1106   if (ptr == NULL)
1107     Error(ERR_EXIT, "cannot allocate %d bytes -- out of memory", size);
1108
1109   return ptr;
1110 }
1111
1112 void *checked_realloc(void *ptr, unsigned int size)
1113 {
1114   ptr = realloc(ptr, size);
1115
1116   if (ptr == NULL)
1117     Error(ERR_EXIT, "cannot allocate %d bytes -- out of memory", size);
1118
1119   return ptr;
1120 }
1121
1122 void checked_free(void *ptr)
1123 {
1124   if (ptr != NULL)      /* this check should be done by free() anyway */
1125     free(ptr);
1126 }
1127
1128 void clear_mem(void *ptr, unsigned int size)
1129 {
1130 #if defined(PLATFORM_WIN32)
1131   /* for unknown reason, memset() sometimes crashes when compiled with MinGW */
1132   char *cptr = (char *)ptr;
1133
1134   while (size--)
1135     *cptr++ = 0;
1136 #else
1137   memset(ptr, 0, size);
1138 #endif
1139 }
1140
1141
1142 /* ------------------------------------------------------------------------- */
1143 /* various helper functions                                                  */
1144 /* ------------------------------------------------------------------------- */
1145
1146 inline void swap_numbers(int *i1, int *i2)
1147 {
1148   int help = *i1;
1149
1150   *i1 = *i2;
1151   *i2 = help;
1152 }
1153
1154 inline void swap_number_pairs(int *x1, int *y1, int *x2, int *y2)
1155 {
1156   int help_x = *x1;
1157   int help_y = *y1;
1158
1159   *x1 = *x2;
1160   *x2 = help_x;
1161
1162   *y1 = *y2;
1163   *y2 = help_y;
1164 }
1165
1166 /* the "put" variants of the following file access functions check for the file
1167    pointer being != NULL and return the number of bytes they have or would have
1168    written; this allows for chunk writing functions to first determine the size
1169    of the (not yet written) chunk, write the correct chunk size and finally
1170    write the chunk itself */
1171
1172 int getFile8BitInteger(File *file)
1173 {
1174   return getByteFromFile(file);
1175 }
1176
1177 int putFile8BitInteger(FILE *file, int value)
1178 {
1179   if (file != NULL)
1180     fputc(value, file);
1181
1182   return 1;
1183 }
1184
1185 int getFile16BitInteger(File *file, int byte_order)
1186 {
1187   if (byte_order == BYTE_ORDER_BIG_ENDIAN)
1188     return ((getByteFromFile(file) << 8) |
1189             (getByteFromFile(file) << 0));
1190   else           /* BYTE_ORDER_LITTLE_ENDIAN */
1191     return ((getByteFromFile(file) << 0) |
1192             (getByteFromFile(file) << 8));
1193 }
1194
1195 int putFile16BitInteger(FILE *file, int value, int byte_order)
1196 {
1197   if (file != NULL)
1198   {
1199     if (byte_order == BYTE_ORDER_BIG_ENDIAN)
1200     {
1201       fputc((value >> 8) & 0xff, file);
1202       fputc((value >> 0) & 0xff, file);
1203     }
1204     else           /* BYTE_ORDER_LITTLE_ENDIAN */
1205     {
1206       fputc((value >> 0) & 0xff, file);
1207       fputc((value >> 8) & 0xff, file);
1208     }
1209   }
1210
1211   return 2;
1212 }
1213
1214 int getFile32BitInteger(File *file, int byte_order)
1215 {
1216   if (byte_order == BYTE_ORDER_BIG_ENDIAN)
1217     return ((getByteFromFile(file) << 24) |
1218             (getByteFromFile(file) << 16) |
1219             (getByteFromFile(file) <<  8) |
1220             (getByteFromFile(file) <<  0));
1221   else           /* BYTE_ORDER_LITTLE_ENDIAN */
1222     return ((getByteFromFile(file) <<  0) |
1223             (getByteFromFile(file) <<  8) |
1224             (getByteFromFile(file) << 16) |
1225             (getByteFromFile(file) << 24));
1226 }
1227
1228 int putFile32BitInteger(FILE *file, int value, int byte_order)
1229 {
1230   if (file != NULL)
1231   {
1232     if (byte_order == BYTE_ORDER_BIG_ENDIAN)
1233     {
1234       fputc((value >> 24) & 0xff, file);
1235       fputc((value >> 16) & 0xff, file);
1236       fputc((value >>  8) & 0xff, file);
1237       fputc((value >>  0) & 0xff, file);
1238     }
1239     else           /* BYTE_ORDER_LITTLE_ENDIAN */
1240     {
1241       fputc((value >>  0) & 0xff, file);
1242       fputc((value >>  8) & 0xff, file);
1243       fputc((value >> 16) & 0xff, file);
1244       fputc((value >> 24) & 0xff, file);
1245     }
1246   }
1247
1248   return 4;
1249 }
1250
1251 boolean getFileChunk(File *file, char *chunk_name, int *chunk_size,
1252                      int byte_order)
1253 {
1254   const int chunk_name_length = 4;
1255
1256   /* read chunk name */
1257   if (getStringFromFile(file, chunk_name, chunk_name_length + 1) == NULL)
1258     return FALSE;
1259
1260   if (chunk_size != NULL)
1261   {
1262     /* read chunk size */
1263     *chunk_size = getFile32BitInteger(file, byte_order);
1264   }
1265
1266   return (checkEndOfFile(file) ? FALSE : TRUE);
1267 }
1268
1269 int putFileChunk(FILE *file, char *chunk_name, int chunk_size,
1270                  int byte_order)
1271 {
1272   int num_bytes = 0;
1273
1274   /* write chunk name */
1275   if (file != NULL)
1276     fputs(chunk_name, file);
1277
1278   num_bytes += strlen(chunk_name);
1279
1280   if (chunk_size >= 0)
1281   {
1282     /* write chunk size */
1283     if (file != NULL)
1284       putFile32BitInteger(file, chunk_size, byte_order);
1285
1286     num_bytes += 4;
1287   }
1288
1289   return num_bytes;
1290 }
1291
1292 int getFileVersion(File *file)
1293 {
1294   int version_major = getByteFromFile(file);
1295   int version_minor = getByteFromFile(file);
1296   int version_patch = getByteFromFile(file);
1297   int version_build = getByteFromFile(file);
1298
1299   return VERSION_IDENT(version_major, version_minor, version_patch,
1300                        version_build);
1301 }
1302
1303 int putFileVersion(FILE *file, int version)
1304 {
1305   if (file != NULL)
1306   {
1307     int version_major = VERSION_MAJOR(version);
1308     int version_minor = VERSION_MINOR(version);
1309     int version_patch = VERSION_PATCH(version);
1310     int version_build = VERSION_BUILD(version);
1311
1312     fputc(version_major, file);
1313     fputc(version_minor, file);
1314     fputc(version_patch, file);
1315     fputc(version_build, file);
1316   }
1317
1318   return 4;
1319 }
1320
1321 void ReadBytesFromFile(File *file, byte *buffer, unsigned int bytes)
1322 {
1323   int i;
1324
1325   for (i = 0; i < bytes && !checkEndOfFile(file); i++)
1326     buffer[i] = getByteFromFile(file);
1327 }
1328
1329 void WriteBytesToFile(FILE *file, byte *buffer, unsigned int bytes)
1330 {
1331   int i;
1332
1333   for(i = 0; i < bytes; i++)
1334     fputc(buffer[i], file);
1335 }
1336
1337 void ReadUnusedBytesFromFile(File *file, unsigned int bytes)
1338 {
1339   while (bytes-- && !checkEndOfFile(file))
1340     getByteFromFile(file);
1341 }
1342
1343 void WriteUnusedBytesToFile(FILE *file, unsigned int bytes)
1344 {
1345   while (bytes--)
1346     fputc(0, file);
1347 }
1348
1349
1350 /* ------------------------------------------------------------------------- */
1351 /* functions to translate key identifiers between different format           */
1352 /* ------------------------------------------------------------------------- */
1353
1354 #define TRANSLATE_KEYSYM_TO_KEYNAME     0
1355 #define TRANSLATE_KEYSYM_TO_X11KEYNAME  1
1356 #define TRANSLATE_KEYNAME_TO_KEYSYM     2
1357 #define TRANSLATE_X11KEYNAME_TO_KEYSYM  3
1358
1359 void translate_keyname(Key *keysym, char **x11name, char **name, int mode)
1360 {
1361   static struct
1362   {
1363     Key key;
1364     char *x11name;
1365     char *name;
1366   } translate_key[] =
1367   {
1368     /* normal cursor keys */
1369     { KSYM_Left,        "XK_Left",              "cursor left" },
1370     { KSYM_Right,       "XK_Right",             "cursor right" },
1371     { KSYM_Up,          "XK_Up",                "cursor up" },
1372     { KSYM_Down,        "XK_Down",              "cursor down" },
1373
1374     /* keypad cursor keys */
1375 #ifdef KSYM_KP_Left
1376     { KSYM_KP_Left,     "XK_KP_Left",           "keypad left" },
1377     { KSYM_KP_Right,    "XK_KP_Right",          "keypad right" },
1378     { KSYM_KP_Up,       "XK_KP_Up",             "keypad up" },
1379     { KSYM_KP_Down,     "XK_KP_Down",           "keypad down" },
1380 #endif
1381
1382     /* other keypad keys */
1383 #ifdef KSYM_KP_Enter
1384     { KSYM_KP_Enter,    "XK_KP_Enter",          "keypad enter" },
1385     { KSYM_KP_Add,      "XK_KP_Add",            "keypad +" },
1386     { KSYM_KP_Subtract, "XK_KP_Subtract",       "keypad -" },
1387     { KSYM_KP_Multiply, "XK_KP_Multiply",       "keypad mltply" },
1388     { KSYM_KP_Divide,   "XK_KP_Divide",         "keypad /" },
1389     { KSYM_KP_Separator,"XK_KP_Separator",      "keypad ," },
1390 #endif
1391
1392     /* modifier keys */
1393     { KSYM_Shift_L,     "XK_Shift_L",           "left shift" },
1394     { KSYM_Shift_R,     "XK_Shift_R",           "right shift" },
1395     { KSYM_Control_L,   "XK_Control_L",         "left control" },
1396     { KSYM_Control_R,   "XK_Control_R",         "right control" },
1397     { KSYM_Meta_L,      "XK_Meta_L",            "left meta" },
1398     { KSYM_Meta_R,      "XK_Meta_R",            "right meta" },
1399     { KSYM_Alt_L,       "XK_Alt_L",             "left alt" },
1400     { KSYM_Alt_R,       "XK_Alt_R",             "right alt" },
1401 #if !defined(TARGET_SDL2)
1402     { KSYM_Super_L,     "XK_Super_L",           "left super" },  /* Win-L */
1403     { KSYM_Super_R,     "XK_Super_R",           "right super" }, /* Win-R */
1404 #endif
1405     { KSYM_Mode_switch, "XK_Mode_switch",       "mode switch" }, /* Alt-R */
1406     { KSYM_Multi_key,   "XK_Multi_key",         "multi key" },   /* Ctrl-R */
1407
1408     /* some special keys */
1409     { KSYM_BackSpace,   "XK_BackSpace",         "backspace" },
1410     { KSYM_Delete,      "XK_Delete",            "delete" },
1411     { KSYM_Insert,      "XK_Insert",            "insert" },
1412     { KSYM_Tab,         "XK_Tab",               "tab" },
1413     { KSYM_Home,        "XK_Home",              "home" },
1414     { KSYM_End,         "XK_End",               "end" },
1415     { KSYM_Page_Up,     "XK_Page_Up",           "page up" },
1416     { KSYM_Page_Down,   "XK_Page_Down",         "page down" },
1417
1418 #if defined(TARGET_SDL2)
1419     { KSYM_Menu,        "XK_Menu",              "menu" },        /* menu key */
1420     { KSYM_Back,        "XK_Back",              "back" },        /* back key */
1421 #endif
1422
1423     /* ASCII 0x20 to 0x40 keys (except numbers) */
1424     { KSYM_space,       "XK_space",             "space" },
1425     { KSYM_exclam,      "XK_exclam",            "!" },
1426     { KSYM_quotedbl,    "XK_quotedbl",          "\"" },
1427     { KSYM_numbersign,  "XK_numbersign",        "#" },
1428     { KSYM_dollar,      "XK_dollar",            "$" },
1429     { KSYM_percent,     "XK_percent",           "%" },
1430     { KSYM_ampersand,   "XK_ampersand",         "&" },
1431     { KSYM_apostrophe,  "XK_apostrophe",        "'" },
1432     { KSYM_parenleft,   "XK_parenleft",         "(" },
1433     { KSYM_parenright,  "XK_parenright",        ")" },
1434     { KSYM_asterisk,    "XK_asterisk",          "*" },
1435     { KSYM_plus,        "XK_plus",              "+" },
1436     { KSYM_comma,       "XK_comma",             "," },
1437     { KSYM_minus,       "XK_minus",             "-" },
1438     { KSYM_period,      "XK_period",            "." },
1439     { KSYM_slash,       "XK_slash",             "/" },
1440     { KSYM_colon,       "XK_colon",             ":" },
1441     { KSYM_semicolon,   "XK_semicolon",         ";" },
1442     { KSYM_less,        "XK_less",              "<" },
1443     { KSYM_equal,       "XK_equal",             "=" },
1444     { KSYM_greater,     "XK_greater",           ">" },
1445     { KSYM_question,    "XK_question",          "?" },
1446     { KSYM_at,          "XK_at",                "@" },
1447
1448     /* more ASCII keys */
1449     { KSYM_bracketleft, "XK_bracketleft",       "[" },
1450     { KSYM_backslash,   "XK_backslash",         "\\" },
1451     { KSYM_bracketright,"XK_bracketright",      "]" },
1452     { KSYM_asciicircum, "XK_asciicircum",       "^" },
1453     { KSYM_underscore,  "XK_underscore",        "_" },
1454     { KSYM_grave,       "XK_grave",             "grave" },
1455     { KSYM_quoteleft,   "XK_quoteleft",         "quote left" },
1456     { KSYM_braceleft,   "XK_braceleft",         "brace left" },
1457     { KSYM_bar,         "XK_bar",               "bar" },
1458     { KSYM_braceright,  "XK_braceright",        "brace right" },
1459     { KSYM_asciitilde,  "XK_asciitilde",        "~" },
1460
1461     /* special (non-ASCII) keys */
1462     { KSYM_degree,      "XK_degree",            "degree" },
1463     { KSYM_Adiaeresis,  "XK_Adiaeresis",        "A umlaut" },
1464     { KSYM_Odiaeresis,  "XK_Odiaeresis",        "O umlaut" },
1465     { KSYM_Udiaeresis,  "XK_Udiaeresis",        "U umlaut" },
1466     { KSYM_adiaeresis,  "XK_adiaeresis",        "a umlaut" },
1467     { KSYM_odiaeresis,  "XK_odiaeresis",        "o umlaut" },
1468     { KSYM_udiaeresis,  "XK_udiaeresis",        "u umlaut" },
1469     { KSYM_ssharp,      "XK_ssharp",            "sharp s" },
1470
1471 #if defined(TARGET_SDL2)
1472     /* special (non-ASCII) keys (UTF-8, for reverse mapping only) */
1473     { KSYM_degree,      "XK_degree",            "\xc2\xb0" },
1474     { KSYM_Adiaeresis,  "XK_Adiaeresis",        "\xc3\x84" },
1475     { KSYM_Odiaeresis,  "XK_Odiaeresis",        "\xc3\x96" },
1476     { KSYM_Udiaeresis,  "XK_Udiaeresis",        "\xc3\x9c" },
1477     { KSYM_adiaeresis,  "XK_adiaeresis",        "\xc3\xa4" },
1478     { KSYM_odiaeresis,  "XK_odiaeresis",        "\xc3\xb6" },
1479     { KSYM_udiaeresis,  "XK_udiaeresis",        "\xc3\xbc" },
1480     { KSYM_ssharp,      "XK_ssharp",            "\xc3\x9f" },
1481
1482     /* other keys (for reverse mapping only) */
1483     { KSYM_space,       "XK_space",             " " },
1484 #endif
1485
1486 #if defined(TARGET_SDL2)
1487     /* keypad keys are not in numerical order in SDL2 */
1488     { KSYM_KP_0,        "XK_KP_0",              "keypad 0" },
1489     { KSYM_KP_1,        "XK_KP_1",              "keypad 1" },
1490     { KSYM_KP_2,        "XK_KP_2",              "keypad 2" },
1491     { KSYM_KP_3,        "XK_KP_3",              "keypad 3" },
1492     { KSYM_KP_4,        "XK_KP_4",              "keypad 4" },
1493     { KSYM_KP_5,        "XK_KP_5",              "keypad 5" },
1494     { KSYM_KP_6,        "XK_KP_6",              "keypad 6" },
1495     { KSYM_KP_7,        "XK_KP_7",              "keypad 7" },
1496     { KSYM_KP_8,        "XK_KP_8",              "keypad 8" },
1497     { KSYM_KP_9,        "XK_KP_9",              "keypad 9" },
1498 #endif
1499
1500     /* end-of-array identifier */
1501     { 0,                NULL,                   NULL }
1502   };
1503
1504   int i;
1505
1506   if (mode == TRANSLATE_KEYSYM_TO_KEYNAME)
1507   {
1508     static char name_buffer[30];
1509     Key key = *keysym;
1510
1511     if (key >= KSYM_A && key <= KSYM_Z)
1512       sprintf(name_buffer, "%c", 'A' + (char)(key - KSYM_A));
1513     else if (key >= KSYM_a && key <= KSYM_z)
1514       sprintf(name_buffer, "%c", 'a' + (char)(key - KSYM_a));
1515     else if (key >= KSYM_0 && key <= KSYM_9)
1516       sprintf(name_buffer, "%c", '0' + (char)(key - KSYM_0));
1517 #if !defined(TARGET_SDL2)
1518     else if (key >= KSYM_KP_0 && key <= KSYM_KP_9)
1519       sprintf(name_buffer, "keypad %c", '0' + (char)(key - KSYM_KP_0));
1520 #endif
1521     else if (key >= KSYM_FKEY_FIRST && key <= KSYM_FKEY_LAST)
1522       sprintf(name_buffer, "F%d", (int)(key - KSYM_FKEY_FIRST + 1));
1523     else if (key == KSYM_UNDEFINED)
1524       strcpy(name_buffer, "(undefined)");
1525     else
1526     {
1527       i = 0;
1528
1529       do
1530       {
1531         if (key == translate_key[i].key)
1532         {
1533           strcpy(name_buffer, translate_key[i].name);
1534           break;
1535         }
1536       }
1537       while (translate_key[++i].name);
1538
1539       if (!translate_key[i].name)
1540         strcpy(name_buffer, "(unknown)");
1541     }
1542
1543     *name = name_buffer;
1544   }
1545   else if (mode == TRANSLATE_KEYSYM_TO_X11KEYNAME)
1546   {
1547     static char name_buffer[30];
1548     Key key = *keysym;
1549
1550     if (key >= KSYM_A && key <= KSYM_Z)
1551       sprintf(name_buffer, "XK_%c", 'A' + (char)(key - KSYM_A));
1552     else if (key >= KSYM_a && key <= KSYM_z)
1553       sprintf(name_buffer, "XK_%c", 'a' + (char)(key - KSYM_a));
1554     else if (key >= KSYM_0 && key <= KSYM_9)
1555       sprintf(name_buffer, "XK_%c", '0' + (char)(key - KSYM_0));
1556 #if !defined(TARGET_SDL2)
1557     else if (key >= KSYM_KP_0 && key <= KSYM_KP_9)
1558       sprintf(name_buffer, "XK_KP_%c", '0' + (char)(key - KSYM_KP_0));
1559 #endif
1560     else if (key >= KSYM_FKEY_FIRST && key <= KSYM_FKEY_LAST)
1561       sprintf(name_buffer, "XK_F%d", (int)(key - KSYM_FKEY_FIRST + 1));
1562     else if (key == KSYM_UNDEFINED)
1563       strcpy(name_buffer, "[undefined]");
1564     else
1565     {
1566       i = 0;
1567
1568       do
1569       {
1570         if (key == translate_key[i].key)
1571         {
1572           strcpy(name_buffer, translate_key[i].x11name);
1573           break;
1574         }
1575       }
1576       while (translate_key[++i].x11name);
1577
1578       if (!translate_key[i].x11name)
1579         sprintf(name_buffer, "0x%04x", (unsigned int)key);
1580     }
1581
1582     *x11name = name_buffer;
1583   }
1584   else if (mode == TRANSLATE_KEYNAME_TO_KEYSYM)
1585   {
1586     Key key = KSYM_UNDEFINED;
1587     char *name_ptr = *name;
1588
1589     if (strlen(*name) == 1)
1590     {
1591       char c = name_ptr[0];
1592
1593       if (c >= 'A' && c <= 'Z')
1594         key = KSYM_A + (Key)(c - 'A');
1595       else if (c >= 'a' && c <= 'z')
1596         key = KSYM_a + (Key)(c - 'a');
1597       else if (c >= '0' && c <= '9')
1598         key = KSYM_0 + (Key)(c - '0');
1599     }
1600
1601     if (key == KSYM_UNDEFINED)
1602     {
1603       i = 0;
1604
1605       do
1606       {
1607         if (strEqual(translate_key[i].name, *name))
1608         {
1609           key = translate_key[i].key;
1610           break;
1611         }
1612       }
1613       while (translate_key[++i].x11name);
1614     }
1615
1616     if (key == KSYM_UNDEFINED)
1617       Error(ERR_WARN, "getKeyFromKeyName(): not completely implemented");
1618
1619     *keysym = key;
1620   }
1621   else if (mode == TRANSLATE_X11KEYNAME_TO_KEYSYM)
1622   {
1623     Key key = KSYM_UNDEFINED;
1624     char *name_ptr = *x11name;
1625
1626     if (strPrefix(name_ptr, "XK_") && strlen(name_ptr) == 4)
1627     {
1628       char c = name_ptr[3];
1629
1630       if (c >= 'A' && c <= 'Z')
1631         key = KSYM_A + (Key)(c - 'A');
1632       else if (c >= 'a' && c <= 'z')
1633         key = KSYM_a + (Key)(c - 'a');
1634       else if (c >= '0' && c <= '9')
1635         key = KSYM_0 + (Key)(c - '0');
1636     }
1637 #if !defined(TARGET_SDL2)
1638     else if (strPrefix(name_ptr, "XK_KP_") && strlen(name_ptr) == 7)
1639     {
1640       char c = name_ptr[6];
1641
1642       if (c >= '0' && c <= '9')
1643         key = KSYM_KP_0 + (Key)(c - '0');
1644     }
1645 #endif
1646     else if (strPrefix(name_ptr, "XK_F") && strlen(name_ptr) <= 6)
1647     {
1648       char c1 = name_ptr[4];
1649       char c2 = name_ptr[5];
1650       int d = 0;
1651
1652       if ((c1 >= '0' && c1 <= '9') &&
1653           ((c2 >= '0' && c1 <= '9') || c2 == '\0'))
1654         d = atoi(&name_ptr[4]);
1655
1656       if (d >= 1 && d <= KSYM_NUM_FKEYS)
1657         key = KSYM_F1 + (Key)(d - 1);
1658     }
1659     else if (strPrefix(name_ptr, "XK_"))
1660     {
1661       i = 0;
1662
1663       do
1664       {
1665         if (strEqual(name_ptr, translate_key[i].x11name))
1666         {
1667           key = translate_key[i].key;
1668           break;
1669         }
1670       }
1671       while (translate_key[++i].x11name);
1672     }
1673     else if (strPrefix(name_ptr, "0x"))
1674     {
1675       unsigned int value = 0;
1676
1677       name_ptr += 2;
1678
1679       while (name_ptr)
1680       {
1681         char c = *name_ptr++;
1682         int d = -1;
1683
1684         if (c >= '0' && c <= '9')
1685           d = (int)(c - '0');
1686         else if (c >= 'a' && c <= 'f')
1687           d = (int)(c - 'a' + 10);
1688         else if (c >= 'A' && c <= 'F')
1689           d = (int)(c - 'A' + 10);
1690
1691         if (d == -1)
1692         {
1693           value = -1;
1694           break;
1695         }
1696
1697         value = value * 16 + d;
1698       }
1699
1700       if (value != -1)
1701         key = (Key)value;
1702     }
1703
1704     *keysym = key;
1705   }
1706 }
1707
1708 char *getKeyNameFromKey(Key key)
1709 {
1710   char *name;
1711
1712   translate_keyname(&key, NULL, &name, TRANSLATE_KEYSYM_TO_KEYNAME);
1713   return name;
1714 }
1715
1716 char *getX11KeyNameFromKey(Key key)
1717 {
1718   char *x11name;
1719
1720   translate_keyname(&key, &x11name, NULL, TRANSLATE_KEYSYM_TO_X11KEYNAME);
1721   return x11name;
1722 }
1723
1724 Key getKeyFromKeyName(char *name)
1725 {
1726   Key key;
1727
1728   translate_keyname(&key, NULL, &name, TRANSLATE_KEYNAME_TO_KEYSYM);
1729   return key;
1730 }
1731
1732 Key getKeyFromX11KeyName(char *x11name)
1733 {
1734   Key key;
1735
1736   translate_keyname(&key, &x11name, NULL, TRANSLATE_X11KEYNAME_TO_KEYSYM);
1737   return key;
1738 }
1739
1740 char getCharFromKey(Key key)
1741 {
1742   static struct
1743   {
1744     Key key;
1745     byte key_char;
1746   } translate_key_char[] =
1747   {
1748     /* special (non-ASCII) keys (ISO-8859-1) */
1749     { KSYM_degree,      CHAR_BYTE_DEGREE        },
1750     { KSYM_Adiaeresis,  CHAR_BYTE_UMLAUT_A      },
1751     { KSYM_Odiaeresis,  CHAR_BYTE_UMLAUT_O      },
1752     { KSYM_Udiaeresis,  CHAR_BYTE_UMLAUT_U      },
1753     { KSYM_adiaeresis,  CHAR_BYTE_UMLAUT_a      },
1754     { KSYM_odiaeresis,  CHAR_BYTE_UMLAUT_o      },
1755     { KSYM_udiaeresis,  CHAR_BYTE_UMLAUT_u      },
1756     { KSYM_ssharp,      CHAR_BYTE_SHARP_S       },
1757
1758     /* end-of-array identifier */
1759     { 0,                0                       }
1760   };
1761
1762   char *keyname = getKeyNameFromKey(key);
1763   char c = 0;
1764
1765   if (strlen(keyname) == 1)
1766     c = keyname[0];
1767   else if (strEqual(keyname, "space"))
1768     c = ' ';
1769   else
1770   {
1771     int i = 0;
1772
1773     do
1774     {
1775       if (key == translate_key_char[i].key)
1776       {
1777         c = translate_key_char[i].key_char;
1778
1779         break;
1780       }
1781     }
1782     while (translate_key_char[++i].key_char);
1783   }
1784
1785   return c;
1786 }
1787
1788 char getValidConfigValueChar(char c)
1789 {
1790   if (c == '#' ||       /* used to mark comments */
1791       c == '\\')        /* used to mark continued lines */
1792     c = 0;
1793
1794   return c;
1795 }
1796
1797
1798 /* ------------------------------------------------------------------------- */
1799 /* functions to translate string identifiers to integer or boolean value     */
1800 /* ------------------------------------------------------------------------- */
1801
1802 int get_integer_from_string(char *s)
1803 {
1804   static char *number_text[][3] =
1805   {
1806     { "0",      "zero",         "null",         },
1807     { "1",      "one",          "first"         },
1808     { "2",      "two",          "second"        },
1809     { "3",      "three",        "third"         },
1810     { "4",      "four",         "fourth"        },
1811     { "5",      "five",         "fifth"         },
1812     { "6",      "six",          "sixth"         },
1813     { "7",      "seven",        "seventh"       },
1814     { "8",      "eight",        "eighth"        },
1815     { "9",      "nine",         "ninth"         },
1816     { "10",     "ten",          "tenth"         },
1817     { "11",     "eleven",       "eleventh"      },
1818     { "12",     "twelve",       "twelfth"       },
1819
1820     { NULL,     NULL,           NULL            },
1821   };
1822
1823   int i, j;
1824   char *s_lower = getStringToLower(s);
1825   int result = -1;
1826
1827   for (i = 0; number_text[i][0] != NULL; i++)
1828     for (j = 0; j < 3; j++)
1829       if (strEqual(s_lower, number_text[i][j]))
1830         result = i;
1831
1832   if (result == -1)
1833   {
1834     if (strEqual(s_lower, "false") ||
1835         strEqual(s_lower, "no") ||
1836         strEqual(s_lower, "off"))
1837       result = 0;
1838     else if (strEqual(s_lower, "true") ||
1839              strEqual(s_lower, "yes") ||
1840              strEqual(s_lower, "on"))
1841       result = 1;
1842     else
1843       result = atoi(s);
1844   }
1845
1846   free(s_lower);
1847
1848   return result;
1849 }
1850
1851 boolean get_boolean_from_string(char *s)
1852 {
1853   char *s_lower = getStringToLower(s);
1854   boolean result = FALSE;
1855
1856   if (strEqual(s_lower, "true") ||
1857       strEqual(s_lower, "yes") ||
1858       strEqual(s_lower, "on") ||
1859       get_integer_from_string(s) == 1)
1860     result = TRUE;
1861
1862   free(s_lower);
1863
1864   return result;
1865 }
1866
1867 int get_switch3_from_string(char *s)
1868 {
1869   char *s_lower = getStringToLower(s);
1870   int result = FALSE;
1871
1872   if (strEqual(s_lower, "true") ||
1873       strEqual(s_lower, "yes") ||
1874       strEqual(s_lower, "on") ||
1875       get_integer_from_string(s) == 1)
1876     result = TRUE;
1877   else if (strEqual(s_lower, "auto"))
1878     result = AUTO;
1879
1880   free(s_lower);
1881
1882   return result;
1883 }
1884
1885
1886 /* ------------------------------------------------------------------------- */
1887 /* functions for generic lists                                               */
1888 /* ------------------------------------------------------------------------- */
1889
1890 ListNode *newListNode()
1891 {
1892   return checked_calloc(sizeof(ListNode));
1893 }
1894
1895 void addNodeToList(ListNode **node_first, char *key, void *content)
1896 {
1897   ListNode *node_new = newListNode();
1898
1899   node_new->key = getStringCopy(key);
1900   node_new->content = content;
1901   node_new->next = *node_first;
1902   *node_first = node_new;
1903 }
1904
1905 void deleteNodeFromList(ListNode **node_first, char *key,
1906                         void (*destructor_function)(void *))
1907 {
1908   if (node_first == NULL || *node_first == NULL)
1909     return;
1910
1911   if (strEqual((*node_first)->key, key))
1912   {
1913     checked_free((*node_first)->key);
1914     if (destructor_function)
1915       destructor_function((*node_first)->content);
1916     *node_first = (*node_first)->next;
1917   }
1918   else
1919     deleteNodeFromList(&(*node_first)->next, key, destructor_function);
1920 }
1921
1922 ListNode *getNodeFromKey(ListNode *node_first, char *key)
1923 {
1924   if (node_first == NULL)
1925     return NULL;
1926
1927   if (strEqual(node_first->key, key))
1928     return node_first;
1929   else
1930     return getNodeFromKey(node_first->next, key);
1931 }
1932
1933 int getNumNodes(ListNode *node_first)
1934 {
1935   return (node_first ? 1 + getNumNodes(node_first->next) : 0);
1936 }
1937
1938 void dumpList(ListNode *node_first)
1939 {
1940   ListNode *node = node_first;
1941
1942   while (node)
1943   {
1944     printf("['%s' (%d)]\n", node->key,
1945            ((struct ListNodeInfo *)node->content)->num_references);
1946     node = node->next;
1947   }
1948
1949   printf("[%d nodes]\n", getNumNodes(node_first));
1950 }
1951
1952
1953 /* ------------------------------------------------------------------------- */
1954 /* functions for file handling                                               */
1955 /* ------------------------------------------------------------------------- */
1956
1957 File *openFile(char *filename, char *mode)
1958 {
1959   File *file = checked_calloc(sizeof(File));
1960
1961   file->file = fopen(filename, mode);
1962
1963   if (file->file != NULL)
1964   {
1965     file->filename = getStringCopy(filename);
1966
1967     return file;
1968   }
1969
1970 #if defined(PLATFORM_ANDROID)
1971   file->asset_file = SDL_RWFromFile(filename, mode);
1972
1973   if (file->asset_file != NULL)
1974   {
1975     file->file_is_asset = TRUE;
1976     file->filename = getStringCopy(filename);
1977
1978     return file;
1979   }
1980 #endif
1981
1982   checked_free(file);
1983
1984   return NULL;
1985 }
1986
1987 int closeFile(File *file)
1988 {
1989   if (file == NULL)
1990     return -1;
1991
1992   int result = 0;
1993
1994 #if defined(PLATFORM_ANDROID)
1995   if (file->asset_file)
1996     result = SDL_RWclose(file->asset_file);
1997 #endif
1998
1999   if (file->file)
2000     result = fclose(file->file);
2001
2002   checked_free(file->filename);
2003   checked_free(file);
2004
2005   return result;
2006 }
2007
2008 int checkEndOfFile(File *file)
2009 {
2010 #if defined(PLATFORM_ANDROID)
2011   if (file->file_is_asset)
2012     return file->end_of_file;
2013 #endif
2014
2015   return feof(file->file);
2016 }
2017
2018 size_t readFile(File *file, void *buffer, size_t item_size, size_t num_items)
2019 {
2020 #if defined(PLATFORM_ANDROID)
2021   if (file->file_is_asset)
2022   {
2023     if (file->end_of_file)
2024       return 0;
2025
2026     size_t num_items_read =
2027       SDL_RWread(file->asset_file, buffer, item_size, num_items);
2028
2029     if (num_items_read < num_items)
2030       file->end_of_file = TRUE;
2031
2032     return num_items_read;
2033   }
2034 #endif
2035
2036   return fread(buffer, item_size, num_items, file->file);
2037 }
2038
2039 int seekFile(File *file, long offset, int whence)
2040 {
2041 #if defined(PLATFORM_ANDROID)
2042   if (file->file_is_asset)
2043   {
2044     int sdl_whence = (whence == SEEK_SET ? RW_SEEK_SET :
2045                       whence == SEEK_CUR ? RW_SEEK_CUR :
2046                       whence == SEEK_END ? RW_SEEK_END : 0);
2047
2048     return (SDL_RWseek(file->asset_file, offset, sdl_whence) == -1 ? -1 : 0);
2049   }
2050 #endif
2051
2052   return fseek(file->file, offset, whence);
2053 }
2054
2055 int getByteFromFile(File *file)
2056 {
2057 #if defined(PLATFORM_ANDROID)
2058   if (file->file_is_asset)
2059   {
2060     if (file->end_of_file)
2061       return EOF;
2062
2063     byte c;
2064     size_t num_bytes_read = SDL_RWread(file->asset_file, &c, 1, 1);
2065
2066     if (num_bytes_read < 1)
2067       file->end_of_file = TRUE;
2068
2069     return (file->end_of_file ? EOF : (int)c);
2070   }
2071 #endif
2072
2073   return fgetc(file->file);
2074 }
2075
2076 char *getStringFromFile(File *file, char *line, int size)
2077 {
2078 #if defined(PLATFORM_ANDROID)
2079   if (file->file_is_asset)
2080   {
2081     if (file->end_of_file)
2082       return NULL;
2083
2084     char *line_ptr = line;
2085     int num_bytes_read = 0;
2086
2087     while (num_bytes_read < size - 1 &&
2088            SDL_RWread(file->asset_file, line_ptr, 1, 1) == 1 &&
2089            *line_ptr++ != '\n')
2090       num_bytes_read++;
2091
2092     *line_ptr = '\0';
2093
2094     if (strlen(line) == 0)
2095     {
2096       file->end_of_file = TRUE;
2097
2098       return NULL;
2099     }
2100
2101     return line;
2102   }
2103 #endif
2104
2105   return fgets(line, size, file->file);
2106 }
2107
2108
2109 /* ------------------------------------------------------------------------- */
2110 /* functions for directory handling                                          */
2111 /* ------------------------------------------------------------------------- */
2112
2113 Directory *openDirectory(char *dir_name)
2114 {
2115   Directory *dir = checked_calloc(sizeof(Directory));
2116
2117   dir->dir = opendir(dir_name);
2118
2119   if (dir->dir != NULL)
2120   {
2121     dir->filename = getStringCopy(dir_name);
2122
2123     return dir;
2124   }
2125
2126 #if defined(PLATFORM_ANDROID)
2127   char *asset_toc_filename = getPath2(dir_name, ASSET_TOC_BASENAME);
2128
2129   dir->asset_toc_file = SDL_RWFromFile(asset_toc_filename, MODE_READ);
2130
2131   checked_free(asset_toc_filename);
2132
2133   if (dir->asset_toc_file != NULL)
2134   {
2135     dir->directory_is_asset = TRUE;
2136     dir->filename = getStringCopy(dir_name);
2137
2138     return dir;
2139   }
2140 #endif
2141
2142   checked_free(dir);
2143
2144   return NULL;
2145 }
2146
2147 int closeDirectory(Directory *dir)
2148 {
2149   if (dir == NULL)
2150     return -1;
2151
2152   int result = 0;
2153
2154 #if defined(PLATFORM_ANDROID)
2155   if (dir->asset_toc_file)
2156     result = SDL_RWclose(dir->asset_toc_file);
2157 #endif
2158
2159   if (dir->dir)
2160     result = closedir(dir->dir);
2161
2162   if (dir->dir_entry)
2163     freeDirectoryEntry(dir->dir_entry);
2164
2165   checked_free(dir->filename);
2166   checked_free(dir);
2167
2168   return result;
2169 }
2170
2171 DirectoryEntry *readDirectory(Directory *dir)
2172 {
2173   if (dir->dir_entry)
2174     freeDirectoryEntry(dir->dir_entry);
2175
2176   dir->dir_entry = NULL;
2177
2178 #if defined(PLATFORM_ANDROID)
2179   if (dir->directory_is_asset)
2180   {
2181     char line[MAX_LINE_LEN];
2182     char *line_ptr = line;
2183     int num_bytes_read = 0;
2184
2185     while (num_bytes_read < MAX_LINE_LEN - 1 &&
2186            SDL_RWread(dir->asset_toc_file, line_ptr, 1, 1) == 1 &&
2187            *line_ptr != '\n')
2188     {
2189       line_ptr++;
2190       num_bytes_read++;
2191     }
2192
2193     *line_ptr = '\0';
2194
2195     if (strlen(line) == 0)
2196       return NULL;
2197
2198     dir->dir_entry = checked_calloc(sizeof(DirectoryEntry));
2199
2200     dir->dir_entry->is_directory = FALSE;
2201     if (line[strlen(line) - 1] == '/')
2202     {
2203       dir->dir_entry->is_directory = TRUE;
2204
2205       line[strlen(line) - 1] = '\0';
2206     }
2207
2208     dir->dir_entry->basename = getStringCopy(line);
2209     dir->dir_entry->filename = getPath2(dir->filename, line);
2210
2211     return dir->dir_entry;
2212   }
2213 #endif
2214
2215   struct dirent *dir_entry = readdir(dir->dir);
2216
2217   if (dir_entry == NULL)
2218     return NULL;
2219
2220   dir->dir_entry = checked_calloc(sizeof(DirectoryEntry));
2221
2222   dir->dir_entry->basename = getStringCopy(dir_entry->d_name);
2223   dir->dir_entry->filename = getPath2(dir->filename, dir_entry->d_name);
2224
2225   struct stat file_status;
2226
2227   dir->dir_entry->is_directory =
2228     (stat(dir->dir_entry->filename, &file_status) == 0 &&
2229      (file_status.st_mode & S_IFMT) == S_IFDIR);
2230
2231   return dir->dir_entry;
2232 }
2233
2234 void freeDirectoryEntry(DirectoryEntry *dir_entry)
2235 {
2236   if (dir_entry == NULL)
2237     return;
2238
2239   checked_free(dir_entry->basename);
2240   checked_free(dir_entry->filename);
2241   checked_free(dir_entry);
2242 }
2243
2244
2245 /* ------------------------------------------------------------------------- */
2246 /* functions for checking files and filenames                                */
2247 /* ------------------------------------------------------------------------- */
2248
2249 boolean directoryExists(char *dir_name)
2250 {
2251   if (dir_name == NULL)
2252     return FALSE;
2253
2254   struct stat file_status;
2255   boolean success = (stat(dir_name, &file_status) == 0 &&
2256                      (file_status.st_mode & S_IFMT) == S_IFDIR);
2257
2258 #if defined(PLATFORM_ANDROID)
2259   if (!success)
2260   {
2261     // this might be an asset directory; check by trying to open toc file
2262     char *asset_toc_filename = getPath2(dir_name, ASSET_TOC_BASENAME);
2263     SDL_RWops *file = SDL_RWFromFile(asset_toc_filename, MODE_READ);
2264
2265     checked_free(asset_toc_filename);
2266
2267     success = (file != NULL);
2268
2269     if (success)
2270       SDL_RWclose(file);
2271   }
2272 #endif
2273
2274   return success;
2275 }
2276
2277 boolean fileExists(char *filename)
2278 {
2279   if (filename == NULL)
2280     return FALSE;
2281
2282   boolean success = (access(filename, F_OK) == 0);
2283
2284 #if defined(PLATFORM_ANDROID)
2285   if (!success)
2286   {
2287     // this might be an asset file; check by trying to open it
2288     SDL_RWops *file = SDL_RWFromFile(filename, MODE_READ);
2289
2290     success = (file != NULL);
2291
2292     if (success)
2293       SDL_RWclose(file);
2294   }
2295 #endif
2296
2297   return success;
2298 }
2299
2300 boolean fileHasPrefix(char *basename, char *prefix)
2301 {
2302   static char *basename_lower = NULL;
2303   int basename_length, prefix_length;
2304
2305   checked_free(basename_lower);
2306
2307   if (basename == NULL || prefix == NULL)
2308     return FALSE;
2309
2310   basename_lower = getStringToLower(basename);
2311   basename_length = strlen(basename_lower);
2312   prefix_length = strlen(prefix);
2313
2314   if (basename_length > prefix_length + 1 &&
2315       basename_lower[prefix_length] == '.' &&
2316       strncmp(basename_lower, prefix, prefix_length) == 0)
2317     return TRUE;
2318
2319   return FALSE;
2320 }
2321
2322 boolean fileHasSuffix(char *basename, char *suffix)
2323 {
2324   static char *basename_lower = NULL;
2325   int basename_length, suffix_length;
2326
2327   checked_free(basename_lower);
2328
2329   if (basename == NULL || suffix == NULL)
2330     return FALSE;
2331
2332   basename_lower = getStringToLower(basename);
2333   basename_length = strlen(basename_lower);
2334   suffix_length = strlen(suffix);
2335
2336   if (basename_length > suffix_length + 1 &&
2337       basename_lower[basename_length - suffix_length - 1] == '.' &&
2338       strEqual(&basename_lower[basename_length - suffix_length], suffix))
2339     return TRUE;
2340
2341   return FALSE;
2342 }
2343
2344 static boolean FileCouldBeArtwork(char *filename)
2345 {
2346   char *basename = getBaseNamePtr(filename);
2347
2348   return (!strEqual(basename, ".") &&
2349           !strEqual(basename, "..") &&
2350           !fileHasSuffix(basename, "txt") &&
2351           !fileHasSuffix(basename, "conf") &&
2352           !directoryExists(filename));
2353 }
2354
2355 boolean FileIsGraphic(char *filename)
2356 {
2357   return FileCouldBeArtwork(filename);
2358 }
2359
2360 boolean FileIsSound(char *filename)
2361 {
2362   return FileCouldBeArtwork(filename);
2363 }
2364
2365 boolean FileIsMusic(char *filename)
2366 {
2367   return FileCouldBeArtwork(filename);
2368 }
2369
2370 boolean FileIsArtworkType(char *filename, int type)
2371 {
2372   if ((type == TREE_TYPE_GRAPHICS_DIR && FileIsGraphic(filename)) ||
2373       (type == TREE_TYPE_SOUNDS_DIR && FileIsSound(filename)) ||
2374       (type == TREE_TYPE_MUSIC_DIR && FileIsMusic(filename)))
2375     return TRUE;
2376
2377   return FALSE;
2378 }
2379
2380 /* ------------------------------------------------------------------------- */
2381 /* functions for loading artwork configuration information                   */
2382 /* ------------------------------------------------------------------------- */
2383
2384 char *get_mapped_token(char *token)
2385 {
2386   /* !!! make this dynamically configurable (init.c:InitArtworkConfig) !!! */
2387   static char *map_token_prefix[][2] =
2388   {
2389     { "char_procent",           "char_percent"  },
2390     { NULL,                                     }
2391   };
2392   int i;
2393
2394   for (i = 0; map_token_prefix[i][0] != NULL; i++)
2395   {
2396     int len_token_prefix = strlen(map_token_prefix[i][0]);
2397
2398     if (strncmp(token, map_token_prefix[i][0], len_token_prefix) == 0)
2399       return getStringCat2(map_token_prefix[i][1], &token[len_token_prefix]);
2400   }
2401
2402   return NULL;
2403 }
2404
2405 /* This function checks if a string <s> of the format "string1, string2, ..."
2406    exactly contains a string <s_contained>. */
2407
2408 static boolean string_has_parameter(char *s, char *s_contained)
2409 {
2410   char *substring;
2411
2412   if (s == NULL || s_contained == NULL)
2413     return FALSE;
2414
2415   if (strlen(s_contained) > strlen(s))
2416     return FALSE;
2417
2418   if (strncmp(s, s_contained, strlen(s_contained)) == 0)
2419   {
2420     char next_char = s[strlen(s_contained)];
2421
2422     /* check if next character is delimiter or whitespace */
2423     return (next_char == ',' || next_char == '\0' ||
2424             next_char == ' ' || next_char == '\t' ? TRUE : FALSE);
2425   }
2426
2427   /* check if string contains another parameter string after a comma */
2428   substring = strchr(s, ',');
2429   if (substring == NULL)        /* string does not contain a comma */
2430     return FALSE;
2431
2432   /* advance string pointer to next character after the comma */
2433   substring++;
2434
2435   /* skip potential whitespaces after the comma */
2436   while (*substring == ' ' || *substring == '\t')
2437     substring++;
2438
2439   return string_has_parameter(substring, s_contained);
2440 }
2441
2442 int get_parameter_value(char *value_raw, char *suffix, int type)
2443 {
2444   char *value = getStringToLower(value_raw);
2445   int result = 0;       /* probably a save default value */
2446
2447   if (strEqual(suffix, ".direction"))
2448   {
2449     result = (strEqual(value, "left")  ? MV_LEFT :
2450               strEqual(value, "right") ? MV_RIGHT :
2451               strEqual(value, "up")    ? MV_UP :
2452               strEqual(value, "down")  ? MV_DOWN : MV_NONE);
2453   }
2454   else if (strEqual(suffix, ".align"))
2455   {
2456     result = (strEqual(value, "left")   ? ALIGN_LEFT :
2457               strEqual(value, "right")  ? ALIGN_RIGHT :
2458               strEqual(value, "center") ? ALIGN_CENTER :
2459               strEqual(value, "middle") ? ALIGN_CENTER : ALIGN_DEFAULT);
2460   }
2461   else if (strEqual(suffix, ".valign"))
2462   {
2463     result = (strEqual(value, "top")    ? VALIGN_TOP :
2464               strEqual(value, "bottom") ? VALIGN_BOTTOM :
2465               strEqual(value, "middle") ? VALIGN_MIDDLE :
2466               strEqual(value, "center") ? VALIGN_MIDDLE : VALIGN_DEFAULT);
2467   }
2468   else if (strEqual(suffix, ".anim_mode"))
2469   {
2470     result = (string_has_parameter(value, "none")       ? ANIM_NONE :
2471               string_has_parameter(value, "loop")       ? ANIM_LOOP :
2472               string_has_parameter(value, "linear")     ? ANIM_LINEAR :
2473               string_has_parameter(value, "pingpong")   ? ANIM_PINGPONG :
2474               string_has_parameter(value, "pingpong2")  ? ANIM_PINGPONG2 :
2475               string_has_parameter(value, "random")     ? ANIM_RANDOM :
2476               string_has_parameter(value, "ce_value")   ? ANIM_CE_VALUE :
2477               string_has_parameter(value, "ce_score")   ? ANIM_CE_SCORE :
2478               string_has_parameter(value, "ce_delay")   ? ANIM_CE_DELAY :
2479               string_has_parameter(value, "horizontal") ? ANIM_HORIZONTAL :
2480               string_has_parameter(value, "vertical")   ? ANIM_VERTICAL :
2481               string_has_parameter(value, "centered")   ? ANIM_CENTERED :
2482               ANIM_DEFAULT);
2483
2484     if (string_has_parameter(value, "reverse"))
2485       result |= ANIM_REVERSE;
2486
2487     if (string_has_parameter(value, "opaque_player"))
2488       result |= ANIM_OPAQUE_PLAYER;
2489
2490     if (string_has_parameter(value, "static_panel"))
2491       result |= ANIM_STATIC_PANEL;
2492   }
2493   else if (strEqual(suffix, ".class"))
2494   {
2495     result = get_hash_from_key(value);
2496   }
2497   else if (strEqual(suffix, ".style"))
2498   {
2499     result = STYLE_DEFAULT;
2500
2501     if (string_has_parameter(value, "accurate_borders"))
2502       result |= STYLE_ACCURATE_BORDERS;
2503
2504     if (string_has_parameter(value, "inner_corners"))
2505       result |= STYLE_INNER_CORNERS;
2506   }
2507   else if (strEqual(suffix, ".fade_mode"))
2508   {
2509     result = (string_has_parameter(value, "none")       ? FADE_MODE_NONE :
2510               string_has_parameter(value, "fade")       ? FADE_MODE_FADE :
2511               string_has_parameter(value, "crossfade")  ? FADE_MODE_CROSSFADE :
2512               string_has_parameter(value, "melt")       ? FADE_MODE_MELT :
2513               FADE_MODE_DEFAULT);
2514   }
2515   else if (strPrefix(suffix, ".font"))          /* (may also be ".font_xyz") */
2516   {
2517     result = gfx.get_font_from_token_function(value);
2518   }
2519   else          /* generic parameter of type integer or boolean */
2520   {
2521     result = (strEqual(value, ARG_UNDEFINED) ? ARG_UNDEFINED_VALUE :
2522               type == TYPE_INTEGER ? get_integer_from_string(value) :
2523               type == TYPE_BOOLEAN ? get_boolean_from_string(value) :
2524               ARG_UNDEFINED_VALUE);
2525   }
2526
2527   free(value);
2528
2529   return result;
2530 }
2531
2532 struct ScreenModeInfo *get_screen_mode_from_string(char *screen_mode_string)
2533 {
2534   static struct ScreenModeInfo screen_mode;
2535   char *screen_mode_string_x = strchr(screen_mode_string, 'x');
2536   char *screen_mode_string_copy;
2537   char *screen_mode_string_pos_w;
2538   char *screen_mode_string_pos_h;
2539
2540   if (screen_mode_string_x == NULL)     /* invalid screen mode format */
2541     return NULL;
2542
2543   screen_mode_string_copy = getStringCopy(screen_mode_string);
2544
2545   screen_mode_string_pos_w = screen_mode_string_copy;
2546   screen_mode_string_pos_h = strchr(screen_mode_string_copy, 'x');
2547   *screen_mode_string_pos_h++ = '\0';
2548
2549   screen_mode.width  = atoi(screen_mode_string_pos_w);
2550   screen_mode.height = atoi(screen_mode_string_pos_h);
2551
2552   return &screen_mode;
2553 }
2554
2555 void get_aspect_ratio_from_screen_mode(struct ScreenModeInfo *screen_mode,
2556                                        int *x, int *y)
2557 {
2558   float aspect_ratio = (float)screen_mode->width / (float)screen_mode->height;
2559   float aspect_ratio_new;
2560   int i = 1;
2561
2562   do
2563   {
2564     *x = i * aspect_ratio + 0.000001;
2565     *y = i;
2566
2567     aspect_ratio_new = (float)*x / (float)*y;
2568
2569     i++;
2570   }
2571   while (aspect_ratio_new != aspect_ratio && *y < screen_mode->height);
2572 }
2573
2574 static void FreeCustomArtworkList(struct ArtworkListInfo *,
2575                                   struct ListNodeInfo ***, int *);
2576
2577 struct FileInfo *getFileListFromConfigList(struct ConfigInfo *config_list,
2578                                            struct ConfigTypeInfo *suffix_list,
2579                                            char **ignore_tokens,
2580                                            int num_file_list_entries)
2581 {
2582   struct FileInfo *file_list;
2583   int num_file_list_entries_found = 0;
2584   int num_suffix_list_entries = 0;
2585   int list_pos;
2586   int i, j;
2587
2588   file_list = checked_calloc(num_file_list_entries * sizeof(struct FileInfo));
2589
2590   for (i = 0; suffix_list[i].token != NULL; i++)
2591     num_suffix_list_entries++;
2592
2593   /* always start with reliable default values */
2594   for (i = 0; i < num_file_list_entries; i++)
2595   {
2596     file_list[i].token = NULL;
2597
2598     file_list[i].default_filename = NULL;
2599     file_list[i].filename = NULL;
2600
2601     if (num_suffix_list_entries > 0)
2602     {
2603       int parameter_array_size = num_suffix_list_entries * sizeof(char *);
2604
2605       file_list[i].default_parameter = checked_calloc(parameter_array_size);
2606       file_list[i].parameter = checked_calloc(parameter_array_size);
2607
2608       for (j = 0; j < num_suffix_list_entries; j++)
2609       {
2610         setString(&file_list[i].default_parameter[j], suffix_list[j].value);
2611         setString(&file_list[i].parameter[j], suffix_list[j].value);
2612       }
2613
2614       file_list[i].redefined = FALSE;
2615       file_list[i].fallback_to_default = FALSE;
2616       file_list[i].default_is_cloned = FALSE;
2617     }
2618   }
2619
2620   list_pos = 0;
2621
2622   for (i = 0; config_list[i].token != NULL; i++)
2623   {
2624     int len_config_token = strlen(config_list[i].token);
2625     boolean is_file_entry = TRUE;
2626
2627     for (j = 0; suffix_list[j].token != NULL; j++)
2628     {
2629       int len_suffix = strlen(suffix_list[j].token);
2630
2631       if (len_suffix < len_config_token &&
2632           strEqual(&config_list[i].token[len_config_token - len_suffix],
2633                    suffix_list[j].token))
2634       {
2635         setString(&file_list[list_pos].default_parameter[j],
2636                   config_list[i].value);
2637
2638         is_file_entry = FALSE;
2639
2640         break;
2641       }
2642     }
2643
2644     /* the following tokens are no file definitions, but other config tokens */
2645     for (j = 0; ignore_tokens[j] != NULL; j++)
2646       if (strEqual(config_list[i].token, ignore_tokens[j]))
2647         is_file_entry = FALSE;
2648
2649     if (is_file_entry)
2650     {
2651       if (i > 0)
2652         list_pos++;
2653
2654       if (list_pos >= num_file_list_entries)
2655         break;
2656
2657       file_list[list_pos].token = config_list[i].token;
2658       file_list[list_pos].default_filename = config_list[i].value;
2659     }
2660
2661     if (strSuffix(config_list[i].token, ".clone_from"))
2662       file_list[list_pos].default_is_cloned = TRUE;
2663   }
2664
2665   num_file_list_entries_found = list_pos + 1;
2666   if (num_file_list_entries_found != num_file_list_entries)
2667   {
2668     Error(ERR_INFO_LINE, "-");
2669     Error(ERR_INFO, "inconsistant config list information:");
2670     Error(ERR_INFO, "- should be:   %d (according to 'src/conf_xxx.h')",
2671           num_file_list_entries);
2672     Error(ERR_INFO, "- found to be: %d (according to 'src/conf_xxx.c')",
2673           num_file_list_entries_found);
2674     Error(ERR_EXIT,   "please fix");
2675   }
2676
2677   return file_list;
2678 }
2679
2680 static boolean token_suffix_match(char *token, char *suffix, int start_pos)
2681 {
2682   int len_token = strlen(token);
2683   int len_suffix = strlen(suffix);
2684
2685   if (start_pos < 0)    /* compare suffix from end of string */
2686     start_pos += len_token;
2687
2688   if (start_pos < 0 || start_pos + len_suffix > len_token)
2689     return FALSE;
2690
2691   if (strncmp(&token[start_pos], suffix, len_suffix) != 0)
2692     return FALSE;
2693
2694   if (token[start_pos + len_suffix] == '\0')
2695     return TRUE;
2696
2697   if (token[start_pos + len_suffix] == '.')
2698     return TRUE;
2699
2700   return FALSE;
2701 }
2702
2703 #define KNOWN_TOKEN_VALUE       "[KNOWN_TOKEN_VALUE]"
2704
2705 static void read_token_parameters(SetupFileHash *setup_file_hash,
2706                                   struct ConfigTypeInfo *suffix_list,
2707                                   struct FileInfo *file_list_entry)
2708 {
2709   /* check for config token that is the base token without any suffixes */
2710   char *filename = getHashEntry(setup_file_hash, file_list_entry->token);
2711   char *known_token_value = KNOWN_TOKEN_VALUE;
2712   int i;
2713
2714   if (filename != NULL)
2715   {
2716     setString(&file_list_entry->filename, filename);
2717
2718     /* when file definition found, set all parameters to default values */
2719     for (i = 0; suffix_list[i].token != NULL; i++)
2720       setString(&file_list_entry->parameter[i], suffix_list[i].value);
2721
2722     file_list_entry->redefined = TRUE;
2723
2724     /* mark config file token as well known from default config */
2725     setHashEntry(setup_file_hash, file_list_entry->token, known_token_value);
2726   }
2727
2728   /* check for config tokens that can be build by base token and suffixes */
2729   for (i = 0; suffix_list[i].token != NULL; i++)
2730   {
2731     char *token = getStringCat2(file_list_entry->token, suffix_list[i].token);
2732     char *value = getHashEntry(setup_file_hash, token);
2733
2734     if (value != NULL)
2735     {
2736       setString(&file_list_entry->parameter[i], value);
2737
2738       /* mark config file token as well known from default config */
2739       setHashEntry(setup_file_hash, token, known_token_value);
2740     }
2741
2742     free(token);
2743   }
2744 }
2745
2746 static void add_dynamic_file_list_entry(struct FileInfo **list,
2747                                         int *num_list_entries,
2748                                         SetupFileHash *extra_file_hash,
2749                                         struct ConfigTypeInfo *suffix_list,
2750                                         int num_suffix_list_entries,
2751                                         char *token)
2752 {
2753   struct FileInfo *new_list_entry;
2754   int parameter_array_size = num_suffix_list_entries * sizeof(char *);
2755
2756   (*num_list_entries)++;
2757   *list = checked_realloc(*list, *num_list_entries * sizeof(struct FileInfo));
2758   new_list_entry = &(*list)[*num_list_entries - 1];
2759
2760   new_list_entry->token = getStringCopy(token);
2761   new_list_entry->default_filename = NULL;
2762   new_list_entry->filename = NULL;
2763   new_list_entry->parameter = checked_calloc(parameter_array_size);
2764
2765   new_list_entry->redefined = FALSE;
2766   new_list_entry->fallback_to_default = FALSE;
2767   new_list_entry->default_is_cloned = FALSE;
2768
2769   read_token_parameters(extra_file_hash, suffix_list, new_list_entry);
2770 }
2771
2772 static void add_property_mapping(struct PropertyMapping **list,
2773                                  int *num_list_entries,
2774                                  int base_index, int ext1_index,
2775                                  int ext2_index, int ext3_index,
2776                                  int artwork_index)
2777 {
2778   struct PropertyMapping *new_list_entry;
2779
2780   (*num_list_entries)++;
2781   *list = checked_realloc(*list,
2782                           *num_list_entries * sizeof(struct PropertyMapping));
2783   new_list_entry = &(*list)[*num_list_entries - 1];
2784
2785   new_list_entry->base_index = base_index;
2786   new_list_entry->ext1_index = ext1_index;
2787   new_list_entry->ext2_index = ext2_index;
2788   new_list_entry->ext3_index = ext3_index;
2789
2790   new_list_entry->artwork_index = artwork_index;
2791 }
2792
2793 static void LoadArtworkConfigFromFilename(struct ArtworkListInfo *artwork_info,
2794                                           char *filename)
2795 {
2796   struct FileInfo *file_list = artwork_info->file_list;
2797   struct ConfigTypeInfo *suffix_list = artwork_info->suffix_list;
2798   char **base_prefixes = artwork_info->base_prefixes;
2799   char **ext1_suffixes = artwork_info->ext1_suffixes;
2800   char **ext2_suffixes = artwork_info->ext2_suffixes;
2801   char **ext3_suffixes = artwork_info->ext3_suffixes;
2802   char **ignore_tokens = artwork_info->ignore_tokens;
2803   int num_file_list_entries = artwork_info->num_file_list_entries;
2804   int num_suffix_list_entries = artwork_info->num_suffix_list_entries;
2805   int num_base_prefixes = artwork_info->num_base_prefixes;
2806   int num_ext1_suffixes = artwork_info->num_ext1_suffixes;
2807   int num_ext2_suffixes = artwork_info->num_ext2_suffixes;
2808   int num_ext3_suffixes = artwork_info->num_ext3_suffixes;
2809   int num_ignore_tokens = artwork_info->num_ignore_tokens;
2810   SetupFileHash *setup_file_hash, *valid_file_hash;
2811   SetupFileHash *extra_file_hash, *empty_file_hash;
2812   char *known_token_value = KNOWN_TOKEN_VALUE;
2813   int i, j, k, l;
2814
2815   if (filename == NULL)
2816     return;
2817
2818   if ((setup_file_hash = loadSetupFileHash(filename)) == NULL)
2819     return;
2820
2821   /* separate valid (defined) from empty (undefined) config token values */
2822   valid_file_hash = newSetupFileHash();
2823   empty_file_hash = newSetupFileHash();
2824   BEGIN_HASH_ITERATION(setup_file_hash, itr)
2825   {
2826     char *value = HASH_ITERATION_VALUE(itr);
2827
2828     setHashEntry(*value ? valid_file_hash : empty_file_hash,
2829                  HASH_ITERATION_TOKEN(itr), value);
2830   }
2831   END_HASH_ITERATION(setup_file_hash, itr)
2832
2833   /* at this point, we do not need the setup file hash anymore -- free it */
2834   freeSetupFileHash(setup_file_hash);
2835
2836   /* map deprecated to current tokens (using prefix match and replace) */
2837   BEGIN_HASH_ITERATION(valid_file_hash, itr)
2838   {
2839     char *token = HASH_ITERATION_TOKEN(itr);
2840     char *mapped_token = get_mapped_token(token);
2841
2842     if (mapped_token != NULL)
2843     {
2844       char *value = HASH_ITERATION_VALUE(itr);
2845
2846       /* add mapped token */
2847       setHashEntry(valid_file_hash, mapped_token, value);
2848
2849       /* ignore old token (by setting it to "known" keyword) */
2850       setHashEntry(valid_file_hash, token, known_token_value);
2851
2852       free(mapped_token);
2853     }
2854   }
2855   END_HASH_ITERATION(valid_file_hash, itr)
2856
2857   /* read parameters for all known config file tokens */
2858   for (i = 0; i < num_file_list_entries; i++)
2859     read_token_parameters(valid_file_hash, suffix_list, &file_list[i]);
2860
2861   /* set all tokens that can be ignored here to "known" keyword */
2862   for (i = 0; i < num_ignore_tokens; i++)
2863     setHashEntry(valid_file_hash, ignore_tokens[i], known_token_value);
2864
2865   /* copy all unknown config file tokens to extra config hash */
2866   extra_file_hash = newSetupFileHash();
2867   BEGIN_HASH_ITERATION(valid_file_hash, itr)
2868   {
2869     char *value = HASH_ITERATION_VALUE(itr);
2870
2871     if (!strEqual(value, known_token_value))
2872       setHashEntry(extra_file_hash, HASH_ITERATION_TOKEN(itr), value);
2873   }
2874   END_HASH_ITERATION(valid_file_hash, itr)
2875
2876   /* at this point, we do not need the valid file hash anymore -- free it */
2877   freeSetupFileHash(valid_file_hash);
2878
2879   /* now try to determine valid, dynamically defined config tokens */
2880
2881   BEGIN_HASH_ITERATION(extra_file_hash, itr)
2882   {
2883     struct FileInfo **dynamic_file_list =
2884       &artwork_info->dynamic_file_list;
2885     int *num_dynamic_file_list_entries =
2886       &artwork_info->num_dynamic_file_list_entries;
2887     struct PropertyMapping **property_mapping =
2888       &artwork_info->property_mapping;
2889     int *num_property_mapping_entries =
2890       &artwork_info->num_property_mapping_entries;
2891     int current_summarized_file_list_entry =
2892       artwork_info->num_file_list_entries +
2893       artwork_info->num_dynamic_file_list_entries;
2894     char *token = HASH_ITERATION_TOKEN(itr);
2895     int len_token = strlen(token);
2896     int start_pos;
2897     boolean base_prefix_found = FALSE;
2898     boolean parameter_suffix_found = FALSE;
2899
2900     /* skip all parameter definitions (handled by read_token_parameters()) */
2901     for (i = 0; i < num_suffix_list_entries && !parameter_suffix_found; i++)
2902     {
2903       int len_suffix = strlen(suffix_list[i].token);
2904
2905       if (token_suffix_match(token, suffix_list[i].token, -len_suffix))
2906         parameter_suffix_found = TRUE;
2907     }
2908
2909     if (parameter_suffix_found)
2910       continue;
2911
2912     /* ---------- step 0: search for matching base prefix ---------- */
2913
2914     start_pos = 0;
2915     for (i = 0; i < num_base_prefixes && !base_prefix_found; i++)
2916     {
2917       char *base_prefix = base_prefixes[i];
2918       int len_base_prefix = strlen(base_prefix);
2919       boolean ext1_suffix_found = FALSE;
2920       boolean ext2_suffix_found = FALSE;
2921       boolean ext3_suffix_found = FALSE;
2922       boolean exact_match = FALSE;
2923       int base_index = -1;
2924       int ext1_index = -1;
2925       int ext2_index = -1;
2926       int ext3_index = -1;
2927
2928       base_prefix_found = token_suffix_match(token, base_prefix, start_pos);
2929
2930       if (!base_prefix_found)
2931         continue;
2932
2933       base_index = i;
2934
2935       if (start_pos + len_base_prefix == len_token)     /* exact match */
2936       {
2937         exact_match = TRUE;
2938
2939         add_dynamic_file_list_entry(dynamic_file_list,
2940                                     num_dynamic_file_list_entries,
2941                                     extra_file_hash,
2942                                     suffix_list,
2943                                     num_suffix_list_entries,
2944                                     token);
2945         add_property_mapping(property_mapping,
2946                              num_property_mapping_entries,
2947                              base_index, -1, -1, -1,
2948                              current_summarized_file_list_entry);
2949         continue;
2950       }
2951
2952       /* ---------- step 1: search for matching first suffix ---------- */
2953
2954       start_pos += len_base_prefix;
2955       for (j = 0; j < num_ext1_suffixes && !ext1_suffix_found; j++)
2956       {
2957         char *ext1_suffix = ext1_suffixes[j];
2958         int len_ext1_suffix = strlen(ext1_suffix);
2959
2960         ext1_suffix_found = token_suffix_match(token, ext1_suffix, start_pos);
2961
2962         if (!ext1_suffix_found)
2963           continue;
2964
2965         ext1_index = j;
2966
2967         if (start_pos + len_ext1_suffix == len_token)   /* exact match */
2968         {
2969           exact_match = TRUE;
2970
2971           add_dynamic_file_list_entry(dynamic_file_list,
2972                                       num_dynamic_file_list_entries,
2973                                       extra_file_hash,
2974                                       suffix_list,
2975                                       num_suffix_list_entries,
2976                                       token);
2977           add_property_mapping(property_mapping,
2978                                num_property_mapping_entries,
2979                                base_index, ext1_index, -1, -1,
2980                                current_summarized_file_list_entry);
2981           continue;
2982         }
2983
2984         start_pos += len_ext1_suffix;
2985       }
2986
2987       if (exact_match)
2988         break;
2989
2990       /* ---------- step 2: search for matching second suffix ---------- */
2991
2992       for (k = 0; k < num_ext2_suffixes && !ext2_suffix_found; k++)
2993       {
2994         char *ext2_suffix = ext2_suffixes[k];
2995         int len_ext2_suffix = strlen(ext2_suffix);
2996
2997         ext2_suffix_found = token_suffix_match(token, ext2_suffix, start_pos);
2998
2999         if (!ext2_suffix_found)
3000           continue;
3001
3002         ext2_index = k;
3003
3004         if (start_pos + len_ext2_suffix == len_token)   /* exact match */
3005         {
3006           exact_match = TRUE;
3007
3008           add_dynamic_file_list_entry(dynamic_file_list,
3009                                       num_dynamic_file_list_entries,
3010                                       extra_file_hash,
3011                                       suffix_list,
3012                                       num_suffix_list_entries,
3013                                       token);
3014           add_property_mapping(property_mapping,
3015                                num_property_mapping_entries,
3016                                base_index, ext1_index, ext2_index, -1,
3017                                current_summarized_file_list_entry);
3018           continue;
3019         }
3020
3021         start_pos += len_ext2_suffix;
3022       }
3023
3024       if (exact_match)
3025         break;
3026
3027       /* ---------- step 3: search for matching third suffix ---------- */
3028
3029       for (l = 0; l < num_ext3_suffixes && !ext3_suffix_found; l++)
3030       {
3031         char *ext3_suffix = ext3_suffixes[l];
3032         int len_ext3_suffix = strlen(ext3_suffix);
3033
3034         ext3_suffix_found = token_suffix_match(token, ext3_suffix, start_pos);
3035
3036         if (!ext3_suffix_found)
3037           continue;
3038
3039         ext3_index = l;
3040
3041         if (start_pos + len_ext3_suffix == len_token) /* exact match */
3042         {
3043           exact_match = TRUE;
3044
3045           add_dynamic_file_list_entry(dynamic_file_list,
3046                                       num_dynamic_file_list_entries,
3047                                       extra_file_hash,
3048                                       suffix_list,
3049                                       num_suffix_list_entries,
3050                                       token);
3051           add_property_mapping(property_mapping,
3052                                num_property_mapping_entries,
3053                                base_index, ext1_index, ext2_index, ext3_index,
3054                                current_summarized_file_list_entry);
3055           continue;
3056         }
3057       }
3058     }
3059   }
3060   END_HASH_ITERATION(extra_file_hash, itr)
3061
3062   if (artwork_info->num_dynamic_file_list_entries > 0)
3063   {
3064     artwork_info->dynamic_artwork_list =
3065       checked_calloc(artwork_info->num_dynamic_file_list_entries *
3066                      artwork_info->sizeof_artwork_list_entry);
3067   }
3068
3069   if (options.verbose && IS_PARENT_PROCESS())
3070   {
3071     SetupFileList *setup_file_list, *list;
3072     boolean dynamic_tokens_found = FALSE;
3073     boolean unknown_tokens_found = FALSE;
3074     boolean undefined_values_found = (hashtable_count(empty_file_hash) != 0);
3075
3076     if ((setup_file_list = loadSetupFileList(filename)) == NULL)
3077       Error(ERR_EXIT, "loadSetupFileHash works, but loadSetupFileList fails");
3078
3079     BEGIN_HASH_ITERATION(extra_file_hash, itr)
3080     {
3081       if (strEqual(HASH_ITERATION_VALUE(itr), known_token_value))
3082         dynamic_tokens_found = TRUE;
3083       else
3084         unknown_tokens_found = TRUE;
3085     }
3086     END_HASH_ITERATION(extra_file_hash, itr)
3087
3088     if (options.debug && dynamic_tokens_found)
3089     {
3090       Error(ERR_INFO_LINE, "-");
3091       Error(ERR_INFO, "dynamic token(s) found in config file:");
3092       Error(ERR_INFO, "- config file: '%s'", filename);
3093
3094       for (list = setup_file_list; list != NULL; list = list->next)
3095       {
3096         char *value = getHashEntry(extra_file_hash, list->token);
3097
3098         if (value != NULL && strEqual(value, known_token_value))
3099           Error(ERR_INFO, "- dynamic token: '%s'", list->token);
3100       }
3101
3102       Error(ERR_INFO_LINE, "-");
3103     }
3104
3105     if (unknown_tokens_found)
3106     {
3107       Error(ERR_INFO_LINE, "-");
3108       Error(ERR_INFO, "warning: unknown token(s) found in config file:");
3109       Error(ERR_INFO, "- config file: '%s'", filename);
3110
3111       for (list = setup_file_list; list != NULL; list = list->next)
3112       {
3113         char *value = getHashEntry(extra_file_hash, list->token);
3114
3115         if (value != NULL && !strEqual(value, known_token_value))
3116           Error(ERR_INFO, "- dynamic token: '%s'", list->token);
3117       }
3118
3119       Error(ERR_INFO_LINE, "-");
3120     }
3121
3122     if (undefined_values_found)
3123     {
3124       Error(ERR_INFO_LINE, "-");
3125       Error(ERR_INFO, "warning: undefined values found in config file:");
3126       Error(ERR_INFO, "- config file: '%s'", filename);
3127
3128       for (list = setup_file_list; list != NULL; list = list->next)
3129       {
3130         char *value = getHashEntry(empty_file_hash, list->token);
3131
3132         if (value != NULL)
3133           Error(ERR_INFO, "- undefined value for token: '%s'", list->token);
3134       }
3135
3136       Error(ERR_INFO_LINE, "-");
3137     }
3138
3139     freeSetupFileList(setup_file_list);
3140   }
3141
3142   freeSetupFileHash(extra_file_hash);
3143   freeSetupFileHash(empty_file_hash);
3144 }
3145
3146 void LoadArtworkConfig(struct ArtworkListInfo *artwork_info)
3147 {
3148   struct FileInfo *file_list = artwork_info->file_list;
3149   int num_file_list_entries = artwork_info->num_file_list_entries;
3150   int num_suffix_list_entries = artwork_info->num_suffix_list_entries;
3151   char *filename_base = UNDEFINED_FILENAME, *filename_local;
3152   int i, j;
3153
3154   DrawInitText("Loading artwork config", 120, FC_GREEN);
3155   DrawInitText(ARTWORKINFO_FILENAME(artwork_info->type), 150, FC_YELLOW);
3156
3157   /* always start with reliable default values */
3158   for (i = 0; i < num_file_list_entries; i++)
3159   {
3160     setString(&file_list[i].filename, file_list[i].default_filename);
3161
3162     for (j = 0; j < num_suffix_list_entries; j++)
3163       setString(&file_list[i].parameter[j], file_list[i].default_parameter[j]);
3164
3165     file_list[i].redefined = FALSE;
3166     file_list[i].fallback_to_default = FALSE;
3167   }
3168
3169   /* free previous dynamic artwork file array */
3170   if (artwork_info->dynamic_file_list != NULL)
3171   {
3172     for (i = 0; i < artwork_info->num_dynamic_file_list_entries; i++)
3173     {
3174       free(artwork_info->dynamic_file_list[i].token);
3175       free(artwork_info->dynamic_file_list[i].filename);
3176       free(artwork_info->dynamic_file_list[i].parameter);
3177     }
3178
3179     free(artwork_info->dynamic_file_list);
3180     artwork_info->dynamic_file_list = NULL;
3181
3182     FreeCustomArtworkList(artwork_info, &artwork_info->dynamic_artwork_list,
3183                           &artwork_info->num_dynamic_file_list_entries);
3184   }
3185
3186   /* free previous property mapping */
3187   if (artwork_info->property_mapping != NULL)
3188   {
3189     free(artwork_info->property_mapping);
3190
3191     artwork_info->property_mapping = NULL;
3192     artwork_info->num_property_mapping_entries = 0;
3193   }
3194
3195   if (!GFX_OVERRIDE_ARTWORK(artwork_info->type))
3196   {
3197     /* first look for special artwork configured in level series config */
3198     filename_base = getCustomArtworkLevelConfigFilename(artwork_info->type);
3199
3200     if (fileExists(filename_base))
3201       LoadArtworkConfigFromFilename(artwork_info, filename_base);
3202   }
3203
3204   filename_local = getCustomArtworkConfigFilename(artwork_info->type);
3205
3206   if (filename_local != NULL && !strEqual(filename_base, filename_local))
3207     LoadArtworkConfigFromFilename(artwork_info, filename_local);
3208 }
3209
3210 static void deleteArtworkListEntry(struct ArtworkListInfo *artwork_info,
3211                                    struct ListNodeInfo **listnode)
3212 {
3213   if (*listnode)
3214   {
3215     char *filename = (*listnode)->source_filename;
3216
3217     if (--(*listnode)->num_references <= 0)
3218       deleteNodeFromList(&artwork_info->content_list, filename,
3219                          artwork_info->free_artwork);
3220
3221     *listnode = NULL;
3222   }
3223 }
3224
3225 static void replaceArtworkListEntry(struct ArtworkListInfo *artwork_info,
3226                                     struct ListNodeInfo **listnode,
3227                                     struct FileInfo *file_list_entry)
3228 {
3229   char *init_text[] =
3230   {
3231     "Loading graphics",
3232     "Loading sounds",
3233     "Loading music"
3234   };
3235
3236   ListNode *node;
3237   char *basename = file_list_entry->filename;
3238   char *filename = getCustomArtworkFilename(basename, artwork_info->type);
3239
3240   if (filename == NULL)
3241   {
3242     Error(ERR_WARN, "cannot find artwork file '%s'", basename);
3243
3244     basename = file_list_entry->default_filename;
3245
3246     /* fail for cloned default artwork that has no default filename defined */
3247     if (file_list_entry->default_is_cloned &&
3248         strEqual(basename, UNDEFINED_FILENAME))
3249     {
3250       int error_mode = ERR_WARN;
3251
3252       /* we can get away without sounds and music, but not without graphics */
3253       if (*listnode == NULL && artwork_info->type == ARTWORK_TYPE_GRAPHICS)
3254         error_mode = ERR_EXIT;
3255
3256       Error(error_mode, "token '%s' was cloned and has no default filename",
3257             file_list_entry->token);
3258
3259       return;
3260     }
3261
3262     /* dynamic artwork has no default filename / skip empty default artwork */
3263     if (basename == NULL || strEqual(basename, UNDEFINED_FILENAME))
3264       return;
3265
3266     file_list_entry->fallback_to_default = TRUE;
3267
3268     Error(ERR_WARN, "trying default artwork file '%s'", basename);
3269
3270     filename = getCustomArtworkFilename(basename, artwork_info->type);
3271
3272     if (filename == NULL)
3273     {
3274       int error_mode = ERR_WARN;
3275
3276       /* we can get away without sounds and music, but not without graphics */
3277       if (*listnode == NULL && artwork_info->type == ARTWORK_TYPE_GRAPHICS)
3278         error_mode = ERR_EXIT;
3279
3280       Error(error_mode, "cannot find default artwork file '%s'", basename);
3281
3282       return;
3283     }
3284   }
3285
3286   /* check if the old and the new artwork file are the same */
3287   if (*listnode && strEqual((*listnode)->source_filename, filename))
3288   {
3289     /* The old and new artwork are the same (have the same filename and path).
3290        This usually means that this artwork does not exist in this artwork set
3291        and a fallback to the existing artwork is done. */
3292
3293     return;
3294   }
3295
3296   /* delete existing artwork file entry */
3297   deleteArtworkListEntry(artwork_info, listnode);
3298
3299   /* check if the new artwork file already exists in the list of artwork */
3300   if ((node = getNodeFromKey(artwork_info->content_list, filename)) != NULL)
3301   {
3302       *listnode = (struct ListNodeInfo *)node->content;
3303       (*listnode)->num_references++;
3304
3305       return;
3306   }
3307
3308   DrawInitText(init_text[artwork_info->type], 120, FC_GREEN);
3309   DrawInitText(basename, 150, FC_YELLOW);
3310
3311   if ((*listnode = artwork_info->load_artwork(filename)) != NULL)
3312   {
3313     /* add new artwork file entry to the list of artwork files */
3314     (*listnode)->num_references = 1;
3315     addNodeToList(&artwork_info->content_list, (*listnode)->source_filename,
3316                   *listnode);
3317   }
3318   else
3319   {
3320     int error_mode = ERR_WARN;
3321
3322     /* we can get away without sounds and music, but not without graphics */
3323     if (artwork_info->type == ARTWORK_TYPE_GRAPHICS)
3324       error_mode = ERR_EXIT;
3325
3326     Error(error_mode, "cannot load artwork file '%s'", basename);
3327
3328     return;
3329   }
3330 }
3331
3332 static void LoadCustomArtwork(struct ArtworkListInfo *artwork_info,
3333                               struct ListNodeInfo **listnode,
3334                               struct FileInfo *file_list_entry)
3335 {
3336   if (strEqual(file_list_entry->filename, UNDEFINED_FILENAME))
3337   {
3338     deleteArtworkListEntry(artwork_info, listnode);
3339
3340     return;
3341   }
3342
3343   replaceArtworkListEntry(artwork_info, listnode, file_list_entry);
3344 }
3345
3346 void ReloadCustomArtworkList(struct ArtworkListInfo *artwork_info)
3347 {
3348   struct FileInfo *file_list = artwork_info->file_list;
3349   struct FileInfo *dynamic_file_list = artwork_info->dynamic_file_list;
3350   int num_file_list_entries = artwork_info->num_file_list_entries;
3351   int num_dynamic_file_list_entries =
3352     artwork_info->num_dynamic_file_list_entries;
3353   int i;
3354
3355   print_timestamp_init("ReloadCustomArtworkList");
3356
3357   for (i = 0; i < num_file_list_entries; i++)
3358     LoadCustomArtwork(artwork_info, &artwork_info->artwork_list[i],
3359                       &file_list[i]);
3360
3361   for (i = 0; i < num_dynamic_file_list_entries; i++)
3362     LoadCustomArtwork(artwork_info, &artwork_info->dynamic_artwork_list[i],
3363                       &dynamic_file_list[i]);
3364
3365   print_timestamp_done("ReloadCustomArtworkList");
3366
3367 #if 0
3368   dumpList(artwork_info->content_list);
3369 #endif
3370 }
3371
3372 static void FreeCustomArtworkList(struct ArtworkListInfo *artwork_info,
3373                                   struct ListNodeInfo ***list,
3374                                   int *num_list_entries)
3375 {
3376   int i;
3377
3378   if (*list == NULL)
3379     return;
3380
3381   for (i = 0; i < *num_list_entries; i++)
3382     deleteArtworkListEntry(artwork_info, &(*list)[i]);
3383   free(*list);
3384
3385   *list = NULL;
3386   *num_list_entries = 0;
3387 }
3388
3389 void FreeCustomArtworkLists(struct ArtworkListInfo *artwork_info)
3390 {
3391   if (artwork_info == NULL)
3392     return;
3393
3394   FreeCustomArtworkList(artwork_info, &artwork_info->artwork_list,
3395                         &artwork_info->num_file_list_entries);
3396
3397   FreeCustomArtworkList(artwork_info, &artwork_info->dynamic_artwork_list,
3398                         &artwork_info->num_dynamic_file_list_entries);
3399 }
3400
3401
3402 /* ------------------------------------------------------------------------- */
3403 /* functions only needed for non-Unix (non-command-line) systems             */
3404 /* (MS-DOS only; SDL/Windows creates files "stdout.txt" and "stderr.txt")    */
3405 /* (now also added for Windows, to create files in user data directory)      */
3406 /* ------------------------------------------------------------------------- */
3407
3408 char *getErrorFilename(char *basename)
3409 {
3410   return getPath2(getUserGameDataDir(), basename);
3411 }
3412
3413 void openErrorFile()
3414 {
3415   InitUserDataDirectory();
3416
3417   if ((program.error_file = fopen(program.error_filename, MODE_WRITE)) == NULL)
3418   {
3419     program.error_file = stderr;
3420
3421     Error(ERR_WARN, "cannot open file '%s' for writing: %s",
3422           program.error_filename, strerror(errno));
3423   }
3424
3425   /* error output should be unbuffered so it is not truncated in a crash */
3426   setbuf(program.error_file, NULL);
3427 }
3428
3429 void closeErrorFile()
3430 {
3431   if (program.error_file != stderr)     /* do not close stream 'stderr' */
3432     fclose(program.error_file);
3433 }
3434
3435 void dumpErrorFile()
3436 {
3437   FILE *error_file = fopen(program.error_filename, MODE_READ);
3438
3439   if (error_file != NULL)
3440   {
3441     while (!feof(error_file))
3442       fputc(fgetc(error_file), stderr);
3443
3444     fclose(error_file);
3445   }
3446 }
3447
3448 void NotifyUserAboutErrorFile()
3449 {
3450 #if defined(PLATFORM_WIN32)
3451   char *title_text = getStringCat2(program.program_title, " Error Message");
3452   char *error_text = getStringCat2("The program was aborted due to an error; "
3453                                    "for details, see the following error file:"
3454                                    STRING_NEWLINE, program.error_filename);
3455
3456   MessageBox(NULL, error_text, title_text, MB_OK);
3457 #endif
3458 }
3459
3460
3461 /* ------------------------------------------------------------------------- */
3462 /* the following is only for debugging purpose and normally not used         */
3463 /* ------------------------------------------------------------------------- */
3464
3465 #if DEBUG
3466
3467 #define DEBUG_PRINT_INIT_TIMESTAMPS             FALSE
3468 #define DEBUG_PRINT_INIT_TIMESTAMPS_DEPTH       10
3469
3470 #define DEBUG_NUM_TIMESTAMPS                    10
3471 #define DEBUG_TIME_IN_MICROSECONDS              0
3472
3473 #if DEBUG_TIME_IN_MICROSECONDS
3474 static double Counter_Microseconds()
3475 {
3476   static struct timeval base_time = { 0, 0 };
3477   struct timeval current_time;
3478   double counter;
3479
3480   gettimeofday(&current_time, NULL);
3481
3482   /* reset base time in case of wrap-around */
3483   if (current_time.tv_sec < base_time.tv_sec)
3484     base_time = current_time;
3485
3486   counter =
3487     ((double)(current_time.tv_sec  - base_time.tv_sec)) * 1000000 +
3488     ((double)(current_time.tv_usec - base_time.tv_usec));
3489
3490   return counter;               /* return microseconds since last init */
3491 }
3492 #endif
3493
3494 char *debug_print_timestamp_get_padding(int padding_size)
3495 {
3496   static char *padding = NULL;
3497   int max_padding_size = 100;
3498
3499   if (padding == NULL)
3500   {
3501     padding = checked_calloc(max_padding_size + 1);
3502     memset(padding, ' ', max_padding_size);
3503   }
3504
3505   return &padding[MAX(0, max_padding_size - padding_size)];
3506 }
3507
3508 void debug_print_timestamp(int counter_nr, char *message)
3509 {
3510   int indent_size = 8;
3511   int padding_size = 40;
3512   float timestamp_interval;
3513
3514   if (counter_nr < 0)
3515     Error(ERR_EXIT, "debugging: invalid negative counter");
3516   else if (counter_nr >= DEBUG_NUM_TIMESTAMPS)
3517     Error(ERR_EXIT, "debugging: increase DEBUG_NUM_TIMESTAMPS in misc.c");
3518
3519 #if DEBUG_TIME_IN_MICROSECONDS
3520   static double counter[DEBUG_NUM_TIMESTAMPS][2];
3521   char *unit = "ms";
3522
3523   counter[counter_nr][0] = Counter_Microseconds();
3524 #else
3525   static int counter[DEBUG_NUM_TIMESTAMPS][2];
3526   char *unit = "s";
3527
3528   counter[counter_nr][0] = Counter();
3529 #endif
3530
3531   timestamp_interval = counter[counter_nr][0] - counter[counter_nr][1];
3532   counter[counter_nr][1] = counter[counter_nr][0];
3533
3534   if (message)
3535     Error(ERR_DEBUG, "%s%s%s %.3f %s",
3536            debug_print_timestamp_get_padding(counter_nr * indent_size),
3537            message,
3538            debug_print_timestamp_get_padding(padding_size - strlen(message)),
3539            timestamp_interval / 1000,
3540            unit);
3541 }
3542
3543 void debug_print_parent_only(char *format, ...)
3544 {
3545   if (!IS_PARENT_PROCESS())
3546     return;
3547
3548   if (format)
3549   {
3550     va_list ap;
3551
3552     va_start(ap, format);
3553     vprintf(format, ap);
3554     va_end(ap);
3555
3556     printf("\n");
3557   }
3558 }
3559
3560 #endif  /* DEBUG */
3561
3562 void print_timestamp_ext(char *message, char *mode)
3563 {
3564 #if DEBUG_PRINT_INIT_TIMESTAMPS
3565   static char *debug_message = NULL;
3566   static char *last_message = NULL;
3567   static int counter_nr = 0;
3568   int max_depth = DEBUG_PRINT_INIT_TIMESTAMPS_DEPTH;
3569
3570   checked_free(debug_message);
3571   debug_message = getStringCat3(mode, " ", message);
3572
3573   if (strEqual(mode, "INIT"))
3574   {
3575     debug_print_timestamp(counter_nr, NULL);
3576
3577     if (counter_nr + 1 < max_depth)
3578       debug_print_timestamp(counter_nr, debug_message);
3579
3580     counter_nr++;
3581
3582     debug_print_timestamp(counter_nr, NULL);
3583   }
3584   else if (strEqual(mode, "DONE"))
3585   {
3586     counter_nr--;
3587
3588     if (counter_nr + 1 < max_depth ||
3589         (counter_nr == 0 && max_depth == 1))
3590     {
3591       last_message = message;
3592
3593       if (counter_nr == 0 && max_depth == 1)
3594       {
3595         checked_free(debug_message);
3596         debug_message = getStringCat3("TIME", " ", message);
3597       }
3598
3599       debug_print_timestamp(counter_nr, debug_message);
3600     }
3601   }
3602   else if (!strEqual(mode, "TIME") ||
3603            !strEqual(message, last_message))
3604   {
3605     if (counter_nr < max_depth)
3606       debug_print_timestamp(counter_nr, debug_message);
3607   }
3608 #endif
3609 }
3610
3611 void print_timestamp_init(char *message)
3612 {
3613   print_timestamp_ext(message, "INIT");
3614 }
3615
3616 void print_timestamp_time(char *message)
3617 {
3618   print_timestamp_ext(message, "TIME");
3619 }
3620
3621 void print_timestamp_done(char *message)
3622 {
3623   print_timestamp_ext(message, "DONE");
3624 }