changed logging to write to one single log file
[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 //                  https://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_UNIX)
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 // logging functions
42 // ----------------------------------------------------------------------------
43
44 #define DUPLICATE_LOGGING_TO_STDOUT             TRUE
45
46
47 #if defined(PLATFORM_ANDROID)
48 static int android_log_prio = ANDROID_LOG_INFO;
49 static char *android_log_buffer = NULL;
50
51 static void append_to_android_log_buffer(char *format, va_list ap)
52 {
53   char text_new[MAX_OUTPUT_LINESIZE];
54
55   // print text to temporary string
56   vsnprintf(text_new, MAX_OUTPUT_LINESIZE, format, ap);
57
58   if (android_log_buffer == NULL)
59   {
60     android_log_buffer = getStringCopy(text_new);
61   }
62   else
63   {
64     char *android_log_buffer_old = android_log_buffer;
65
66     // append new text to existing text
67     android_log_buffer = getStringCat2(android_log_buffer, text_new);
68
69     checked_free(android_log_buffer_old);
70   }
71 }
72
73 static void vprintf_log_nonewline(char *format, va_list ap)
74 {
75   // add log output to buffer until text with newline is printed
76   append_to_android_log_buffer(format, ap);
77 }
78
79 static void vprintf_log(char *format, va_list ap)
80 {
81   // add log output to buffer
82   append_to_android_log_buffer(format, ap);
83
84   // __android_log_vprint(android_log_prio, program.program_title, format, ap);
85   __android_log_write(android_log_prio, program.program_title,
86                       android_log_buffer);
87
88   checked_free(android_log_buffer);
89   android_log_buffer = NULL;
90 }
91
92 #else
93
94 static void vprintf_log_nonewline(char *format, va_list ap)
95 {
96   FILE *file = program.log_file;
97
98 #if DUPLICATE_LOGGING_TO_STDOUT
99   if (file != program.log_file_default)
100   {
101     va_list ap2;
102     va_copy(ap2, ap);
103
104     vfprintf(program.log_file_default, format, ap2);
105
106     va_end(ap2);
107   }
108 #endif
109
110   vfprintf(file, format, ap);
111 }
112
113 static void vprintf_log(char *format, va_list ap)
114 {
115   FILE *file = program.log_file;
116   char *newline = STRING_NEWLINE;
117
118 #if DUPLICATE_LOGGING_TO_STDOUT
119   if (file != program.log_file_default)
120   {
121     va_list ap2;
122     va_copy(ap2, ap);
123
124     vfprintf(program.log_file_default, format, ap2);
125     fprintf(program.log_file_default, "%s", newline);
126
127     va_end(ap2);
128   }
129 #endif
130
131   vfprintf(file, format, ap);
132   fprintf(file, "%s", newline);
133 }
134 #endif
135
136 static void printf_log_nonewline(char *format, ...)
137 {
138   va_list ap;
139
140   va_start(ap, format);
141   vprintf_log_nonewline(format, ap);
142   va_end(ap);
143 }
144
145 static void printf_log(char *format, ...)
146 {
147   va_list ap;
148
149   va_start(ap, format);
150   vprintf_log(format, ap);
151   va_end(ap);
152 }
153
154 static void printf_log_line(char *line_chars, int line_length)
155 {
156   int i;
157
158   for (i = 0; i < line_length; i++)
159     printf_log_nonewline("%s", line_chars);
160
161   printf_log("");
162 }
163
164
165 // ----------------------------------------------------------------------------
166 // platform independent wrappers for printf() et al.
167 // ----------------------------------------------------------------------------
168
169 void fprintf_line(FILE *file, char *line_chars, int line_length)
170 {
171   int i;
172
173   for (i = 0; i < line_length; i++)
174     fprintf(file, "%s", line_chars);
175
176   fprintf(file, "\n");
177 }
178
179 void fprintf_line_with_prefix(FILE *file, char *prefix, char *line_chars,
180                               int line_length)
181 {
182   fprintf(file, "%s", prefix);
183   fprintf_line(file, line_chars, line_length);
184 }
185
186 void printf_line(char *line_chars, int line_length)
187 {
188   fprintf_line(stdout, line_chars, line_length);
189 }
190
191 void printf_line_with_prefix(char *prefix, char *line_chars, int line_length)
192 {
193   fprintf_line_with_prefix(stdout, prefix, line_chars, line_length);
194 }
195
196 static void vPrint(char *format, va_list ap)
197 {
198   FILE *file = program.log_file;
199
200 #if DUPLICATE_LOGGING_TO_STDOUT
201   if (file != program.log_file_default)
202   {
203     va_list ap2;
204     va_copy(ap2, ap);
205
206     vfprintf(program.log_file_default, format, ap2);
207
208     va_end(ap2);
209   }
210 #endif
211
212   vfprintf(file, format, ap);
213 }
214
215 void Print(char *format, ...)
216 {
217   va_list ap;
218
219   va_start(ap, format);
220   vPrint(format, ap);
221   va_end(ap);
222 }
223
224 void PrintNoLog(char *format, ...)
225 {
226   FILE *file = program.log_file_default;
227   va_list ap;
228
229   va_start(ap, format);
230   vfprintf(file, format, ap);
231   va_end(ap);
232
233   fflush(file);
234 }
235
236 void PrintLine(char *line_chars, int line_length)
237 {
238   int i;
239
240   for (i = 0; i < line_length; i++)
241     Print(line_chars);
242
243   Print("\n");
244 }
245
246 void PrintLineWithPrefix(char *prefix, char *line_chars, int line_length)
247 {
248   Print(prefix);
249   PrintLine(line_chars, line_length);
250 }
251
252
253 // ----------------------------------------------------------------------------
254 // generic logging and error handling functions
255 // ----------------------------------------------------------------------------
256
257 enum log_levels
258 {
259   LOG_UNKNOWN = 0,
260   LOG_DEBUG,
261   LOG_INFO,
262   LOG_WARN,
263   LOG_ERROR,
264   LOG_FATAL
265 };
266
267 static char *log_tokens[] =
268 {
269   "UNKNOWN",
270   "DEBUG",
271   "INFO",
272   "WARN",
273   "ERROR",
274   "FATAL"
275 };
276
277 static void printf_log_prefix(int log_level, char *mode)
278 {
279   if (log_level < 0 || log_level > LOG_FATAL)
280     return;
281
282   char *log_token = log_tokens[log_level];
283
284   if (log_level == LOG_DEBUG)
285     printf_log_nonewline("[%s] [%s] ", log_token, mode);
286   else
287     printf_log_nonewline("[%s] ", log_token);
288 }
289
290 static void vLog(int log_level, char *mode, char *format, va_list ap)
291 {
292   if (log_level < 0 || log_level > LOG_FATAL)
293     return;
294
295   if (log_level == LOG_DEBUG)
296   {
297     // only show debug messages when in debug mode
298     if (!options.debug)
299       return;
300
301     // if optional debug mode specified, limit debug output accordingly
302     if (options.debug_mode != NULL &&
303         strstr(mode, options.debug_mode) == NULL)
304       return;
305   }
306   else if (log_level == LOG_WARN)
307   {
308     // only show warning messages when in verbose mode
309     if (!options.verbose)
310       return;
311   }
312
313 #if defined(PLATFORM_ANDROID)
314   android_log_prio = (log_level == LOG_DEBUG ? ANDROID_LOG_DEBUG :
315                       log_level == LOG_INFO  ? ANDROID_LOG_INFO :
316                       log_level == LOG_WARN  ? ANDROID_LOG_WARN :
317                       log_level == LOG_ERROR ? ANDROID_LOG_ERROR :
318                       log_level == LOG_FATAL ? ANDROID_LOG_FATAL :
319                       ANDROID_LOG_UNKNOWN);
320 #endif
321
322   static boolean last_line_was_separator = FALSE;
323   char *log_token = log_tokens[log_level];
324
325   if (strEqual(format, "===") ||
326       strEqual(format, "---"))
327   {
328     static char *mode_last = NULL;
329     char line_char[2] = { format[0], '\0' };
330     int line_length = 80 - strlen(log_token) - 3;
331
332     if (log_level == LOG_DEBUG)
333       line_length -= strlen(mode) + 3;
334
335     if (last_line_was_separator && strEqual(mode, mode_last))
336       return;
337
338     printf_log_prefix(log_level, mode);
339     printf_log_line(line_char, line_length);
340
341     if (!strEqual(mode, mode_last))
342       setString(&mode_last, mode);
343
344     last_line_was_separator = TRUE;
345
346     return;
347   }
348
349   last_line_was_separator = FALSE;
350
351   printf_log_prefix(log_level, mode);
352
353   vprintf_log(format, ap);
354 }
355
356 static void Log(int log_level, char *mode, char *format, ...)
357 {
358   va_list ap;
359
360   va_start(ap, format);
361   vLog(log_level, mode, format, ap);
362   va_end(ap);
363 }
364
365 void DebugContinued(char *mode, char *format, ...)
366 {
367   static char message[MAX_LINE_LEN] = { 0 };
368
369   // initialize message (optional)
370   if (strEqual(format, ""))
371   {
372     message[0] = '\0';
373
374     return;
375   }
376
377   char *message_ptr = message + strlen(message);
378   int size_left = MAX_LINE_LEN - strlen(message);
379   va_list ap;
380
381   // append message
382   va_start(ap, format);
383   vsnprintf(message_ptr, size_left, format, ap);
384   va_end(ap);
385
386   // finalize message
387   if (strSuffix(format, "\n"))
388   {
389     message[strlen(message) - 1] = '\0';
390
391     Debug(mode, message);
392
393     message[0] = '\0';
394   }
395 }
396
397 void Debug(char *mode, char *format, ...)
398 {
399   va_list ap;
400
401   va_start(ap, format);
402   vLog(LOG_DEBUG, mode, format, ap);
403   va_end(ap);
404 }
405
406 void Info(char *format, ...)
407 {
408   va_list ap;
409
410   va_start(ap, format);
411   vLog(LOG_INFO, NULL, format, ap);
412   va_end(ap);
413 }
414
415 void Warn(char *format, ...)
416 {
417   va_list ap;
418
419   va_start(ap, format);
420   vLog(LOG_WARN, NULL, format, ap);
421   va_end(ap);
422 }
423
424 void Error(char *format, ...)
425 {
426   va_list ap;
427
428   va_start(ap, format);
429   vLog(LOG_ERROR, NULL, format, ap);
430   va_end(ap);
431 }
432
433 void Fail(char *format, ...)
434 {
435   va_list ap;
436
437   va_start(ap, format);
438   vLog(LOG_FATAL, NULL, format, ap);
439   va_end(ap);
440
441   if (!network.is_server_thread)
442   {
443     va_start(ap, format);
444     program.exit_message_function(format, ap);
445     va_end(ap);
446   }
447
448   Log(LOG_FATAL, NULL, "aborting");
449
450   // network server thread: normal exit
451   if (network.is_server_thread)
452     exit(1);
453
454   // main process: clean up stuff
455   program.exit_function(1);
456 }
457
458 void FailWithHelp(char *format, ...)
459 {
460   va_list ap;
461
462   va_start(ap, format);
463   vLog(LOG_FATAL, NULL, format, ap);
464   va_end(ap);
465
466   Log(LOG_FATAL, NULL, "try option '--help' for more information");
467   Log(LOG_FATAL, NULL, "aborting");
468
469   // main process: clean up stuff
470   program.exit_function(1);
471 }
472
473
474 // ----------------------------------------------------------------------------
475 // string functions
476 // ----------------------------------------------------------------------------
477
478 /* int2str() returns a number converted to a string;
479    the used memory is static, but will be overwritten by later calls,
480    so if you want to save the result, copy it to a private string buffer;
481    there can be 10 local calls of int2str() without buffering the result --
482    the 11th call will then destroy the result from the first call and so on. */
483
484 char *int2str(int number, int size)
485 {
486   static char shift_array[10][40];
487   static int shift_counter = 0;
488   char *s = shift_array[shift_counter];
489
490   shift_counter = (shift_counter + 1) % 10;
491
492   if (size > 20)
493     size = 20;
494
495   if (size > 0)
496   {
497     sprintf(s, "                    %09d", number);
498     return &s[strlen(s) - size];
499   }
500   else
501   {
502     sprintf(s, "%d", number);
503     return s;
504   }
505 }
506
507
508 // something similar to "int2str()" above, but allocates its own memory
509 // and has a different interface; we cannot use "itoa()", because this
510 // seems to be already defined when cross-compiling to the win32 target
511
512 char *i_to_a(unsigned int i)
513 {
514   static char *a = NULL;
515
516   checked_free(a);
517
518   if (i > 2147483647)   // yes, this is a kludge
519     i = 2147483647;
520
521   a = checked_malloc(10 + 1);
522
523   sprintf(a, "%d", i);
524
525   return a;
526 }
527
528
529 // calculate base-2 logarithm of argument (rounded down to integer;
530 // this function returns the number of the highest bit set in argument)
531
532 int log_2(unsigned int x)
533 {
534   int e = 0;
535
536   while ((1 << e) < x)
537   {
538     x -= (1 << e);      // for rounding down (rounding up: remove this line)
539     e++;
540   }
541
542   return e;
543 }
544
545 boolean getTokenValueFromString(char *string, char **token, char **value)
546 {
547   return getTokenValueFromSetupLine(string, token, value);
548 }
549
550
551 // ----------------------------------------------------------------------------
552 // UUID functions
553 // ----------------------------------------------------------------------------
554
555 #define UUID_BYTES              16
556 #define UUID_CHARS              (UUID_BYTES * 2)
557 #define UUID_LENGTH             (UUID_CHARS + 4)
558
559 static unsigned int uuid_random_function(int max)
560 {
561   return GetBetterRandom(max);
562 }
563
564 char *getUUIDExt(unsigned int (*random_function)(int max))
565 {
566   static char uuid[UUID_LENGTH + 1];
567   int data[UUID_BYTES];
568   int count = 0;
569   int i;
570
571   for (i = 0; i < UUID_BYTES; i++)
572     data[i] = random_function(256);
573
574   data[6] = 0x40 | (data[6] & 0x0f);
575   data[8] = 0x80 | (data[8] & 0x3f);
576
577   for (i = 0; i < UUID_BYTES; i++)
578   {
579     sprintf(&uuid[count], "%02x", data[i]);
580     count += 2;
581
582     if (i == 3 || i == 5 || i == 7 || i == 9)
583       strcat(&uuid[count++], "-");
584   }
585
586   return uuid;
587 }
588
589 char *getUUID(void)
590 {
591   return getUUIDExt(uuid_random_function);
592 }
593
594
595 // ----------------------------------------------------------------------------
596 // counter functions
597 // ----------------------------------------------------------------------------
598
599 // maximal allowed length of a command line option
600 #define MAX_OPTION_LEN          256
601
602 static unsigned int getCurrentMS(void)
603 {
604   return SDL_GetTicks();
605 }
606
607 static unsigned int mainCounter(int mode)
608 {
609   static unsigned int base_ms = 0;
610   unsigned int current_ms;
611
612   // get current system milliseconds
613   current_ms = getCurrentMS();
614
615   // reset base timestamp in case of counter reset or wrap-around
616   if (mode == INIT_COUNTER || current_ms < base_ms)
617     base_ms = current_ms;
618
619   // return milliseconds since last counter reset
620   return current_ms - base_ms;
621 }
622
623 void InitCounter()              // set counter back to zero
624 {
625   mainCounter(INIT_COUNTER);
626 }
627
628 unsigned int Counter()  // get milliseconds since last call of InitCounter()
629 {
630   return mainCounter(READ_COUNTER);
631 }
632
633 static void sleep_milliseconds(unsigned int milliseconds_delay)
634 {
635   SDL_Delay(milliseconds_delay);
636 }
637
638 void Delay(unsigned int delay)  // Sleep specified number of milliseconds
639 {
640   sleep_milliseconds(delay);
641 }
642
643 boolean DelayReachedExt2(unsigned int *counter_var, unsigned int delay,
644                          unsigned int actual_counter)
645 {
646   if (actual_counter >= *counter_var &&
647       actual_counter < *counter_var + delay)
648     return FALSE;
649
650   *counter_var = actual_counter;
651
652   return TRUE;
653 }
654
655 boolean DelayReachedExt(DelayCounter *counter, unsigned int actual_counter)
656 {
657   return DelayReachedExt2(&counter->count, counter->value, actual_counter);
658 }
659
660 boolean FrameReached(DelayCounter *counter)
661 {
662   return DelayReachedExt(counter, FrameCounter);
663 }
664
665 boolean DelayReached(DelayCounter *counter)
666 {
667   return DelayReachedExt(counter, Counter());
668 }
669
670 void ResetDelayCounterExt(DelayCounter *counter, unsigned int actual_counter)
671 {
672   DelayReachedExt2(&counter->count, 0, actual_counter);
673 }
674
675 void ResetFrameCounter(DelayCounter *counter)
676 {
677   ResetDelayCounterExt(counter, FrameCounter);
678 }
679
680 void ResetDelayCounter(DelayCounter *counter)
681 {
682   ResetDelayCounterExt(counter, Counter());
683 }
684
685 int WaitUntilDelayReached(DelayCounter *counter)
686 {
687   unsigned int *counter_var = &counter->count;
688   unsigned int delay = counter->value;
689   unsigned int actual_counter;
690   int skip_frames = 0;
691
692   while (1)
693   {
694     actual_counter = Counter();
695
696     if (actual_counter >= *counter_var &&
697         actual_counter < *counter_var + delay)
698       sleep_milliseconds((*counter_var + delay - actual_counter) / 2);
699     else
700       break;
701   }
702
703   if (*counter_var != 0 &&
704       delay != 0 &&
705       actual_counter >= *counter_var + delay)
706   {
707     int lag = actual_counter - (*counter_var + delay);
708     int delay2 = (delay + 1) / 2;
709
710     if (lag >= delay2)
711       skip_frames = (lag + delay2) / delay;
712   }
713
714   *counter_var = actual_counter;
715
716   return skip_frames;
717 }
718
719 void SkipUntilDelayReached(DelayCounter *counter,
720                            int *loop_var, int last_loop_value)
721 {
722   int skip_frames = WaitUntilDelayReached(counter);
723
724 #if 0
725 #if DEBUG
726   if (skip_frames)
727     Debug("internal:SkipUntilDelayReached",
728           "%d: %d ms -> SKIP %d FRAME(S) [%d ms]",
729           *loop_var, counter->value,
730           skip_frames, skip_frames * counter->value);
731   else
732     Debug("internal:SkipUntilDelayReached",
733           "%d: %d ms",
734           *loop_var, counter->value);
735 #endif
736 #endif
737
738   if (skip_frames == 0)
739     return;
740
741   // when skipping frames, make sure to never skip the last frame, as
742   // this may be needed for animations to reach a defined end state;
743   // furthermore, we assume that this function is called at the end
744   // of a "for" loop, which continues by incrementing the loop variable
745   // by one before checking the loop condition again; therefore we have
746   // to check against the last loop value minus one here
747
748   last_loop_value--;
749
750   if (*loop_var < last_loop_value)      // never skip the last frame
751   {
752     *loop_var += skip_frames;
753
754     if (*loop_var > last_loop_value)    // never skip the last frame
755       *loop_var = last_loop_value;
756   }
757 }
758
759
760 // ----------------------------------------------------------------------------
761 // random generator functions
762 // ----------------------------------------------------------------------------
763
764 static unsigned int init_random_number_ext(int nr, int seed)
765 {
766   if (seed == NEW_RANDOMIZE)
767   {
768     // default random seed
769     seed = (int)time(NULL);                     // seconds since the epoch
770
771 #if !defined(PLATFORM_WINDOWS)
772     // add some more randomness
773     struct timeval current_time;
774
775     gettimeofday(&current_time, NULL);
776
777     seed += (int)current_time.tv_usec;          // microseconds since the epoch
778 #endif
779
780     // add some more randomness
781     seed += (int)SDL_GetTicks();                // milliseconds since SDL init
782
783     // add some more randomness
784     seed += GetSimpleRandom(1000000);
785   }
786
787   srandom_linux_libc(nr, (unsigned int) seed);
788
789   return (unsigned int) seed;
790 }
791
792 static unsigned int prng_seed_gettimeofday(void)
793 {
794   struct timeval current_time;
795
796   gettimeofday(&current_time, NULL);
797
798   prng_seed_bytes(&current_time, sizeof(current_time));
799
800   return 0;
801 }
802
803 unsigned int init_random_number(int nr, int seed)
804 {
805   return (nr == RANDOM_BETTER ? prng_seed_gettimeofday() :
806           init_random_number_ext(nr, seed));
807 }
808
809 static unsigned int get_random_number_ext(int nr)
810 {
811   return (nr == RANDOM_BETTER ? prng_get_uint() :
812           random_linux_libc(nr));
813 }
814
815 unsigned int get_random_number(int nr, int max)
816 {
817   return (max > 0 ? get_random_number_ext(nr) % max : 0);
818 }
819
820
821 // ----------------------------------------------------------------------------
822 // system info functions
823 // ----------------------------------------------------------------------------
824
825 #if !defined(PLATFORM_ANDROID)
826 static char *get_corrected_real_name(char *real_name)
827 {
828   char *real_name_new = checked_malloc(MAX_USERNAME_LEN + 1);
829   char *from_ptr = real_name;
830   char *to_ptr   = real_name_new;
831
832   // copy the name string, but not more than MAX_USERNAME_LEN characters
833   while (*from_ptr && (int)(to_ptr - real_name_new) < MAX_USERNAME_LEN - 1)
834   {
835     // the name field read from "passwd" file may also contain additional
836     // user information, separated by commas, which will be removed here
837     if (*from_ptr == ',')
838       break;
839
840     // the user's real name may contain 'german sharp s' characters,
841     // which have no equivalent in upper case letters (used by our fonts)
842     if (*from_ptr == CHAR_BYTE_SHARP_S)
843     {
844       from_ptr++;
845       *to_ptr++ = 's';
846       *to_ptr++ = 's';
847     }
848     else
849       *to_ptr++ = *from_ptr++;
850   }
851
852   *to_ptr = '\0';
853
854   return real_name_new;
855 }
856 #endif
857
858 #if defined(PLATFORM_UNIX)
859 static struct passwd *getPasswdEntry(void)
860 {
861 #if defined(PLATFORM_EMSCRIPTEN)
862   // currently not fully supported; force fallback to default values
863   return NULL;
864 #else
865   return getpwuid(getuid());
866 #endif
867 }
868
869 char *getUnixLoginName(void)
870 {
871   struct passwd *pwd = getPasswdEntry();
872
873   if (pwd != NULL && strlen(pwd->pw_name) != 0)
874     return pwd->pw_name;
875
876   return NULL;
877 }
878
879 char *getUnixRealName(void)
880 {
881   struct passwd *pwd = getPasswdEntry();
882
883   if (pwd != NULL && strlen(pwd->pw_gecos) != 0)
884     return pwd->pw_gecos;
885
886   return NULL;
887 }
888
889 char *getUnixHomeDir(void)
890 {
891   struct passwd *pwd = getPasswdEntry();
892
893   if (pwd != NULL && strlen(pwd->pw_dir) != 0)
894     return pwd->pw_dir;
895
896   return NULL;
897 }
898 #endif
899
900 char *getLoginName(void)
901 {
902   static char *login_name = NULL;
903
904 #if defined(PLATFORM_WINDOWS)
905   if (login_name == NULL)
906   {
907     unsigned long buffer_size = MAX_USERNAME_LEN + 1;
908
909     login_name = checked_malloc(buffer_size);
910
911     if (GetUserName(login_name, &buffer_size) == 0)
912       strcpy(login_name, ANONYMOUS_NAME);
913   }
914 #elif defined(PLATFORM_UNIX) && !defined(PLATFORM_ANDROID)
915   if (login_name == NULL)
916   {
917     char *name = getUnixLoginName();
918
919     if (name != NULL)
920       login_name = getStringCopy(name);
921     else
922       login_name = ANONYMOUS_NAME;
923   }
924 #else
925   login_name = ANONYMOUS_NAME;
926 #endif
927
928   return login_name;
929 }
930
931 char *getRealName(void)
932 {
933   static char *real_name = NULL;
934
935 #if defined(PLATFORM_WINDOWS)
936   if (real_name == NULL)
937   {
938     static char buffer[MAX_USERNAME_LEN + 1];
939     unsigned long buffer_size = MAX_USERNAME_LEN + 1;
940
941     if (GetUserName(buffer, &buffer_size) != 0)
942       real_name = get_corrected_real_name(buffer);
943     else
944       real_name = ANONYMOUS_NAME;
945   }
946 #elif defined(PLATFORM_UNIX) && !defined(PLATFORM_ANDROID)
947   if (real_name == NULL)
948   {
949     char *name = getUnixRealName();
950
951     if (name != NULL)
952       real_name = get_corrected_real_name(name);
953     else
954       real_name = ANONYMOUS_NAME;
955   }
956 #else
957   real_name = ANONYMOUS_NAME;
958 #endif
959
960   return real_name;
961 }
962
963 char *getFixedUserName(char *name)
964 {
965   // needed because player name must be a fixed length string
966   char *name_new = checked_malloc(MAX_PLAYER_NAME_LEN + 1);
967
968   strncpy(name_new, name, MAX_PLAYER_NAME_LEN);
969   name_new[MAX_PLAYER_NAME_LEN] = '\0';
970
971   if (strlen(name) > MAX_PLAYER_NAME_LEN)               // name has been cut
972     if (strchr(name_new, ' '))
973       *strchr(name_new, ' ') = '\0';
974
975   return name_new;
976 }
977
978 char *getDefaultUserName(int nr)
979 {
980   static char *user_name[MAX_PLAYER_NAMES] = { NULL };
981
982   nr = MIN(MAX(0, nr), MAX_PLAYER_NAMES - 1);
983
984   if (user_name[nr] == NULL)
985   {
986     user_name[nr] = (nr == 0 ? getLoginName() : EMPTY_PLAYER_NAME);
987     user_name[nr] = getFixedUserName(user_name[nr]);
988   }
989
990   return user_name[nr];
991 }
992
993 char *getTimestampFromEpoch(time_t epoch_seconds)
994 {
995   struct tm *now = localtime(&epoch_seconds);
996   static char timestamp[20];
997
998   sprintf(timestamp, "%04d%02d%02d-%02d%02d%02d",
999           now->tm_year + 1900, now->tm_mon + 1, now->tm_mday,
1000           now->tm_hour, now->tm_min, now->tm_sec);
1001
1002   return timestamp;
1003 }
1004
1005 char *getCurrentTimestamp(void)
1006 {
1007   return getTimestampFromEpoch(time(NULL));
1008 }
1009
1010 time_t getFileTimestampEpochSeconds(char *filename)
1011 {
1012   struct stat file_status;
1013
1014   if (stat(filename, &file_status) != 0)        // cannot stat file
1015     return 0;
1016
1017   return file_status.st_mtime;
1018 }
1019
1020
1021 // ----------------------------------------------------------------------------
1022 // path manipulation functions
1023 // ----------------------------------------------------------------------------
1024
1025 static char *getLastPathSeparatorPtr(char *filename)
1026 {
1027   char *last_separator = strrchr(filename, CHAR_PATH_SEPARATOR_UNIX);
1028
1029   if (last_separator == NULL)   // also try DOS/Windows variant
1030     last_separator = strrchr(filename, CHAR_PATH_SEPARATOR_DOS);
1031
1032   return last_separator;
1033 }
1034
1035 char *getBaseNamePtr(char *filename)
1036 {
1037   char *last_separator = getLastPathSeparatorPtr(filename);
1038
1039   if (last_separator != NULL)
1040     return last_separator + 1;  // separator found: strip base path
1041   else
1042     return filename;            // no separator found: filename has no path
1043 }
1044
1045 char *getBaseName(char *filename)
1046 {
1047   return getStringCopy(getBaseNamePtr(filename));
1048 }
1049
1050 char *getBaseNameNoSuffix(char *filename)
1051 {
1052   char *basename = getStringCopy(getBaseNamePtr(filename));
1053
1054   // remove trailing suffix (separated by dot or hyphen)
1055   if (basename[0] != '.' && basename[0] != '-')
1056   {
1057     if (strchr(basename, '.') != NULL)
1058       *strchr(basename, '.') = '\0';
1059
1060     if (strchr(basename, '-') != NULL)
1061       *strchr(basename, '-') = '\0';
1062   }
1063
1064   return basename;
1065 }
1066
1067 char *getBasePath(char *filename)
1068 {
1069   char *basepath = getStringCopy(filename);
1070   char *last_separator = getLastPathSeparatorPtr(basepath);
1071
1072   // if no separator was found, use current directory
1073   if (last_separator == NULL)
1074   {
1075     free(basepath);
1076
1077     return getStringCopy(".");
1078   }
1079
1080   // separator found: strip basename
1081   *last_separator = '\0';
1082
1083   return basepath;
1084 }
1085
1086
1087 // ----------------------------------------------------------------------------
1088 // various string functions
1089 // ----------------------------------------------------------------------------
1090
1091 char *getStringCat2WithSeparator(char *s1, char *s2, char *sep)
1092 {
1093   if (s1 == NULL || s2 == NULL || sep == NULL)
1094     return NULL;
1095
1096   char *complete_string = checked_malloc(strlen(s1) + strlen(sep) +
1097                                          strlen(s2) + 1);
1098
1099   sprintf(complete_string, "%s%s%s", s1, sep, s2);
1100
1101   return complete_string;
1102 }
1103
1104 char *getStringCat3WithSeparator(char *s1, char *s2, char *s3, char *sep)
1105 {
1106   if (s1 == NULL || s2 == NULL || s3 == NULL || sep == NULL)
1107     return NULL;
1108
1109   char *complete_string = checked_malloc(strlen(s1) + strlen(sep) +
1110                                          strlen(s2) + strlen(sep) +
1111                                          strlen(s3) + 1);
1112
1113   sprintf(complete_string, "%s%s%s%s%s", s1, sep, s2, sep, s3);
1114
1115   return complete_string;
1116 }
1117
1118 char *getStringCat2(char *s1, char *s2)
1119 {
1120   return getStringCat2WithSeparator(s1, s2, "");
1121 }
1122
1123 char *getStringCat3(char *s1, char *s2, char *s3)
1124 {
1125   return getStringCat3WithSeparator(s1, s2, s3, "");
1126 }
1127
1128 char *getPath2(char *path1, char *path2)
1129 {
1130 #if defined(PLATFORM_ANDROID)
1131   // workaround for reading from assets directory -- skip "." subdirs in path
1132   if (strEqual(path1, "."))
1133     return getStringCopy(path2);
1134   else if (strEqual(path2, "."))
1135     return getStringCopy(path1);
1136 #endif
1137
1138   return getStringCat2WithSeparator(path1, path2, STRING_PATH_SEPARATOR);
1139 }
1140
1141 char *getPath3(char *path1, char *path2, char *path3)
1142 {
1143 #if defined(PLATFORM_ANDROID)
1144   // workaround for reading from assets directory -- skip "." subdirs in path
1145   if (strEqual(path1, "."))
1146     return getStringCat2WithSeparator(path2, path3, STRING_PATH_SEPARATOR);
1147   else if (strEqual(path2, "."))
1148     return getStringCat2WithSeparator(path1, path3, STRING_PATH_SEPARATOR);
1149   else if (strEqual(path3, "."))
1150     return getStringCat2WithSeparator(path1, path2, STRING_PATH_SEPARATOR);
1151 #endif
1152
1153   return getStringCat3WithSeparator(path1, path2, path3, STRING_PATH_SEPARATOR);
1154 }
1155
1156 static char *getPngOrPcxIfNotExists(char *filename)
1157 {
1158   // switch from PNG to PCX file and vice versa, if file does not exist
1159   // (backwards compatibility with PCX files used in previous versions)
1160
1161   if (!fileExists(filename) && strSuffix(filename, ".png"))
1162     strcpy(&filename[strlen(filename) - 3], "pcx");
1163   else if (!fileExists(filename) && strSuffix(filename, ".pcx"))
1164     strcpy(&filename[strlen(filename) - 3], "png");
1165
1166   return filename;
1167 }
1168
1169 char *getImg2(char *path1, char *path2)
1170 {
1171   return getPngOrPcxIfNotExists(getPath2(path1, path2));
1172 }
1173
1174 char *getImg3(char *path1, char *path2, char *path3)
1175 {
1176   return getPngOrPcxIfNotExists(getPath3(path1, path2, path3));
1177 }
1178
1179 char *getStringCopy(const char *s)
1180 {
1181   char *s_copy;
1182
1183   if (s == NULL)
1184     return NULL;
1185
1186   s_copy = checked_malloc(strlen(s) + 1);
1187   strcpy(s_copy, s);
1188
1189   return s_copy;
1190 }
1191
1192 char *getStringCopyN(const char *s, int n)
1193 {
1194   char *s_copy;
1195   int s_len = MAX(0, n);
1196
1197   if (s == NULL)
1198     return NULL;
1199
1200   s_copy = checked_malloc(s_len + 1);
1201   strncpy(s_copy, s, s_len);
1202   s_copy[s_len] = '\0';
1203
1204   return s_copy;
1205 }
1206
1207 char *getStringCopyNStatic(const char *s, int n)
1208 {
1209   static char *s_copy = NULL;
1210
1211   checked_free(s_copy);
1212
1213   s_copy = getStringCopyN(s, n);
1214
1215   return s_copy;
1216 }
1217
1218 char *getStringToLower(const char *s)
1219 {
1220   char *s_copy = checked_malloc(strlen(s) + 1);
1221   char *s_ptr = s_copy;
1222
1223   while (*s)
1224     *s_ptr++ = tolower(*s++);
1225   *s_ptr = '\0';
1226
1227   return s_copy;
1228 }
1229
1230 void setString(char **old_value, char *new_value)
1231 {
1232   checked_free(*old_value);
1233
1234   *old_value = getStringCopy(new_value);
1235 }
1236
1237 boolean strEqual(char *s1, char *s2)
1238 {
1239   return (s1 == NULL && s2 == NULL ? TRUE  :
1240           s1 == NULL && s2 != NULL ? FALSE :
1241           s1 != NULL && s2 == NULL ? FALSE :
1242           strcmp(s1, s2) == 0);
1243 }
1244
1245 boolean strEqualN(char *s1, char *s2, int n)
1246 {
1247   return (s1 == NULL && s2 == NULL ? TRUE  :
1248           s1 == NULL && s2 != NULL ? FALSE :
1249           s1 != NULL && s2 == NULL ? FALSE :
1250           strncmp(s1, s2, n) == 0);
1251 }
1252
1253 boolean strEqualCase(char *s1, char *s2)
1254 {
1255   return (s1 == NULL && s2 == NULL ? TRUE  :
1256           s1 == NULL && s2 != NULL ? FALSE :
1257           s1 != NULL && s2 == NULL ? FALSE :
1258           strcasecmp(s1, s2) == 0);
1259 }
1260
1261 boolean strEqualCaseN(char *s1, char *s2, int n)
1262 {
1263   return (s1 == NULL && s2 == NULL ? TRUE  :
1264           s1 == NULL && s2 != NULL ? FALSE :
1265           s1 != NULL && s2 == NULL ? FALSE :
1266           strncasecmp(s1, s2, n) == 0);
1267 }
1268
1269 boolean strPrefix(char *s, char *prefix)
1270 {
1271   return (s == NULL && prefix == NULL ? TRUE  :
1272           s == NULL && prefix != NULL ? FALSE :
1273           s != NULL && prefix == NULL ? FALSE :
1274           strncmp(s, prefix, strlen(prefix)) == 0);
1275 }
1276
1277 boolean strSuffix(char *s, char *suffix)
1278 {
1279   return (s == NULL && suffix == NULL ? TRUE  :
1280           s == NULL && suffix != NULL ? FALSE :
1281           s != NULL && suffix == NULL ? FALSE :
1282           strlen(s) < strlen(suffix)  ? FALSE :
1283           strcmp(&s[strlen(s) - strlen(suffix)], suffix) == 0);
1284 }
1285
1286 boolean strPrefixLower(char *s, char *prefix)
1287 {
1288   char *s_lower = getStringToLower(s);
1289   boolean match = strPrefix(s_lower, prefix);
1290
1291   free(s_lower);
1292
1293   return match;
1294 }
1295
1296 boolean strSuffixLower(char *s, char *suffix)
1297 {
1298   char *s_lower = getStringToLower(s);
1299   boolean match = strSuffix(s_lower, suffix);
1300
1301   free(s_lower);
1302
1303   return match;
1304 }
1305
1306 boolean isURL(char *s)
1307 {
1308   while (*s && *s >= 'a' && *s <= 'z')
1309     s++;
1310
1311   return strPrefix(s, "://");
1312 }
1313
1314
1315 // ----------------------------------------------------------------------------
1316 // command line option handling functions
1317 // ----------------------------------------------------------------------------
1318
1319 void GetOptions(int argc, char *argv[],
1320                 void (*print_usage_function)(void),
1321                 void (*print_version_function)(void))
1322 {
1323   char *base_path = getProgramMainDataPath(argv[0], BASE_PATH);
1324   char **argvplus = checked_calloc((argc + 1) * sizeof(char **));
1325   char **options_left = &argvplus[1];
1326
1327   // replace original "argv" with null-terminated array of string pointers
1328   while (argc--)
1329     argvplus[argc] = argv[argc];
1330
1331   // initialize global program options
1332   options.server_host = NULL;
1333   options.server_port = 0;
1334
1335   options.base_directory = base_path;
1336
1337   options.level_directory    = getPath2(base_path, LEVELS_DIRECTORY);
1338   options.graphics_directory = getPath2(base_path, GRAPHICS_DIRECTORY);
1339   options.sounds_directory   = getPath2(base_path, SOUNDS_DIRECTORY);
1340   options.music_directory    = getPath2(base_path, MUSIC_DIRECTORY);
1341   options.docs_directory     = getPath2(base_path, DOCS_DIRECTORY);
1342   options.conf_directory     = getPath2(base_path, CONF_DIRECTORY);
1343
1344   options.execute_command = NULL;
1345   options.tape_log_filename = NULL;
1346   options.special_flags = NULL;
1347   options.debug_mode = NULL;
1348   options.player_name = NULL;
1349   options.identifier = NULL;
1350   options.level_nr = NULL;
1351
1352   options.display_nr = 0;
1353
1354   options.mytapes = FALSE;
1355   options.serveronly = FALSE;
1356   options.network = FALSE;
1357   options.verbose = FALSE;
1358   options.debug = FALSE;
1359
1360 #if 1
1361   options.verbose = TRUE;
1362 #else
1363 #if !defined(PLATFORM_UNIX)
1364   if (*options_left == NULL)    // no options given -- enable verbose mode
1365     options.verbose = TRUE;
1366 #endif
1367 #endif
1368
1369 #if DEBUG
1370 #if defined(PLATFORM_ANDROID)
1371   options.debug = TRUE;
1372 #endif
1373 #endif
1374
1375   while (*options_left)
1376   {
1377     char option_str[MAX_OPTION_LEN];
1378     char *option = options_left[0];
1379     char *next_option = options_left[1];
1380     char *option_arg = NULL;
1381     int option_len = strlen(option);
1382
1383     if (option_len >= MAX_OPTION_LEN)
1384       FailWithHelp("unrecognized option '%s'", option);
1385
1386     strcpy(option_str, option);                 // copy argument into buffer
1387     option = option_str;
1388
1389     if (strEqual(option, "--"))                 // stop scanning arguments
1390       break;
1391
1392     if (strPrefix(option, "--"))                // treat '--' like '-'
1393       option++;
1394
1395     option_arg = strchr(option, '=');
1396     if (option_arg == NULL)                     // no '=' in option
1397       option_arg = next_option;
1398     else
1399     {
1400       *option_arg++ = '\0';                     // cut argument from option
1401       if (*option_arg == '\0')                  // no argument after '='
1402         FailWithHelp("option '%s' has invalid argument", option_str);
1403     }
1404
1405     option_len = strlen(option);
1406
1407     if (strEqual(option, "-"))
1408     {
1409       FailWithHelp("unrecognized option '%s'", option);
1410     }
1411     else if (strncmp(option, "-help", option_len) == 0)
1412     {
1413       print_usage_function();
1414
1415       exit(0);
1416     }
1417     else if (strncmp(option, "-basepath", option_len) == 0)
1418     {
1419       if (option_arg == NULL)
1420         FailWithHelp("option '%s' requires an argument", option_str);
1421
1422       options.base_directory = base_path = getStringCopy(option_arg);
1423       if (option_arg == next_option)
1424         options_left++;
1425
1426       // adjust paths for sub-directories in base directory accordingly
1427       options.level_directory    = getPath2(base_path, LEVELS_DIRECTORY);
1428       options.graphics_directory = getPath2(base_path, GRAPHICS_DIRECTORY);
1429       options.sounds_directory   = getPath2(base_path, SOUNDS_DIRECTORY);
1430       options.music_directory    = getPath2(base_path, MUSIC_DIRECTORY);
1431       options.docs_directory     = getPath2(base_path, DOCS_DIRECTORY);
1432       options.conf_directory     = getPath2(base_path, CONF_DIRECTORY);
1433     }
1434     else if (strncmp(option, "-levels", option_len) == 0)
1435     {
1436       if (option_arg == NULL)
1437         FailWithHelp("option '%s' requires an argument", option_str);
1438
1439       options.level_directory = getStringCopy(option_arg);
1440       if (option_arg == next_option)
1441         options_left++;
1442     }
1443     else if (strncmp(option, "-graphics", option_len) == 0)
1444     {
1445       if (option_arg == NULL)
1446         FailWithHelp("option '%s' requires an argument", option_str);
1447
1448       options.graphics_directory = getStringCopy(option_arg);
1449       if (option_arg == next_option)
1450         options_left++;
1451     }
1452     else if (strncmp(option, "-sounds", option_len) == 0)
1453     {
1454       if (option_arg == NULL)
1455         FailWithHelp("option '%s' requires an argument", option_str);
1456
1457       options.sounds_directory = getStringCopy(option_arg);
1458       if (option_arg == next_option)
1459         options_left++;
1460     }
1461     else if (strncmp(option, "-music", option_len) == 0)
1462     {
1463       if (option_arg == NULL)
1464         FailWithHelp("option '%s' requires an argument", option_str);
1465
1466       options.music_directory = getStringCopy(option_arg);
1467       if (option_arg == next_option)
1468         options_left++;
1469     }
1470     else if (strncmp(option, "-mytapes", option_len) == 0)
1471     {
1472       options.mytapes = TRUE;
1473     }
1474     else if (strncmp(option, "-network", option_len) == 0)
1475     {
1476       options.network = TRUE;
1477     }
1478     else if (strncmp(option, "-serveronly", option_len) == 0)
1479     {
1480       options.serveronly = TRUE;
1481     }
1482     else if (strncmp(option, "-debug", option_len) == 0)
1483     {
1484       options.debug = TRUE;
1485
1486       // optionally, debug output can be limited to a specific debug mode
1487       if (option_arg != next_option)
1488         options.debug_mode = getStringCopy(option_arg);
1489     }
1490     else if (strncmp(option, "-player-name", option_len) == 0)
1491     {
1492       if (option_arg == NULL)
1493         FailWithHelp("option '%s' requires an argument", option_str);
1494
1495       options.player_name = getStringCopy(option_arg);
1496       if (option_arg == next_option)
1497         options_left++;
1498     }
1499     else if (strncmp(option, "-identifier", option_len) == 0)
1500     {
1501       if (option_arg == NULL)
1502         FailWithHelp("option '%s' requires an argument", option_str);
1503
1504       options.identifier = getStringCopy(option_arg);
1505       if (option_arg == next_option)
1506         options_left++;
1507     }
1508     else if (strncmp(option, "-level-nr", option_len) == 0)
1509     {
1510       if (option_arg == NULL)
1511         FailWithHelp("option '%s' requires an argument", option_str);
1512
1513       options.level_nr = getStringCopy(option_arg);
1514       if (option_arg == next_option)
1515         options_left++;
1516     }
1517     else if (strncmp(option, "-verbose", option_len) == 0)
1518     {
1519       options.verbose = TRUE;
1520     }
1521     else if (strncmp(option, "-version", option_len) == 0 ||
1522              strncmp(option, "-V", option_len) == 0)
1523     {
1524       print_version_function();
1525
1526       exit(0);
1527     }
1528     else if (strPrefix(option, "-D"))
1529     {
1530       options.special_flags = getStringCopy(&option[2]);
1531     }
1532     else if (strncmp(option, "-execute", option_len) == 0)
1533     {
1534       if (option_arg == NULL)
1535         FailWithHelp("option '%s' requires an argument", option_str);
1536
1537       options.execute_command = getStringCopy(option_arg);
1538       if (option_arg == next_option)
1539         options_left++;
1540
1541       // when doing batch processing, always enable verbose mode (warnings)
1542       options.verbose = TRUE;
1543     }
1544     else if (strncmp(option, "-tape_logfile", option_len) == 0)
1545     {
1546       if (option_arg == NULL)
1547         FailWithHelp("option '%s' requires an argument", option_str);
1548
1549       options.tape_log_filename = getStringCopy(option_arg);
1550       if (option_arg == next_option)
1551         options_left++;
1552     }
1553     else if (strncmp(option, "-display", option_len) == 0)
1554     {
1555       if (option_arg == NULL)
1556         FailWithHelp("option '%s' requires an argument", option_str);
1557
1558       if (option_arg == next_option)
1559         options_left++;
1560
1561       int display_nr = atoi(option_arg);
1562
1563 #if 1
1564       // dirty hack: SDL_GetNumVideoDisplays() seems broken on some systems
1565       options.display_nr = display_nr;
1566 #else
1567       options.display_nr =
1568         MAX(0, MIN(display_nr, SDL_GetNumVideoDisplays() - 1));
1569
1570       if (display_nr != options.display_nr)
1571         Warn("invalid display %d -- using display %d",
1572              display_nr, options.display_nr);
1573 #endif
1574     }
1575 #if defined(PLATFORM_MAC)
1576     else if (strPrefix(option, "-psn"))
1577     {
1578       // ignore process serial number when launched via GUI on Mac OS X
1579     }
1580 #endif
1581     else if (*option == '-')
1582     {
1583       FailWithHelp("unrecognized option '%s'", option_str);
1584     }
1585     else if (options.server_host == NULL)
1586     {
1587       options.server_host = *options_left;
1588     }
1589     else if (options.server_port == 0)
1590     {
1591       options.server_port = atoi(*options_left);
1592       if (options.server_port < 1024)
1593         FailWithHelp("bad port number '%d'", options.server_port);
1594     }
1595     else
1596       FailWithHelp("too many arguments");
1597
1598     options_left++;
1599   }
1600 }
1601
1602
1603 // ----------------------------------------------------------------------------
1604 // checked memory allocation and freeing functions
1605 // ----------------------------------------------------------------------------
1606
1607 void *checked_malloc(unsigned int size)
1608 {
1609   void *ptr;
1610
1611   ptr = malloc(size);
1612
1613   if (ptr == NULL)
1614     Fail("cannot allocate %d bytes -- out of memory", size);
1615
1616   return ptr;
1617 }
1618
1619 void *checked_calloc(unsigned int size)
1620 {
1621   void *ptr;
1622
1623   ptr = calloc(1, size);
1624
1625   if (ptr == NULL)
1626     Fail("cannot allocate %d bytes -- out of memory", size);
1627
1628   return ptr;
1629 }
1630
1631 void *checked_realloc(void *ptr, unsigned int size)
1632 {
1633   ptr = realloc(ptr, size);
1634
1635   if (ptr == NULL)
1636     Fail("cannot allocate %d bytes -- out of memory", size);
1637
1638   return ptr;
1639 }
1640
1641 void checked_free(void *ptr)
1642 {
1643   if (ptr != NULL)      // this check should be done by free() anyway
1644     free(ptr);
1645 }
1646
1647 void clear_mem(void *ptr, unsigned int size)
1648 {
1649 #if defined(PLATFORM_WINDOWS)
1650   // for unknown reason, memset() sometimes crashes when compiled with MinGW
1651   char *cptr = (char *)ptr;
1652
1653   while (size--)
1654     *cptr++ = 0;
1655 #else
1656   memset(ptr, 0, size);
1657 #endif
1658 }
1659
1660
1661 // ----------------------------------------------------------------------------
1662 // various helper functions
1663 // ----------------------------------------------------------------------------
1664
1665 void swap_numbers(int *i1, int *i2)
1666 {
1667   int help = *i1;
1668
1669   *i1 = *i2;
1670   *i2 = help;
1671 }
1672
1673 void swap_number_pairs(int *x1, int *y1, int *x2, int *y2)
1674 {
1675   int help_x = *x1;
1676   int help_y = *y1;
1677
1678   *x1 = *x2;
1679   *x2 = help_x;
1680
1681   *y1 = *y2;
1682   *y2 = help_y;
1683 }
1684
1685 /* the "put" variants of the following file access functions check for the file
1686    pointer being != NULL and return the number of bytes they have or would have
1687    written; this allows for chunk writing functions to first determine the size
1688    of the (not yet written) chunk, write the correct chunk size and finally
1689    write the chunk itself */
1690
1691 int getFile8BitInteger(File *file)
1692 {
1693   return getByteFromFile(file);
1694 }
1695
1696 int putFile8BitInteger(FILE *file, int value)
1697 {
1698   if (file != NULL)
1699     fputc(value, file);
1700
1701   return 1;
1702 }
1703
1704 int getFile16BitInteger(File *file, int byte_order)
1705 {
1706   if (byte_order == BYTE_ORDER_BIG_ENDIAN)
1707     return ((getByteFromFile(file) << 8) |
1708             (getByteFromFile(file) << 0));
1709   else           // BYTE_ORDER_LITTLE_ENDIAN
1710     return ((getByteFromFile(file) << 0) |
1711             (getByteFromFile(file) << 8));
1712 }
1713
1714 int putFile16BitInteger(FILE *file, int value, int byte_order)
1715 {
1716   if (file != NULL)
1717   {
1718     if (byte_order == BYTE_ORDER_BIG_ENDIAN)
1719     {
1720       fputc((value >> 8) & 0xff, file);
1721       fputc((value >> 0) & 0xff, file);
1722     }
1723     else           // BYTE_ORDER_LITTLE_ENDIAN
1724     {
1725       fputc((value >> 0) & 0xff, file);
1726       fputc((value >> 8) & 0xff, file);
1727     }
1728   }
1729
1730   return 2;
1731 }
1732
1733 int getFile32BitInteger(File *file, int byte_order)
1734 {
1735   if (byte_order == BYTE_ORDER_BIG_ENDIAN)
1736     return ((getByteFromFile(file) << 24) |
1737             (getByteFromFile(file) << 16) |
1738             (getByteFromFile(file) <<  8) |
1739             (getByteFromFile(file) <<  0));
1740   else           // BYTE_ORDER_LITTLE_ENDIAN
1741     return ((getByteFromFile(file) <<  0) |
1742             (getByteFromFile(file) <<  8) |
1743             (getByteFromFile(file) << 16) |
1744             (getByteFromFile(file) << 24));
1745 }
1746
1747 int putFile32BitInteger(FILE *file, int value, int byte_order)
1748 {
1749   if (file != NULL)
1750   {
1751     if (byte_order == BYTE_ORDER_BIG_ENDIAN)
1752     {
1753       fputc((value >> 24) & 0xff, file);
1754       fputc((value >> 16) & 0xff, file);
1755       fputc((value >>  8) & 0xff, file);
1756       fputc((value >>  0) & 0xff, file);
1757     }
1758     else           // BYTE_ORDER_LITTLE_ENDIAN
1759     {
1760       fputc((value >>  0) & 0xff, file);
1761       fputc((value >>  8) & 0xff, file);
1762       fputc((value >> 16) & 0xff, file);
1763       fputc((value >> 24) & 0xff, file);
1764     }
1765   }
1766
1767   return 4;
1768 }
1769
1770 boolean getFileChunk(File *file, char *chunk_name, int *chunk_size,
1771                      int byte_order)
1772 {
1773   const int chunk_name_length = 4;
1774
1775   // read chunk name
1776   if (getStringFromFile(file, chunk_name, chunk_name_length + 1) == NULL)
1777     return FALSE;
1778
1779   if (chunk_size != NULL)
1780   {
1781     // read chunk size
1782     *chunk_size = getFile32BitInteger(file, byte_order);
1783   }
1784
1785   return (checkEndOfFile(file) ? FALSE : TRUE);
1786 }
1787
1788 int putFileChunk(FILE *file, char *chunk_name, int chunk_size,
1789                  int byte_order)
1790 {
1791   int num_bytes = 0;
1792
1793   // write chunk name
1794   if (file != NULL)
1795     fputs(chunk_name, file);
1796
1797   num_bytes += strlen(chunk_name);
1798
1799   if (chunk_size >= 0)
1800   {
1801     // write chunk size
1802     if (file != NULL)
1803       putFile32BitInteger(file, chunk_size, byte_order);
1804
1805     num_bytes += 4;
1806   }
1807
1808   return num_bytes;
1809 }
1810
1811 int getFileVersion(File *file)
1812 {
1813   int version_super = getByteFromFile(file);
1814   int version_major = getByteFromFile(file);
1815   int version_minor = getByteFromFile(file);
1816   int version_patch = getByteFromFile(file);
1817
1818   return VERSION_IDENT(version_super, version_major, version_minor,
1819                        version_patch);
1820 }
1821
1822 int putFileVersion(FILE *file, int version)
1823 {
1824   if (file != NULL)
1825   {
1826     int version_super = VERSION_SUPER(version);
1827     int version_major = VERSION_MAJOR(version);
1828     int version_minor = VERSION_MINOR(version);
1829     int version_patch = VERSION_PATCH(version);
1830
1831     fputc(version_super, file);
1832     fputc(version_major, file);
1833     fputc(version_minor, file);
1834     fputc(version_patch, file);
1835   }
1836
1837   return 4;
1838 }
1839
1840 void ReadBytesFromFile(File *file, byte *buffer, unsigned int bytes)
1841 {
1842   int i;
1843
1844   for (i = 0; i < bytes && !checkEndOfFile(file); i++)
1845     buffer[i] = getByteFromFile(file);
1846 }
1847
1848 void WriteBytesToFile(FILE *file, byte *buffer, unsigned int bytes)
1849 {
1850   int i;
1851
1852   for (i = 0; i < bytes; i++)
1853     fputc(buffer[i], file);
1854 }
1855
1856 void ReadUnusedBytesFromFile(File *file, unsigned int bytes)
1857 {
1858   while (bytes-- && !checkEndOfFile(file))
1859     getByteFromFile(file);
1860 }
1861
1862 void WriteUnusedBytesToFile(FILE *file, unsigned int bytes)
1863 {
1864   while (bytes--)
1865     fputc(0, file);
1866 }
1867
1868
1869 // ----------------------------------------------------------------------------
1870 // functions to convert between ISO-8859-1 and UTF-8
1871 // ----------------------------------------------------------------------------
1872
1873 char *getUTF8FromLatin1(char *latin1)
1874 {
1875   int max_utf8_size = 2 * strlen(latin1) + 1;
1876   char *utf8 = checked_calloc(max_utf8_size);
1877   unsigned char *src = (unsigned char *)latin1;
1878   unsigned char *dst = (unsigned char *)utf8;
1879
1880   while (*src)
1881   {
1882     if (*src < 128)             // pure 7-bit ASCII
1883     {
1884       *dst++ = *src;
1885     }
1886     else if (*src >= 160)       // non-ASCII characters
1887     {
1888       *dst++ = 194 + (*src >= 192);
1889       *dst++ = 128 + (*src & 63);
1890     }
1891     else                        // undefined in ISO-8859-1
1892     {
1893       *dst++ = '?';
1894     }
1895
1896     src++;
1897   }
1898
1899   // only use the smallest possible string buffer size
1900   utf8 = checked_realloc(utf8, strlen(utf8) + 1);
1901
1902   return utf8;
1903 }
1904
1905 char *getLatin1FromUTF8(char *utf8)
1906 {
1907   int max_latin1_size = strlen(utf8) + 1;
1908   char *latin1 = checked_calloc(max_latin1_size);
1909   unsigned char *src = (unsigned char *)utf8;
1910   unsigned char *dst = (unsigned char *)latin1;
1911
1912   while (*src)
1913   {
1914     if (*src < 128)                             // pure 7-bit ASCII
1915     {
1916       *dst++ = *src++;
1917     }
1918     else if (src[0] == 194 &&
1919              src[1] >= 128 && src[1] < 192)     // non-ASCII characters
1920     {
1921       *dst++ = src[1];
1922       src += 2;
1923     }
1924     else if (src[0] == 195 &&
1925              src[1] >= 128 && src[1] < 192)     // non-ASCII characters
1926     {
1927       *dst++ = src[1] + 64;
1928       src += 2;
1929     }
1930
1931     // all other UTF-8 characters are undefined in ISO-8859-1
1932
1933     else if (src[0] >= 192 && src[0] < 224 &&
1934              src[1] >= 128 && src[1] < 192)
1935     {
1936       *dst++ = '?';
1937       src += 2;
1938     }
1939     else if (src[0] >= 224 && src[0] < 240 &&
1940              src[1] >= 128 && src[1] < 192 &&
1941              src[2] >= 128 && src[2] < 192)
1942     {
1943       *dst++ = '?';
1944       src += 3;
1945     }
1946     else if (src[0] >= 240 && src[0] < 248 &&
1947              src[1] >= 128 && src[1] < 192 &&
1948              src[2] >= 128 && src[2] < 192 &&
1949              src[3] >= 128 && src[3] < 192)
1950     {
1951       *dst++ = '?';
1952       src += 4;
1953     }
1954     else if (src[0] >= 248 && src[0] < 252 &&
1955              src[1] >= 128 && src[1] < 192 &&
1956              src[2] >= 128 && src[2] < 192 &&
1957              src[3] >= 128 && src[3] < 192 &&
1958              src[4] >= 128 && src[4] < 192)
1959     {
1960       *dst++ = '?';
1961       src += 5;
1962     }
1963     else if (src[0] >= 252 && src[0] < 254 &&
1964              src[1] >= 128 && src[1] < 192 &&
1965              src[2] >= 128 && src[2] < 192 &&
1966              src[3] >= 128 && src[3] < 192 &&
1967              src[4] >= 128 && src[4] < 192 &&
1968              src[5] >= 128 && src[5] < 192)
1969     {
1970       *dst++ = '?';
1971       src += 6;
1972     }
1973     else
1974     {
1975       *dst++ = '?';
1976       src++;
1977     }
1978   }
1979
1980   // only use the smallest possible string buffer size
1981   latin1 = checked_realloc(latin1, strlen(latin1) + 1);
1982
1983   return latin1;
1984 }
1985
1986 int getTextEncoding(char *text)
1987 {
1988   unsigned char *src = (unsigned char *)text;
1989   int encoding = TEXT_ENCODING_ASCII;   // default: assume encoding is ASCII
1990
1991   while (*src)
1992   {
1993     if (*src >= 128)
1994       encoding = TEXT_ENCODING_UTF_8;   // non-ASCII character: assume UTF-8
1995
1996     if (*src < 128)
1997     {
1998       src++;
1999     }
2000     else if (src[0] >= 192 && src[0] < 224 &&
2001              src[1] >= 128 && src[1] < 192)
2002     {
2003       src += 2;
2004     }
2005     else if (src[0] >= 224 && src[0] < 240 &&
2006              src[1] >= 128 && src[1] < 192 &&
2007              src[2] >= 128 && src[2] < 192)
2008     {
2009       src += 3;
2010     }
2011     else if (src[0] >= 240 && src[0] < 248 &&
2012              src[1] >= 128 && src[1] < 192 &&
2013              src[2] >= 128 && src[2] < 192 &&
2014              src[3] >= 128 && src[3] < 192)
2015     {
2016       src += 4;
2017     }
2018     else if (src[0] >= 248 && src[0] < 252 &&
2019              src[1] >= 128 && src[1] < 192 &&
2020              src[2] >= 128 && src[2] < 192 &&
2021              src[3] >= 128 && src[3] < 192 &&
2022              src[4] >= 128 && src[4] < 192)
2023     {
2024       src += 5;
2025     }
2026     else if (src[0] >= 252 && src[0] < 254 &&
2027              src[1] >= 128 && src[1] < 192 &&
2028              src[2] >= 128 && src[2] < 192 &&
2029              src[3] >= 128 && src[3] < 192 &&
2030              src[4] >= 128 && src[4] < 192 &&
2031              src[5] >= 128 && src[5] < 192)
2032     {
2033       src += 6;
2034     }
2035     else
2036     {
2037       return TEXT_ENCODING_UNKNOWN;     // non-UTF-8 character: unknown encoding
2038     }
2039   }
2040
2041   return encoding;
2042 }
2043
2044
2045 // ----------------------------------------------------------------------------
2046 // functions for JSON handling
2047 // ----------------------------------------------------------------------------
2048
2049 char *getEscapedJSON(char *s)
2050 {
2051   int max_json_size = 2 * strlen(s) + 1;
2052   char *json = checked_calloc(max_json_size);
2053   unsigned char *src = (unsigned char *)s;
2054   unsigned char *dst = (unsigned char *)json;
2055   char *escaped[256] =
2056   {
2057     ['\b'] = "\\b",
2058     ['\f'] = "\\f",
2059     ['\n'] = "\\n",
2060     ['\r'] = "\\r",
2061     ['\t'] = "\\t",
2062     ['\"'] = "\\\"",
2063     ['\\'] = "\\\\",
2064   };
2065
2066   while (*src)
2067   {
2068     if (escaped[*src] != NULL)
2069     {
2070       char *esc = escaped[*src++];
2071
2072       while (*esc)
2073         *dst++ = *esc++;
2074     }
2075     else
2076     {
2077       *dst++ = *src++;
2078     }
2079   }
2080
2081   // only use the smallest possible string buffer size
2082   json = checked_realloc(json, strlen(json) + 1);
2083
2084   return json;
2085 }
2086
2087
2088 // ----------------------------------------------------------------------------
2089 // functions to translate key identifiers between different format
2090 // ----------------------------------------------------------------------------
2091
2092 #define TRANSLATE_KEYSYM_TO_KEYNAME     0
2093 #define TRANSLATE_KEYSYM_TO_X11KEYNAME  1
2094 #define TRANSLATE_KEYNAME_TO_KEYSYM     2
2095 #define TRANSLATE_X11KEYNAME_TO_KEYSYM  3
2096
2097 static void translate_keyname(Key *keysym, char **x11name, char **name, int mode)
2098 {
2099   static struct
2100   {
2101     Key key;
2102     char *x11name;
2103     char *name;
2104   } translate_key[] =
2105   {
2106     // return and escape keys
2107     { KSYM_Return,      "XK_Return",            "return" },
2108     { KSYM_Escape,      "XK_Escape",            "escape" },
2109
2110     // normal cursor keys
2111     { KSYM_Left,        "XK_Left",              "cursor left" },
2112     { KSYM_Right,       "XK_Right",             "cursor right" },
2113     { KSYM_Up,          "XK_Up",                "cursor up" },
2114     { KSYM_Down,        "XK_Down",              "cursor down" },
2115
2116     // keypad cursor keys
2117 #ifdef KSYM_KP_Left
2118     { KSYM_KP_Left,     "XK_KP_Left",           "keypad left" },
2119     { KSYM_KP_Right,    "XK_KP_Right",          "keypad right" },
2120     { KSYM_KP_Up,       "XK_KP_Up",             "keypad up" },
2121     { KSYM_KP_Down,     "XK_KP_Down",           "keypad down" },
2122 #endif
2123
2124     // other keypad keys
2125 #ifdef KSYM_KP_Enter
2126     { KSYM_KP_Enter,    "XK_KP_Enter",          "keypad enter" },
2127     { KSYM_KP_Add,      "XK_KP_Add",            "keypad +" },
2128     { KSYM_KP_Subtract, "XK_KP_Subtract",       "keypad -" },
2129     { KSYM_KP_Multiply, "XK_KP_Multiply",       "keypad mltply" },
2130     { KSYM_KP_Divide,   "XK_KP_Divide",         "keypad /" },
2131     { KSYM_KP_Separator,"XK_KP_Separator",      "keypad ," },
2132 #endif
2133
2134     // modifier keys
2135     { KSYM_Shift_L,     "XK_Shift_L",           "left shift" },
2136     { KSYM_Shift_R,     "XK_Shift_R",           "right shift" },
2137     { KSYM_Control_L,   "XK_Control_L",         "left control" },
2138     { KSYM_Control_R,   "XK_Control_R",         "right control" },
2139     { KSYM_Meta_L,      "XK_Meta_L",            "left meta" },
2140     { KSYM_Meta_R,      "XK_Meta_R",            "right meta" },
2141     { KSYM_Alt_L,       "XK_Alt_L",             "left alt" },
2142     { KSYM_Alt_R,       "XK_Alt_R",             "right alt" },
2143     { KSYM_Mode_switch, "XK_Mode_switch",       "mode switch" }, // Alt-R
2144     { KSYM_Multi_key,   "XK_Multi_key",         "multi key" },   // Ctrl-R
2145
2146     // some special keys
2147     { KSYM_BackSpace,   "XK_BackSpace",         "backspace" },
2148     { KSYM_Delete,      "XK_Delete",            "delete" },
2149     { KSYM_Insert,      "XK_Insert",            "insert" },
2150     { KSYM_Tab,         "XK_Tab",               "tab" },
2151     { KSYM_Home,        "XK_Home",              "home" },
2152     { KSYM_End,         "XK_End",               "end" },
2153     { KSYM_Page_Up,     "XK_Page_Up",           "page up" },
2154     { KSYM_Page_Down,   "XK_Page_Down",         "page down" },
2155
2156     { KSYM_Select,      "XK_Select",            "select" },
2157     { KSYM_Menu,        "XK_Menu",              "menu" },        // menu key
2158     { KSYM_Back,        "XK_Back",              "back" },        // back key
2159     { KSYM_PlayPause,   "XK_PlayPause",         "play/pause" },
2160 #if defined(PLATFORM_ANDROID)
2161     { KSYM_Rewind,      "XK_Rewind",            "rewind" },
2162     { KSYM_FastForward, "XK_FastForward",       "fast forward" },
2163 #endif
2164
2165     // ASCII 0x20 to 0x40 keys (except numbers)
2166     { KSYM_space,       "XK_space",             "space" },
2167     { KSYM_exclam,      "XK_exclam",            "!" },
2168     { KSYM_quotedbl,    "XK_quotedbl",          "\"" },
2169     { KSYM_numbersign,  "XK_numbersign",        "#" },
2170     { KSYM_dollar,      "XK_dollar",            "$" },
2171     { KSYM_percent,     "XK_percent",           "%" },
2172     { KSYM_ampersand,   "XK_ampersand",         "&" },
2173     { KSYM_apostrophe,  "XK_apostrophe",        "'" },
2174     { KSYM_parenleft,   "XK_parenleft",         "(" },
2175     { KSYM_parenright,  "XK_parenright",        ")" },
2176     { KSYM_asterisk,    "XK_asterisk",          "*" },
2177     { KSYM_plus,        "XK_plus",              "+" },
2178     { KSYM_comma,       "XK_comma",             "," },
2179     { KSYM_minus,       "XK_minus",             "-" },
2180     { KSYM_period,      "XK_period",            "." },
2181     { KSYM_slash,       "XK_slash",             "/" },
2182     { KSYM_colon,       "XK_colon",             ":" },
2183     { KSYM_semicolon,   "XK_semicolon",         ";" },
2184     { KSYM_less,        "XK_less",              "<" },
2185     { KSYM_equal,       "XK_equal",             "=" },
2186     { KSYM_greater,     "XK_greater",           ">" },
2187     { KSYM_question,    "XK_question",          "?" },
2188     { KSYM_at,          "XK_at",                "@" },
2189
2190     // more ASCII keys
2191     { KSYM_bracketleft, "XK_bracketleft",       "[" },
2192     { KSYM_backslash,   "XK_backslash",         "\\" },
2193     { KSYM_bracketright,"XK_bracketright",      "]" },
2194     { KSYM_asciicircum, "XK_asciicircum",       "^" },
2195     { KSYM_underscore,  "XK_underscore",        "_" },
2196     { KSYM_grave,       "XK_grave",             "grave" },
2197     { KSYM_quoteleft,   "XK_quoteleft",         "quote left" },
2198     { KSYM_braceleft,   "XK_braceleft",         "brace left" },
2199     { KSYM_bar,         "XK_bar",               "bar" },
2200     { KSYM_braceright,  "XK_braceright",        "brace right" },
2201     { KSYM_asciitilde,  "XK_asciitilde",        "~" },
2202
2203     // special (non-ASCII) keys
2204     { KSYM_degree,      "XK_degree",            "degree" },
2205     { KSYM_Adiaeresis,  "XK_Adiaeresis",        "A umlaut" },
2206     { KSYM_Odiaeresis,  "XK_Odiaeresis",        "O umlaut" },
2207     { KSYM_Udiaeresis,  "XK_Udiaeresis",        "U umlaut" },
2208     { KSYM_adiaeresis,  "XK_adiaeresis",        "a umlaut" },
2209     { KSYM_odiaeresis,  "XK_odiaeresis",        "o umlaut" },
2210     { KSYM_udiaeresis,  "XK_udiaeresis",        "u umlaut" },
2211     { KSYM_ssharp,      "XK_ssharp",            "sharp s" },
2212
2213     // special (non-ASCII) keys (UTF-8, for reverse mapping only)
2214     { KSYM_degree,      "XK_degree",            "\xc2\xb0" },
2215     { KSYM_Adiaeresis,  "XK_Adiaeresis",        "\xc3\x84" },
2216     { KSYM_Odiaeresis,  "XK_Odiaeresis",        "\xc3\x96" },
2217     { KSYM_Udiaeresis,  "XK_Udiaeresis",        "\xc3\x9c" },
2218     { KSYM_adiaeresis,  "XK_adiaeresis",        "\xc3\xa4" },
2219     { KSYM_odiaeresis,  "XK_odiaeresis",        "\xc3\xb6" },
2220     { KSYM_udiaeresis,  "XK_udiaeresis",        "\xc3\xbc" },
2221     { KSYM_ssharp,      "XK_ssharp",            "\xc3\x9f" },
2222
2223     // other keys (for reverse mapping only)
2224     { KSYM_space,       "XK_space",             " " },
2225
2226     // keypad keys are not in numerical order in SDL2
2227     { KSYM_KP_0,        "XK_KP_0",              "keypad 0" },
2228     { KSYM_KP_1,        "XK_KP_1",              "keypad 1" },
2229     { KSYM_KP_2,        "XK_KP_2",              "keypad 2" },
2230     { KSYM_KP_3,        "XK_KP_3",              "keypad 3" },
2231     { KSYM_KP_4,        "XK_KP_4",              "keypad 4" },
2232     { KSYM_KP_5,        "XK_KP_5",              "keypad 5" },
2233     { KSYM_KP_6,        "XK_KP_6",              "keypad 6" },
2234     { KSYM_KP_7,        "XK_KP_7",              "keypad 7" },
2235     { KSYM_KP_8,        "XK_KP_8",              "keypad 8" },
2236     { KSYM_KP_9,        "XK_KP_9",              "keypad 9" },
2237
2238     // end-of-array identifier
2239     { 0,                NULL,                   NULL }
2240   };
2241
2242   int i;
2243
2244   if (mode == TRANSLATE_KEYSYM_TO_KEYNAME)
2245   {
2246     static char name_buffer[30];
2247     Key key = *keysym;
2248
2249     if (key >= KSYM_A && key <= KSYM_Z)
2250       sprintf(name_buffer, "%c", 'A' + (char)(key - KSYM_A));
2251     else if (key >= KSYM_a && key <= KSYM_z)
2252       sprintf(name_buffer, "%c", 'a' + (char)(key - KSYM_a));
2253     else if (key >= KSYM_0 && key <= KSYM_9)
2254       sprintf(name_buffer, "%c", '0' + (char)(key - KSYM_0));
2255     else if (key >= KSYM_FKEY_FIRST && key <= KSYM_FKEY_LAST)
2256       sprintf(name_buffer, "F%d", (int)(key - KSYM_FKEY_FIRST + 1));
2257     else if (key == KSYM_UNDEFINED)
2258       strcpy(name_buffer, "(undefined)");
2259     else
2260     {
2261       i = 0;
2262
2263       do
2264       {
2265         if (key == translate_key[i].key)
2266         {
2267           strcpy(name_buffer, translate_key[i].name);
2268           break;
2269         }
2270       }
2271       while (translate_key[++i].name);
2272
2273       if (!translate_key[i].name)
2274         strcpy(name_buffer, "(unknown)");
2275     }
2276
2277     *name = name_buffer;
2278   }
2279   else if (mode == TRANSLATE_KEYSYM_TO_X11KEYNAME)
2280   {
2281     static char name_buffer[30];
2282     Key key = *keysym;
2283
2284     if (key >= KSYM_A && key <= KSYM_Z)
2285       sprintf(name_buffer, "XK_%c", 'A' + (char)(key - KSYM_A));
2286     else if (key >= KSYM_a && key <= KSYM_z)
2287       sprintf(name_buffer, "XK_%c", 'a' + (char)(key - KSYM_a));
2288     else if (key >= KSYM_0 && key <= KSYM_9)
2289       sprintf(name_buffer, "XK_%c", '0' + (char)(key - KSYM_0));
2290     else if (key >= KSYM_FKEY_FIRST && key <= KSYM_FKEY_LAST)
2291       sprintf(name_buffer, "XK_F%d", (int)(key - KSYM_FKEY_FIRST + 1));
2292     else if (key == KSYM_UNDEFINED)
2293       strcpy(name_buffer, "[undefined]");
2294     else
2295     {
2296       i = 0;
2297
2298       do
2299       {
2300         if (key == translate_key[i].key)
2301         {
2302           strcpy(name_buffer, translate_key[i].x11name);
2303           break;
2304         }
2305       }
2306       while (translate_key[++i].x11name);
2307
2308       if (!translate_key[i].x11name)
2309         sprintf(name_buffer, "0x%04x", (unsigned int)key);
2310     }
2311
2312     *x11name = name_buffer;
2313   }
2314   else if (mode == TRANSLATE_KEYNAME_TO_KEYSYM)
2315   {
2316     Key key = KSYM_UNDEFINED;
2317     char *name_ptr = *name;
2318
2319     if (strlen(*name) == 1)
2320     {
2321       char c = name_ptr[0];
2322
2323       if (c >= 'A' && c <= 'Z')
2324         key = KSYM_A + (Key)(c - 'A');
2325       else if (c >= 'a' && c <= 'z')
2326         key = KSYM_a + (Key)(c - 'a');
2327       else if (c >= '0' && c <= '9')
2328         key = KSYM_0 + (Key)(c - '0');
2329     }
2330
2331     if (key == KSYM_UNDEFINED)
2332     {
2333       i = 0;
2334
2335       do
2336       {
2337         if (strEqual(translate_key[i].name, *name))
2338         {
2339           key = translate_key[i].key;
2340           break;
2341         }
2342       }
2343       while (translate_key[++i].x11name);
2344     }
2345
2346     if (key == KSYM_UNDEFINED)
2347       Warn("getKeyFromKeyName(): not completely implemented");
2348
2349     *keysym = key;
2350   }
2351   else if (mode == TRANSLATE_X11KEYNAME_TO_KEYSYM)
2352   {
2353     Key key = KSYM_UNDEFINED;
2354     char *name_ptr = *x11name;
2355
2356     if (strPrefix(name_ptr, "XK_") && strlen(name_ptr) == 4)
2357     {
2358       char c = name_ptr[3];
2359
2360       if (c >= 'A' && c <= 'Z')
2361         key = KSYM_A + (Key)(c - 'A');
2362       else if (c >= 'a' && c <= 'z')
2363         key = KSYM_a + (Key)(c - 'a');
2364       else if (c >= '0' && c <= '9')
2365         key = KSYM_0 + (Key)(c - '0');
2366     }
2367     else if (strPrefix(name_ptr, "XK_F") && strlen(name_ptr) <= 6)
2368     {
2369       char c1 = name_ptr[4];
2370       char c2 = name_ptr[5];
2371       int d = 0;
2372
2373       if ((c1 >= '0' && c1 <= '9') &&
2374           ((c2 >= '0' && c1 <= '9') || c2 == '\0'))
2375         d = atoi(&name_ptr[4]);
2376
2377       if (d >= 1 && d <= KSYM_NUM_FKEYS)
2378         key = KSYM_F1 + (Key)(d - 1);
2379     }
2380     else if (strPrefix(name_ptr, "XK_"))
2381     {
2382       i = 0;
2383
2384       do
2385       {
2386         if (strEqual(name_ptr, translate_key[i].x11name))
2387         {
2388           key = translate_key[i].key;
2389           break;
2390         }
2391       }
2392       while (translate_key[++i].x11name);
2393     }
2394     else if (strPrefix(name_ptr, "0x"))
2395     {
2396       unsigned int value = 0;
2397
2398       name_ptr += 2;
2399
2400       while (name_ptr)
2401       {
2402         char c = *name_ptr++;
2403         int d = -1;
2404
2405         if (c >= '0' && c <= '9')
2406           d = (int)(c - '0');
2407         else if (c >= 'a' && c <= 'f')
2408           d = (int)(c - 'a' + 10);
2409         else if (c >= 'A' && c <= 'F')
2410           d = (int)(c - 'A' + 10);
2411
2412         if (d == -1)
2413         {
2414           value = -1;
2415           break;
2416         }
2417
2418         value = value * 16 + d;
2419       }
2420
2421       if (value != -1)
2422         key = (Key)value;
2423     }
2424
2425     *keysym = key;
2426   }
2427 }
2428
2429 char *getKeyNameFromKey(Key key)
2430 {
2431   char *name;
2432
2433   translate_keyname(&key, NULL, &name, TRANSLATE_KEYSYM_TO_KEYNAME);
2434   return name;
2435 }
2436
2437 char *getX11KeyNameFromKey(Key key)
2438 {
2439   char *x11name;
2440
2441   translate_keyname(&key, &x11name, NULL, TRANSLATE_KEYSYM_TO_X11KEYNAME);
2442   return x11name;
2443 }
2444
2445 Key getKeyFromKeyName(char *name)
2446 {
2447   Key key;
2448
2449   translate_keyname(&key, NULL, &name, TRANSLATE_KEYNAME_TO_KEYSYM);
2450   return key;
2451 }
2452
2453 Key getKeyFromX11KeyName(char *x11name)
2454 {
2455   Key key;
2456
2457   translate_keyname(&key, &x11name, NULL, TRANSLATE_X11KEYNAME_TO_KEYSYM);
2458   return key;
2459 }
2460
2461 char getCharFromKey(Key key)
2462 {
2463   static struct
2464   {
2465     Key key;
2466     byte key_char;
2467   } translate_key_char[] =
2468   {
2469     // special (non-ASCII) keys (ISO-8859-1)
2470     { KSYM_degree,      CHAR_BYTE_DEGREE        },
2471     { KSYM_Adiaeresis,  CHAR_BYTE_UMLAUT_A      },
2472     { KSYM_Odiaeresis,  CHAR_BYTE_UMLAUT_O      },
2473     { KSYM_Udiaeresis,  CHAR_BYTE_UMLAUT_U      },
2474     { KSYM_adiaeresis,  CHAR_BYTE_UMLAUT_a      },
2475     { KSYM_odiaeresis,  CHAR_BYTE_UMLAUT_o      },
2476     { KSYM_udiaeresis,  CHAR_BYTE_UMLAUT_u      },
2477     { KSYM_ssharp,      CHAR_BYTE_SHARP_S       },
2478
2479     // end-of-array identifier
2480     { 0,                0                       }
2481   };
2482
2483   char *keyname = getKeyNameFromKey(key);
2484   char c = 0;
2485
2486   if (strlen(keyname) == 1)
2487     c = keyname[0];
2488   else if (strEqual(keyname, "space"))
2489     c = ' ';
2490   else
2491   {
2492     int i = 0;
2493
2494     do
2495     {
2496       if (key == translate_key_char[i].key)
2497       {
2498         c = translate_key_char[i].key_char;
2499
2500         break;
2501       }
2502     }
2503     while (translate_key_char[++i].key_char);
2504   }
2505
2506   return c;
2507 }
2508
2509 char getValidConfigValueChar(char c)
2510 {
2511   if (c == '#' ||       // used to mark comments
2512       c == '\\')        // used to mark continued lines
2513     c = 0;
2514
2515   return c;
2516 }
2517
2518
2519 // ----------------------------------------------------------------------------
2520 // functions to translate string identifiers to integer or boolean value
2521 // ----------------------------------------------------------------------------
2522
2523 int get_integer_from_string(char *s)
2524 {
2525   // check for the most common case first
2526   if (s[0] >= '0' && s[0] <= '9')
2527     return atoi(s);
2528
2529   char *s_lower = getStringToLower(s);
2530   int result = -1;
2531
2532   if (strEqual(s_lower, "false") ||
2533       strEqual(s_lower, "no") ||
2534       strEqual(s_lower, "off"))
2535     result = 0;
2536   else if (strEqual(s_lower, "true") ||
2537            strEqual(s_lower, "yes") ||
2538            strEqual(s_lower, "on"))
2539     result = 1;
2540   else
2541     result = atoi(s);
2542
2543   free(s_lower);
2544
2545   return result;
2546 }
2547
2548 boolean get_boolean_from_string(char *s)
2549 {
2550   char *s_lower = getStringToLower(s);
2551   boolean result = FALSE;
2552
2553   if (strEqual(s_lower, "true") ||
2554       strEqual(s_lower, "yes") ||
2555       strEqual(s_lower, "on") ||
2556       get_integer_from_string(s) == 1)
2557     result = TRUE;
2558
2559   free(s_lower);
2560
2561   return result;
2562 }
2563
2564 int get_switch3_from_string(char *s)
2565 {
2566   char *s_lower = getStringToLower(s);
2567   int result = FALSE;
2568
2569   if (strEqual(s_lower, "true") ||
2570       strEqual(s_lower, "yes") ||
2571       strEqual(s_lower, "on") ||
2572       get_integer_from_string(s) == 1)
2573     result = TRUE;
2574   else if (strEqual(s_lower, "auto"))
2575     result = AUTO;
2576
2577   free(s_lower);
2578
2579   return result;
2580 }
2581
2582 int get_player_nr_from_string(char *s)
2583 {
2584   static char *player_text[] =
2585   {
2586     "player_1",
2587     "player_2",
2588     "player_3",
2589     "player_4",
2590
2591     NULL
2592   };
2593
2594   char *s_lower = getStringToLower(s);
2595   int result = 0;
2596   int i;
2597
2598   for (i = 0; player_text[i] != NULL; i++)
2599     if (strEqual(s_lower, player_text[i]))
2600       result = i;
2601
2602   free(s_lower);
2603
2604   return result;
2605 }
2606
2607
2608 // ----------------------------------------------------------------------------
2609 // functions for generic lists
2610 // ----------------------------------------------------------------------------
2611
2612 ListNode *newListNode(void)
2613 {
2614   return checked_calloc(sizeof(ListNode));
2615 }
2616
2617 void addNodeToList(ListNode **node_first, char *key, void *content)
2618 {
2619   ListNode *node_new = newListNode();
2620
2621   node_new->key = getStringCopy(key);
2622   node_new->content = content;
2623   node_new->next = *node_first;
2624
2625   if (*node_first)
2626     (*node_first)->prev = node_new;
2627
2628   *node_first = node_new;
2629 }
2630
2631 void deleteNodeFromList(ListNode **node_first, char *key,
2632                         void (*destructor_function)(void *))
2633 {
2634   if (node_first == NULL || *node_first == NULL)
2635     return;
2636
2637   if (strEqual((*node_first)->key, key))
2638   {
2639     // after first recursion, (*node_first)->prev->next == *node_first,
2640     // so *node_first would be overwritten with (*node_first)->next
2641     // => use a copy of *node_first (and later of (*node_first)->next)
2642     ListNode *node = *node_first;
2643     ListNode *node_next = node->next;
2644
2645     checked_free(node->key);
2646
2647     if (destructor_function)
2648       destructor_function(node->content);
2649
2650     if (node->prev)
2651       node->prev->next = node->next;
2652
2653     if (node->next)
2654       node->next->prev = node->prev;
2655
2656     checked_free(node);
2657
2658     // after removing node, set list pointer to next valid list node
2659     // (this is important if the first node of the list was deleted)
2660     *node_first = node_next;
2661   }
2662   else
2663   {
2664     deleteNodeFromList(&(*node_first)->next, key, destructor_function);
2665   }
2666 }
2667
2668 ListNode *getNodeFromKey(ListNode *node_first, char *key)
2669 {
2670   if (node_first == NULL)
2671     return NULL;
2672
2673   if (strEqual(node_first->key, key))
2674     return node_first;
2675   else
2676     return getNodeFromKey(node_first->next, key);
2677 }
2678
2679 int getNumNodes(ListNode *node_first)
2680 {
2681   return (node_first ? 1 + getNumNodes(node_first->next) : 0);
2682 }
2683
2684 #if 0
2685 static void dumpList(ListNode *node_first)
2686 {
2687   ListNode *node = node_first;
2688
2689   while (node)
2690   {
2691     Debug("internal:dumpList", "['%s' (%d)]", node->key,
2692           ((struct ListNodeInfo *)node->content)->num_references);
2693     node = node->next;
2694   }
2695
2696   Debug("internal:dumpList", "[%d nodes]", getNumNodes(node_first));
2697 }
2698 #endif
2699
2700
2701 // ----------------------------------------------------------------------------
2702 // functions for file handling
2703 // ----------------------------------------------------------------------------
2704
2705 #define MAX_BUFFER_SIZE                 4096
2706
2707 File *openFile(char *filename, char *mode)
2708 {
2709   File *file = checked_calloc(sizeof(File));
2710
2711   file->file = fopen(filename, mode);
2712
2713   if (file->file != NULL)
2714   {
2715     file->filename = getStringCopy(filename);
2716
2717     return file;
2718   }
2719
2720 #if defined(PLATFORM_ANDROID)
2721   file->asset_file = SDL_RWFromFile(filename, mode);
2722
2723   if (file->asset_file != NULL)
2724   {
2725     file->file_is_asset = TRUE;
2726     file->filename = getStringCopy(filename);
2727
2728     return file;
2729   }
2730 #endif
2731
2732   checked_free(file);
2733
2734   return NULL;
2735 }
2736
2737 int closeFile(File *file)
2738 {
2739   if (file == NULL)
2740     return -1;
2741
2742   int result = 0;
2743
2744 #if defined(PLATFORM_ANDROID)
2745   if (file->asset_file)
2746     result = SDL_RWclose(file->asset_file);
2747 #endif
2748
2749   if (file->file)
2750     result = fclose(file->file);
2751
2752   checked_free(file->filename);
2753   checked_free(file);
2754
2755   return result;
2756 }
2757
2758 int checkEndOfFile(File *file)
2759 {
2760 #if defined(PLATFORM_ANDROID)
2761   if (file->file_is_asset)
2762     return file->end_of_file;
2763 #endif
2764
2765   return feof(file->file);
2766 }
2767
2768 size_t readFile(File *file, void *buffer, size_t item_size, size_t num_items)
2769 {
2770 #if defined(PLATFORM_ANDROID)
2771   if (file->file_is_asset)
2772   {
2773     if (file->end_of_file)
2774       return 0;
2775
2776     size_t num_items_read =
2777       SDL_RWread(file->asset_file, buffer, item_size, num_items);
2778
2779     if (num_items_read < num_items)
2780       file->end_of_file = TRUE;
2781
2782     return num_items_read;
2783   }
2784 #endif
2785
2786   return fread(buffer, item_size, num_items, file->file);
2787 }
2788
2789 size_t writeFile(File *file, void *buffer, size_t item_size, size_t num_items)
2790 {
2791   return fwrite(buffer, item_size, num_items, file->file);
2792 }
2793
2794 int seekFile(File *file, long offset, int whence)
2795 {
2796 #if defined(PLATFORM_ANDROID)
2797   if (file->file_is_asset)
2798   {
2799     int sdl_whence = (whence == SEEK_SET ? RW_SEEK_SET :
2800                       whence == SEEK_CUR ? RW_SEEK_CUR :
2801                       whence == SEEK_END ? RW_SEEK_END : 0);
2802
2803     return (SDL_RWseek(file->asset_file, offset, sdl_whence) == -1 ? -1 : 0);
2804   }
2805 #endif
2806
2807   return fseek(file->file, offset, whence);
2808 }
2809
2810 int getByteFromFile(File *file)
2811 {
2812 #if defined(PLATFORM_ANDROID)
2813   if (file->file_is_asset)
2814   {
2815     if (file->end_of_file)
2816       return EOF;
2817
2818     byte c;
2819     size_t num_bytes_read = SDL_RWread(file->asset_file, &c, 1, 1);
2820
2821     if (num_bytes_read < 1)
2822       file->end_of_file = TRUE;
2823
2824     return (file->end_of_file ? EOF : (int)c);
2825   }
2826 #endif
2827
2828   return fgetc(file->file);
2829 }
2830
2831 char *getStringFromFile(File *file, char *line, int size)
2832 {
2833 #if defined(PLATFORM_ANDROID)
2834   if (file->file_is_asset)
2835   {
2836     if (file->end_of_file)
2837       return NULL;
2838
2839     char *line_ptr = line;
2840     int num_bytes_read = 0;
2841
2842     while (num_bytes_read < size - 1 &&
2843            SDL_RWread(file->asset_file, line_ptr, 1, 1) == 1 &&
2844            *line_ptr++ != '\n')
2845       num_bytes_read++;
2846
2847     *line_ptr = '\0';
2848
2849     if (strlen(line) == 0)
2850     {
2851       file->end_of_file = TRUE;
2852
2853       return NULL;
2854     }
2855
2856     return line;
2857   }
2858 #endif
2859
2860   return fgets(line, size, file->file);
2861 }
2862
2863 int copyFile(char *filename_from, char *filename_to)
2864 {
2865   File *file_from, *file_to;
2866
2867   if ((file_from = openFile(filename_from, MODE_READ)) == NULL)
2868   {
2869     return -1;
2870   }
2871
2872   if ((file_to = openFile(filename_to, MODE_WRITE)) == NULL)
2873   {
2874     closeFile(file_from);
2875
2876     return -1;
2877   }
2878
2879   while (!checkEndOfFile(file_from))
2880   {
2881     byte buffer[MAX_BUFFER_SIZE];
2882     size_t bytes_read = readFile(file_from, buffer, 1, MAX_BUFFER_SIZE);
2883
2884     writeFile(file_to, buffer, 1, bytes_read);
2885   }
2886
2887   closeFile(file_from);
2888   closeFile(file_to);
2889
2890   return 0;
2891 }
2892
2893 boolean touchFile(char *filename)
2894 {
2895   FILE *file;
2896
2897   if (!(file = fopen(filename, MODE_WRITE)))
2898   {
2899     Warn("cannot touch file '%s'", filename);
2900
2901     return FALSE;
2902   }
2903
2904   fclose(file);
2905
2906   return TRUE;
2907 }
2908
2909
2910 // ----------------------------------------------------------------------------
2911 // functions for directory handling
2912 // ----------------------------------------------------------------------------
2913
2914 Directory *openDirectory(char *dir_name)
2915 {
2916   Directory *dir = checked_calloc(sizeof(Directory));
2917
2918   dir->dir = opendir(dir_name);
2919
2920   if (dir->dir != NULL)
2921   {
2922     dir->filename = getStringCopy(dir_name);
2923
2924     return dir;
2925   }
2926
2927 #if defined(PLATFORM_ANDROID)
2928   char *asset_toc_filename = getPath2(dir_name, ASSET_TOC_BASENAME);
2929
2930   dir->asset_toc_file = SDL_RWFromFile(asset_toc_filename, MODE_READ);
2931
2932   checked_free(asset_toc_filename);
2933
2934   if (dir->asset_toc_file != NULL)
2935   {
2936     dir->directory_is_asset = TRUE;
2937     dir->filename = getStringCopy(dir_name);
2938
2939     return dir;
2940   }
2941 #endif
2942
2943   checked_free(dir);
2944
2945   return NULL;
2946 }
2947
2948 int closeDirectory(Directory *dir)
2949 {
2950   if (dir == NULL)
2951     return -1;
2952
2953   int result = 0;
2954
2955 #if defined(PLATFORM_ANDROID)
2956   if (dir->asset_toc_file)
2957     result = SDL_RWclose(dir->asset_toc_file);
2958 #endif
2959
2960   if (dir->dir)
2961     result = closedir(dir->dir);
2962
2963   if (dir->dir_entry)
2964     freeDirectoryEntry(dir->dir_entry);
2965
2966   checked_free(dir->filename);
2967   checked_free(dir);
2968
2969   return result;
2970 }
2971
2972 DirectoryEntry *readDirectory(Directory *dir)
2973 {
2974   if (dir->dir_entry)
2975     freeDirectoryEntry(dir->dir_entry);
2976
2977   dir->dir_entry = NULL;
2978
2979 #if defined(PLATFORM_ANDROID)
2980   if (dir->directory_is_asset)
2981   {
2982     char line[MAX_LINE_LEN];
2983     char *line_ptr = line;
2984     int num_bytes_read = 0;
2985
2986     while (num_bytes_read < MAX_LINE_LEN - 1 &&
2987            SDL_RWread(dir->asset_toc_file, line_ptr, 1, 1) == 1 &&
2988            *line_ptr != '\n')
2989     {
2990       line_ptr++;
2991       num_bytes_read++;
2992     }
2993
2994     *line_ptr = '\0';
2995
2996     if (strlen(line) == 0)
2997       return NULL;
2998
2999     dir->dir_entry = checked_calloc(sizeof(DirectoryEntry));
3000
3001     dir->dir_entry->is_directory = FALSE;
3002     if (line[strlen(line) - 1] == '/')
3003     {
3004       dir->dir_entry->is_directory = TRUE;
3005
3006       line[strlen(line) - 1] = '\0';
3007     }
3008
3009     dir->dir_entry->basename = getStringCopy(line);
3010     dir->dir_entry->filename = getPath2(dir->filename, line);
3011
3012     return dir->dir_entry;
3013   }
3014 #endif
3015
3016   struct dirent *dir_entry = readdir(dir->dir);
3017
3018   if (dir_entry == NULL)
3019     return NULL;
3020
3021   dir->dir_entry = checked_calloc(sizeof(DirectoryEntry));
3022
3023   dir->dir_entry->basename = getStringCopy(dir_entry->d_name);
3024   dir->dir_entry->filename = getPath2(dir->filename, dir_entry->d_name);
3025
3026   struct stat file_status;
3027
3028   dir->dir_entry->is_directory =
3029     (stat(dir->dir_entry->filename, &file_status) == 0 &&
3030      S_ISDIR(file_status.st_mode));
3031
3032   return dir->dir_entry;
3033 }
3034
3035 void freeDirectoryEntry(DirectoryEntry *dir_entry)
3036 {
3037   if (dir_entry == NULL)
3038     return;
3039
3040   checked_free(dir_entry->basename);
3041   checked_free(dir_entry->filename);
3042   checked_free(dir_entry);
3043 }
3044
3045
3046 // ----------------------------------------------------------------------------
3047 // functions for checking files and filenames
3048 // ----------------------------------------------------------------------------
3049
3050 boolean directoryExists(char *dir_name)
3051 {
3052   if (dir_name == NULL)
3053     return FALSE;
3054
3055   struct stat file_status;
3056   boolean success = (stat(dir_name, &file_status) == 0 &&
3057                      S_ISDIR(file_status.st_mode));
3058
3059 #if defined(PLATFORM_ANDROID)
3060   if (!success)
3061   {
3062     // this might be an asset directory; check by trying to open toc file
3063     char *asset_toc_filename = getPath2(dir_name, ASSET_TOC_BASENAME);
3064     SDL_RWops *file = SDL_RWFromFile(asset_toc_filename, MODE_READ);
3065
3066     checked_free(asset_toc_filename);
3067
3068     success = (file != NULL);
3069
3070     if (success)
3071       SDL_RWclose(file);
3072   }
3073 #endif
3074
3075   return success;
3076 }
3077
3078 boolean fileExists(char *filename)
3079 {
3080   if (filename == NULL)
3081     return FALSE;
3082
3083   boolean success = (access(filename, F_OK) == 0);
3084
3085 #if defined(PLATFORM_ANDROID)
3086   if (!success)
3087   {
3088     // this might be an asset file; check by trying to open it
3089     SDL_RWops *file = SDL_RWFromFile(filename, MODE_READ);
3090
3091     success = (file != NULL);
3092
3093     if (success)
3094       SDL_RWclose(file);
3095   }
3096 #endif
3097
3098   return success;
3099 }
3100
3101 #if 0
3102 static boolean fileHasPrefix(char *basename, char *prefix)
3103 {
3104   static char *basename_lower = NULL;
3105   int basename_length, prefix_length;
3106
3107   checked_free(basename_lower);
3108
3109   if (basename == NULL || prefix == NULL)
3110     return FALSE;
3111
3112   basename_lower = getStringToLower(basename);
3113   basename_length = strlen(basename_lower);
3114   prefix_length = strlen(prefix);
3115
3116   if (basename_length > prefix_length + 1 &&
3117       basename_lower[prefix_length] == '.' &&
3118       strncmp(basename_lower, prefix, prefix_length) == 0)
3119     return TRUE;
3120
3121   return FALSE;
3122 }
3123 #endif
3124
3125 static boolean fileHasSuffix(char *basename, char *suffix)
3126 {
3127   static char *basename_lower = NULL;
3128   int basename_length, suffix_length;
3129
3130   checked_free(basename_lower);
3131
3132   if (basename == NULL || suffix == NULL)
3133     return FALSE;
3134
3135   basename_lower = getStringToLower(basename);
3136   basename_length = strlen(basename_lower);
3137   suffix_length = strlen(suffix);
3138
3139   if (basename_length > suffix_length + 1 &&
3140       basename_lower[basename_length - suffix_length - 1] == '.' &&
3141       strEqual(&basename_lower[basename_length - suffix_length], suffix))
3142     return TRUE;
3143
3144   return FALSE;
3145 }
3146
3147 static boolean FileCouldBeArtwork(char *filename)
3148 {
3149   char *basename = getBaseNamePtr(filename);
3150
3151   return (!strEqual(basename, ".") &&
3152           !strEqual(basename, "..") &&
3153           !fileHasSuffix(basename, "txt") &&
3154           !fileHasSuffix(basename, "conf") &&
3155           !directoryExists(filename));
3156 }
3157
3158 boolean FileIsGraphic(char *filename)
3159 {
3160   return FileCouldBeArtwork(filename);
3161 }
3162
3163 boolean FileIsSound(char *filename)
3164 {
3165   return FileCouldBeArtwork(filename);
3166 }
3167
3168 boolean FileIsMusic(char *filename)
3169 {
3170   return FileCouldBeArtwork(filename);
3171 }
3172
3173 boolean FileIsArtworkType(char *filename, int type)
3174 {
3175   if ((type == TREE_TYPE_GRAPHICS_DIR && FileIsGraphic(filename)) ||
3176       (type == TREE_TYPE_SOUNDS_DIR && FileIsSound(filename)) ||
3177       (type == TREE_TYPE_MUSIC_DIR && FileIsMusic(filename)))
3178     return TRUE;
3179
3180   return FALSE;
3181 }
3182
3183 // ----------------------------------------------------------------------------
3184 // functions for loading artwork configuration information
3185 // ----------------------------------------------------------------------------
3186
3187 char *get_mapped_token(char *token)
3188 {
3189   // !!! make this dynamically configurable (init.c:InitArtworkConfig) !!!
3190   static char *map_token_prefix[][2] =
3191   {
3192     { "char_procent",           "char_percent"                  },
3193     { "bd_magic_wall_filling",  "bd_magic_wall.filling"         },
3194     { "bd_magic_wall_emptying", "bd_magic_wall.emptying"        },
3195     { "bd_butterfly_left",      "bd_butterfly.left"             },
3196     { "bd_butterfly_right",     "bd_butterfly.right"            },
3197     { "bd_butterfly_up",        "bd_butterfly.up"               },
3198     { "bd_butterfly_down",      "bd_butterfly.down"             },
3199     { "bd_firefly_left",        "bd_firefly.left"               },
3200     { "bd_firefly_right",       "bd_firefly.right"              },
3201     { "bd_firefly_up",          "bd_firefly.up"                 },
3202     { "bd_firefly_down",        "bd_firefly.down"               },
3203
3204     { NULL,                                                     }
3205   };
3206   int i;
3207
3208   for (i = 0; map_token_prefix[i][0] != NULL; i++)
3209   {
3210     int len_token_prefix = strlen(map_token_prefix[i][0]);
3211
3212     if (strncmp(token, map_token_prefix[i][0], len_token_prefix) == 0)
3213       return getStringCat2(map_token_prefix[i][1], &token[len_token_prefix]);
3214   }
3215
3216   // change tokens containing ".gfx" by moving the "gfx" part to the very left
3217   char *gfx_substring = ".gfx";
3218   char *gfx_prefix = "gfx.";
3219   if (strstr(token, gfx_substring) != NULL)
3220   {
3221     char *token_prefix = getStringCopy(token);
3222     char *token_gfx_pos = strstr(token_prefix, gfx_substring);
3223     char *token_suffix = &token_gfx_pos[strlen(gfx_substring)];
3224     char *mapped_token;
3225
3226     // cut off token string at ".gfx" substring position
3227     *token_gfx_pos = '\0';
3228
3229     // put together prefix "gfx." and token prefix and suffix without ".gfx"
3230     mapped_token = getStringCat3(gfx_prefix, token_prefix, token_suffix);
3231
3232     free(token_prefix);
3233
3234     return mapped_token;
3235   }
3236
3237   return NULL;
3238 }
3239
3240 static char *get_special_base_token(struct ArtworkListInfo *artwork_info,
3241                                     char *token)
3242 {
3243   // !!! make this dynamically configurable (init.c:InitArtworkConfig) !!!
3244   static struct ConfigTypeInfo prefix_list[] =
3245   {
3246     { "global.anim_1"   },
3247     { "global.anim_2"   },
3248     { "global.anim_3"   },
3249     { "global.anim_4"   },
3250     { "global.anim_5"   },
3251     { "global.anim_6"   },
3252     { "global.anim_7"   },
3253     { "global.anim_8"   },
3254     { "global.anim_9"   },
3255     { "global.anim_10"  },
3256     { "global.anim_11"  },
3257     { "global.anim_12"  },
3258     { "global.anim_13"  },
3259     { "global.anim_14"  },
3260     { "global.anim_15"  },
3261     { "global.anim_16"  },
3262     { "global.anim_17"  },
3263     { "global.anim_18"  },
3264     { "global.anim_19"  },
3265     { "global.anim_20"  },
3266     { "global.anim_21"  },
3267     { "global.anim_22"  },
3268     { "global.anim_23"  },
3269     { "global.anim_24"  },
3270     { "global.anim_25"  },
3271     { "global.anim_26"  },
3272     { "global.anim_27"  },
3273     { "global.anim_28"  },
3274     { "global.anim_29"  },
3275     { "global.anim_30"  },
3276     { "global.anim_31"  },
3277     { "global.anim_32"  },
3278
3279     { NULL              }
3280   };
3281   struct ConfigTypeInfo *suffix_list = artwork_info->suffix_list;
3282   boolean prefix_found = FALSE;
3283   int len_suffix = 0;
3284   int i;
3285
3286   // search for prefix to check if base token has to be created
3287   for (i = 0; prefix_list[i].token != NULL; i++)
3288     if (strPrefix(token, prefix_list[i].token))
3289       prefix_found = TRUE;
3290
3291   if (!prefix_found)
3292     return NULL;
3293
3294   // search for suffix (parameter) to determine base token length
3295   for (i = 0; suffix_list[i].token != NULL; i++)
3296     if (strSuffix(token, suffix_list[i].token))
3297       len_suffix = strlen(suffix_list[i].token);
3298
3299   return getStringCopyN(token, strlen(token) - len_suffix);
3300 }
3301
3302 static void FreeCustomArtworkList(struct ArtworkListInfo *,
3303                                   struct ListNodeInfo ***, int *);
3304
3305 struct FileInfo *getFileListFromConfigList(struct ConfigInfo *config_list,
3306                                            struct ConfigTypeInfo *suffix_list,
3307                                            char **ignore_tokens,
3308                                            int num_file_list_entries)
3309 {
3310   SetupFileHash *ignore_tokens_hash;
3311   struct FileInfo *file_list;
3312   int num_file_list_entries_found = 0;
3313   int num_suffix_list_entries = 0;
3314   int list_pos;
3315   int i, j;
3316
3317   // create hash from list of tokens to be ignored (for quick access)
3318   ignore_tokens_hash = newSetupFileHash();
3319   for (i = 0; ignore_tokens[i] != NULL; i++)
3320     setHashEntry(ignore_tokens_hash, ignore_tokens[i], "");
3321
3322   file_list = checked_calloc(num_file_list_entries * sizeof(struct FileInfo));
3323
3324   for (i = 0; suffix_list[i].token != NULL; i++)
3325     num_suffix_list_entries++;
3326
3327   // always start with reliable default values
3328   for (i = 0; i < num_file_list_entries; i++)
3329   {
3330     file_list[i].token = NULL;
3331
3332     file_list[i].default_filename = NULL;
3333     file_list[i].filename = NULL;
3334
3335     if (num_suffix_list_entries > 0)
3336     {
3337       int parameter_array_size = num_suffix_list_entries * sizeof(char *);
3338
3339       file_list[i].default_parameter = checked_calloc(parameter_array_size);
3340       file_list[i].parameter = checked_calloc(parameter_array_size);
3341
3342       for (j = 0; j < num_suffix_list_entries; j++)
3343       {
3344         setString(&file_list[i].default_parameter[j], suffix_list[j].value);
3345         setString(&file_list[i].parameter[j], suffix_list[j].value);
3346       }
3347
3348       file_list[i].redefined = FALSE;
3349       file_list[i].fallback_to_default = FALSE;
3350       file_list[i].default_is_cloned = FALSE;
3351     }
3352   }
3353
3354   list_pos = 0;
3355
3356   for (i = 0; config_list[i].token != NULL; i++)
3357   {
3358     int len_config_token = strlen(config_list[i].token);
3359     boolean is_file_entry = TRUE;
3360
3361     for (j = 0; suffix_list[j].token != NULL; j++)
3362     {
3363       int len_suffix = strlen(suffix_list[j].token);
3364
3365       if (len_suffix < len_config_token &&
3366           strEqual(&config_list[i].token[len_config_token - len_suffix],
3367                    suffix_list[j].token))
3368       {
3369         setString(&file_list[list_pos].default_parameter[j],
3370                   config_list[i].value);
3371
3372         is_file_entry = FALSE;
3373
3374         break;
3375       }
3376     }
3377
3378     // the following tokens are no file definitions, but other config tokens
3379     if (getHashEntry(ignore_tokens_hash, config_list[i].token) != NULL)
3380       is_file_entry = FALSE;
3381
3382     if (is_file_entry)
3383     {
3384       if (i > 0)
3385         list_pos++;
3386
3387       if (list_pos >= num_file_list_entries)
3388         break;
3389
3390       file_list[list_pos].token = config_list[i].token;
3391       file_list[list_pos].default_filename = config_list[i].value;
3392     }
3393
3394     if (strSuffix(config_list[i].token, ".clone_from"))
3395       file_list[list_pos].default_is_cloned = TRUE;
3396   }
3397
3398   num_file_list_entries_found = list_pos + 1;
3399   if (num_file_list_entries_found != num_file_list_entries)
3400   {
3401     Error("---");
3402     Error("inconsistant config list information:");
3403     Error("- should be:   %d (according to 'src/conf_xxx.h')",
3404           num_file_list_entries);
3405     Error("- found to be: %d (according to 'src/conf_xxx.c')",
3406           num_file_list_entries_found);
3407
3408     Fail("please fix");
3409   }
3410
3411   freeSetupFileHash(ignore_tokens_hash);
3412
3413   return file_list;
3414 }
3415
3416 static boolean token_suffix_match(char *token, char *suffix, int start_pos)
3417 {
3418   int len_token = strlen(token);
3419   int len_suffix = strlen(suffix);
3420
3421   if (start_pos < 0)    // compare suffix from end of string
3422     start_pos += len_token;
3423
3424   if (start_pos < 0 || start_pos + len_suffix > len_token)
3425     return FALSE;
3426
3427   if (strncmp(&token[start_pos], suffix, len_suffix) != 0)
3428     return FALSE;
3429
3430   if (token[start_pos + len_suffix] == '\0')
3431     return TRUE;
3432
3433   if (token[start_pos + len_suffix] == '.')
3434     return TRUE;
3435
3436   return FALSE;
3437 }
3438
3439 #define KNOWN_TOKEN_VALUE       "[KNOWN_TOKEN_VALUE]"
3440
3441 static void read_token_parameters(SetupFileHash *setup_file_hash,
3442                                   struct ConfigTypeInfo *suffix_list,
3443                                   struct FileInfo *file_list_entry)
3444 {
3445   // check for config token that is the base token without any suffixes
3446   char *filename = getHashEntry(setup_file_hash, file_list_entry->token);
3447   char *known_token_value = KNOWN_TOKEN_VALUE;
3448   int i;
3449
3450   if (filename != NULL)
3451   {
3452     setString(&file_list_entry->filename, filename);
3453
3454     // when file definition found, set all parameters to default values
3455     for (i = 0; suffix_list[i].token != NULL; i++)
3456       setString(&file_list_entry->parameter[i], suffix_list[i].value);
3457
3458     file_list_entry->redefined = TRUE;
3459
3460     // mark config file token as well known from default config
3461     setHashEntry(setup_file_hash, file_list_entry->token, known_token_value);
3462   }
3463
3464   // check for config tokens that can be build by base token and suffixes
3465   for (i = 0; suffix_list[i].token != NULL; i++)
3466   {
3467     char *token = getStringCat2(file_list_entry->token, suffix_list[i].token);
3468     char *value = getHashEntry(setup_file_hash, token);
3469
3470     if (value != NULL)
3471     {
3472       setString(&file_list_entry->parameter[i], value);
3473
3474       // mark config file token as well known from default config
3475       setHashEntry(setup_file_hash, token, known_token_value);
3476     }
3477
3478     free(token);
3479   }
3480 }
3481
3482 static void add_dynamic_file_list_entry(struct FileInfo **list,
3483                                         int *num_list_entries,
3484                                         SetupFileHash *extra_file_hash,
3485                                         struct ConfigTypeInfo *suffix_list,
3486                                         int num_suffix_list_entries,
3487                                         char *token)
3488 {
3489   struct FileInfo *new_list_entry;
3490   int parameter_array_size = num_suffix_list_entries * sizeof(char *);
3491
3492   (*num_list_entries)++;
3493   *list = checked_realloc(*list, *num_list_entries * sizeof(struct FileInfo));
3494   new_list_entry = &(*list)[*num_list_entries - 1];
3495
3496   new_list_entry->token = getStringCopy(token);
3497   new_list_entry->default_filename = NULL;
3498   new_list_entry->filename = NULL;
3499   new_list_entry->parameter = checked_calloc(parameter_array_size);
3500
3501   new_list_entry->redefined = FALSE;
3502   new_list_entry->fallback_to_default = FALSE;
3503   new_list_entry->default_is_cloned = FALSE;
3504
3505   read_token_parameters(extra_file_hash, suffix_list, new_list_entry);
3506 }
3507
3508 static void add_property_mapping(struct PropertyMapping **list,
3509                                  int *num_list_entries,
3510                                  int base_index, int ext1_index,
3511                                  int ext2_index, int ext3_index,
3512                                  int artwork_index)
3513 {
3514   struct PropertyMapping *new_list_entry;
3515
3516   (*num_list_entries)++;
3517   *list = checked_realloc(*list,
3518                           *num_list_entries * sizeof(struct PropertyMapping));
3519   new_list_entry = &(*list)[*num_list_entries - 1];
3520
3521   new_list_entry->base_index = base_index;
3522   new_list_entry->ext1_index = ext1_index;
3523   new_list_entry->ext2_index = ext2_index;
3524   new_list_entry->ext3_index = ext3_index;
3525
3526   new_list_entry->artwork_index = artwork_index;
3527 }
3528
3529 static void LoadArtworkConfigFromFilename(struct ArtworkListInfo *artwork_info,
3530                                           char *filename)
3531 {
3532   struct FileInfo *file_list = artwork_info->file_list;
3533   struct ConfigTypeInfo *suffix_list = artwork_info->suffix_list;
3534   char **base_prefixes = artwork_info->base_prefixes;
3535   char **ext1_suffixes = artwork_info->ext1_suffixes;
3536   char **ext2_suffixes = artwork_info->ext2_suffixes;
3537   char **ext3_suffixes = artwork_info->ext3_suffixes;
3538   char **ignore_tokens = artwork_info->ignore_tokens;
3539   int num_file_list_entries = artwork_info->num_file_list_entries;
3540   int num_suffix_list_entries = artwork_info->num_suffix_list_entries;
3541   int num_base_prefixes = artwork_info->num_base_prefixes;
3542   int num_ext1_suffixes = artwork_info->num_ext1_suffixes;
3543   int num_ext2_suffixes = artwork_info->num_ext2_suffixes;
3544   int num_ext3_suffixes = artwork_info->num_ext3_suffixes;
3545   int num_ignore_tokens = artwork_info->num_ignore_tokens;
3546   SetupFileHash *setup_file_hash, *valid_file_hash, *valid_file_hash_tmp;
3547   SetupFileHash *extra_file_hash, *empty_file_hash;
3548   char *known_token_value = KNOWN_TOKEN_VALUE;
3549   char *base_token_value = UNDEFINED_FILENAME;
3550   int i, j, k, l;
3551
3552   if (filename == NULL)
3553     return;
3554
3555   if ((setup_file_hash = loadSetupFileHash(filename)) == NULL)
3556     return;
3557
3558   // separate valid (defined) from empty (undefined) config token values
3559   valid_file_hash = newSetupFileHash();
3560   empty_file_hash = newSetupFileHash();
3561   BEGIN_HASH_ITERATION(setup_file_hash, itr)
3562   {
3563     char *value = HASH_ITERATION_VALUE(itr);
3564
3565     setHashEntry(*value ? valid_file_hash : empty_file_hash,
3566                  HASH_ITERATION_TOKEN(itr), value);
3567   }
3568   END_HASH_ITERATION(setup_file_hash, itr)
3569
3570   // at this point, we do not need the setup file hash anymore -- free it
3571   freeSetupFileHash(setup_file_hash);
3572
3573   // prevent changing hash while iterating over it by using a temporary copy
3574   valid_file_hash_tmp = newSetupFileHash();
3575   BEGIN_HASH_ITERATION(valid_file_hash, itr)
3576   {
3577     setHashEntry(valid_file_hash_tmp,
3578                  HASH_ITERATION_TOKEN(itr),
3579                  HASH_ITERATION_VALUE(itr));
3580   }
3581   END_HASH_ITERATION(valid_file_hash, itr)
3582
3583   // (iterate over same temporary hash, as modifications are independent)
3584
3585   // map deprecated to current tokens (using prefix match and replace)
3586   BEGIN_HASH_ITERATION(valid_file_hash_tmp, itr)
3587   {
3588     char *token = HASH_ITERATION_TOKEN(itr);
3589     char *mapped_token = get_mapped_token(token);
3590
3591     if (mapped_token != NULL)
3592     {
3593       char *value = HASH_ITERATION_VALUE(itr);
3594
3595       // add mapped token
3596       setHashEntry(valid_file_hash, mapped_token, value);
3597
3598       // ignore old token (by setting it to "known" keyword)
3599       setHashEntry(valid_file_hash, token, known_token_value);
3600
3601       free(mapped_token);
3602     }
3603   }
3604   END_HASH_ITERATION(valid_file_hash_tmp, itr)
3605
3606   // add special base tokens (using prefix match and replace)
3607   BEGIN_HASH_ITERATION(valid_file_hash_tmp, itr)
3608   {
3609     char *token = HASH_ITERATION_TOKEN(itr);
3610     char *base_token = get_special_base_token(artwork_info, token);
3611
3612     if (base_token != NULL)
3613     {
3614       // add base token only if it does not already exist
3615       if (getHashEntry(valid_file_hash, base_token) == NULL)
3616         setHashEntry(valid_file_hash, base_token, base_token_value);
3617
3618       free(base_token);
3619     }
3620   }
3621   END_HASH_ITERATION(valid_file_hash_tmp, itr)
3622
3623   // free temporary hash used for iteration
3624   freeSetupFileHash(valid_file_hash_tmp);
3625
3626   // read parameters for all known config file tokens
3627   for (i = 0; i < num_file_list_entries; i++)
3628     read_token_parameters(valid_file_hash, suffix_list, &file_list[i]);
3629
3630   // set all tokens that can be ignored here to "known" keyword
3631   for (i = 0; i < num_ignore_tokens; i++)
3632     setHashEntry(valid_file_hash, ignore_tokens[i], known_token_value);
3633
3634   // copy all unknown config file tokens to extra config hash
3635   extra_file_hash = newSetupFileHash();
3636   BEGIN_HASH_ITERATION(valid_file_hash, itr)
3637   {
3638     char *value = HASH_ITERATION_VALUE(itr);
3639
3640     if (!strEqual(value, known_token_value))
3641       setHashEntry(extra_file_hash, HASH_ITERATION_TOKEN(itr), value);
3642   }
3643   END_HASH_ITERATION(valid_file_hash, itr)
3644
3645   // at this point, we do not need the valid file hash anymore -- free it
3646   freeSetupFileHash(valid_file_hash);
3647
3648   // now try to determine valid, dynamically defined config tokens
3649
3650   BEGIN_HASH_ITERATION(extra_file_hash, itr)
3651   {
3652     struct FileInfo **dynamic_file_list =
3653       &artwork_info->dynamic_file_list;
3654     int *num_dynamic_file_list_entries =
3655       &artwork_info->num_dynamic_file_list_entries;
3656     struct PropertyMapping **property_mapping =
3657       &artwork_info->property_mapping;
3658     int *num_property_mapping_entries =
3659       &artwork_info->num_property_mapping_entries;
3660     int current_summarized_file_list_entry =
3661       artwork_info->num_file_list_entries +
3662       artwork_info->num_dynamic_file_list_entries;
3663     char *token = HASH_ITERATION_TOKEN(itr);
3664     int len_token = strlen(token);
3665     int start_pos;
3666     boolean base_prefix_found = FALSE;
3667     boolean parameter_suffix_found = FALSE;
3668
3669     // skip all parameter definitions (handled by read_token_parameters())
3670     for (i = 0; i < num_suffix_list_entries && !parameter_suffix_found; i++)
3671     {
3672       int len_suffix = strlen(suffix_list[i].token);
3673
3674       if (token_suffix_match(token, suffix_list[i].token, -len_suffix))
3675         parameter_suffix_found = TRUE;
3676     }
3677
3678     if (parameter_suffix_found)
3679       continue;
3680
3681     // ---------- step 0: search for matching base prefix ----------
3682
3683     start_pos = 0;
3684     for (i = 0; i < num_base_prefixes && !base_prefix_found; i++)
3685     {
3686       char *base_prefix = base_prefixes[i];
3687       int len_base_prefix = strlen(base_prefix);
3688       boolean ext1_suffix_found = FALSE;
3689       boolean ext2_suffix_found = FALSE;
3690       boolean ext3_suffix_found = FALSE;
3691       boolean exact_match = FALSE;
3692       int base_index = -1;
3693       int ext1_index = -1;
3694       int ext2_index = -1;
3695       int ext3_index = -1;
3696
3697       base_prefix_found = token_suffix_match(token, base_prefix, start_pos);
3698
3699       if (!base_prefix_found)
3700         continue;
3701
3702       base_index = i;
3703
3704       if (start_pos + len_base_prefix == len_token)     // exact match
3705       {
3706         exact_match = TRUE;
3707
3708         add_dynamic_file_list_entry(dynamic_file_list,
3709                                     num_dynamic_file_list_entries,
3710                                     extra_file_hash,
3711                                     suffix_list,
3712                                     num_suffix_list_entries,
3713                                     token);
3714         add_property_mapping(property_mapping,
3715                              num_property_mapping_entries,
3716                              base_index, -1, -1, -1,
3717                              current_summarized_file_list_entry);
3718         continue;
3719       }
3720
3721       // ---------- step 1: search for matching first suffix ----------
3722
3723       start_pos += len_base_prefix;
3724       for (j = 0; j < num_ext1_suffixes && !ext1_suffix_found; j++)
3725       {
3726         char *ext1_suffix = ext1_suffixes[j];
3727         int len_ext1_suffix = strlen(ext1_suffix);
3728
3729         ext1_suffix_found = token_suffix_match(token, ext1_suffix, start_pos);
3730
3731         if (!ext1_suffix_found)
3732           continue;
3733
3734         ext1_index = j;
3735
3736         if (start_pos + len_ext1_suffix == len_token)   // exact match
3737         {
3738           exact_match = TRUE;
3739
3740           add_dynamic_file_list_entry(dynamic_file_list,
3741                                       num_dynamic_file_list_entries,
3742                                       extra_file_hash,
3743                                       suffix_list,
3744                                       num_suffix_list_entries,
3745                                       token);
3746           add_property_mapping(property_mapping,
3747                                num_property_mapping_entries,
3748                                base_index, ext1_index, -1, -1,
3749                                current_summarized_file_list_entry);
3750           continue;
3751         }
3752
3753         start_pos += len_ext1_suffix;
3754       }
3755
3756       if (exact_match)
3757         break;
3758
3759       // ---------- step 2: search for matching second suffix ----------
3760
3761       for (k = 0; k < num_ext2_suffixes && !ext2_suffix_found; k++)
3762       {
3763         char *ext2_suffix = ext2_suffixes[k];
3764         int len_ext2_suffix = strlen(ext2_suffix);
3765
3766         ext2_suffix_found = token_suffix_match(token, ext2_suffix, start_pos);
3767
3768         if (!ext2_suffix_found)
3769           continue;
3770
3771         ext2_index = k;
3772
3773         if (start_pos + len_ext2_suffix == len_token)   // exact match
3774         {
3775           exact_match = TRUE;
3776
3777           add_dynamic_file_list_entry(dynamic_file_list,
3778                                       num_dynamic_file_list_entries,
3779                                       extra_file_hash,
3780                                       suffix_list,
3781                                       num_suffix_list_entries,
3782                                       token);
3783           add_property_mapping(property_mapping,
3784                                num_property_mapping_entries,
3785                                base_index, ext1_index, ext2_index, -1,
3786                                current_summarized_file_list_entry);
3787           continue;
3788         }
3789
3790         start_pos += len_ext2_suffix;
3791       }
3792
3793       if (exact_match)
3794         break;
3795
3796       // ---------- step 3: search for matching third suffix ----------
3797
3798       for (l = 0; l < num_ext3_suffixes && !ext3_suffix_found; l++)
3799       {
3800         char *ext3_suffix = ext3_suffixes[l];
3801         int len_ext3_suffix = strlen(ext3_suffix);
3802
3803         ext3_suffix_found = token_suffix_match(token, ext3_suffix, start_pos);
3804
3805         if (!ext3_suffix_found)
3806           continue;
3807
3808         ext3_index = l;
3809
3810         if (start_pos + len_ext3_suffix == len_token) // exact match
3811         {
3812           exact_match = TRUE;
3813
3814           add_dynamic_file_list_entry(dynamic_file_list,
3815                                       num_dynamic_file_list_entries,
3816                                       extra_file_hash,
3817                                       suffix_list,
3818                                       num_suffix_list_entries,
3819                                       token);
3820           add_property_mapping(property_mapping,
3821                                num_property_mapping_entries,
3822                                base_index, ext1_index, ext2_index, ext3_index,
3823                                current_summarized_file_list_entry);
3824           continue;
3825         }
3826       }
3827     }
3828   }
3829   END_HASH_ITERATION(extra_file_hash, itr)
3830
3831   if (artwork_info->num_dynamic_file_list_entries > 0)
3832   {
3833     artwork_info->dynamic_artwork_list =
3834       checked_calloc(artwork_info->num_dynamic_file_list_entries *
3835                      artwork_info->sizeof_artwork_list_entry);
3836   }
3837
3838   if (options.verbose && IS_PARENT_PROCESS())
3839   {
3840     SetupFileList *setup_file_list, *list;
3841     boolean dynamic_tokens_found = FALSE;
3842     boolean unknown_tokens_found = FALSE;
3843     boolean undefined_values_found = (hashtable_count(empty_file_hash) != 0);
3844
3845     // list may be NULL for empty artwork config files
3846     setup_file_list = loadSetupFileList(filename);
3847
3848     BEGIN_HASH_ITERATION(extra_file_hash, itr)
3849     {
3850       if (strEqual(HASH_ITERATION_VALUE(itr), known_token_value))
3851         dynamic_tokens_found = TRUE;
3852       else
3853         unknown_tokens_found = TRUE;
3854     }
3855     END_HASH_ITERATION(extra_file_hash, itr)
3856
3857     if (options.debug && dynamic_tokens_found)
3858     {
3859       Debug("config", "---");
3860       Debug("config", "dynamic token(s) found in config file:");
3861       Debug("config", "- config file: '%s'", filename);
3862
3863       for (list = setup_file_list; list != NULL; list = list->next)
3864       {
3865         char *value = getHashEntry(extra_file_hash, list->token);
3866
3867         if (value != NULL && strEqual(value, known_token_value))
3868           Debug("config", "- dynamic token: '%s'", list->token);
3869       }
3870
3871       Debug("config", "---");
3872     }
3873
3874     if (unknown_tokens_found)
3875     {
3876       Warn("---");
3877       Warn("unknown token(s) found in config file:");
3878       Warn("- config file: '%s'", filename);
3879
3880       for (list = setup_file_list; list != NULL; list = list->next)
3881       {
3882         char *value = getHashEntry(extra_file_hash, list->token);
3883
3884         if (value != NULL && !strEqual(value, known_token_value))
3885           Warn("- dynamic token: '%s'", list->token);
3886       }
3887
3888       Warn("---");
3889     }
3890
3891     if (undefined_values_found)
3892     {
3893       Warn("---");
3894       Warn("undefined values found in config file:");
3895       Warn("- config file: '%s'", filename);
3896
3897       for (list = setup_file_list; list != NULL; list = list->next)
3898       {
3899         char *value = getHashEntry(empty_file_hash, list->token);
3900
3901         if (value != NULL)
3902           Warn("- undefined value for token: '%s'", list->token);
3903       }
3904
3905       Warn("---");
3906     }
3907
3908     freeSetupFileList(setup_file_list);
3909   }
3910
3911   freeSetupFileHash(extra_file_hash);
3912   freeSetupFileHash(empty_file_hash);
3913 }
3914
3915 void LoadArtworkConfig(struct ArtworkListInfo *artwork_info)
3916 {
3917   struct FileInfo *file_list = artwork_info->file_list;
3918   int num_file_list_entries = artwork_info->num_file_list_entries;
3919   int num_suffix_list_entries = artwork_info->num_suffix_list_entries;
3920   char *filename_base = UNDEFINED_FILENAME, *filename_local;
3921   int i, j;
3922
3923   DrawInitTextHead("Loading artwork config");
3924   DrawInitTextItem(ARTWORKINFO_FILENAME(artwork_info->type));
3925
3926   // always start with reliable default values
3927   for (i = 0; i < num_file_list_entries; i++)
3928   {
3929     setString(&file_list[i].filename, file_list[i].default_filename);
3930
3931     for (j = 0; j < num_suffix_list_entries; j++)
3932       setString(&file_list[i].parameter[j], file_list[i].default_parameter[j]);
3933
3934     file_list[i].redefined = FALSE;
3935     file_list[i].fallback_to_default = FALSE;
3936   }
3937
3938   // free previous dynamic artwork file array
3939   if (artwork_info->dynamic_file_list != NULL)
3940   {
3941     for (i = 0; i < artwork_info->num_dynamic_file_list_entries; i++)
3942     {
3943       free(artwork_info->dynamic_file_list[i].token);
3944       free(artwork_info->dynamic_file_list[i].filename);
3945       free(artwork_info->dynamic_file_list[i].parameter);
3946     }
3947
3948     free(artwork_info->dynamic_file_list);
3949     artwork_info->dynamic_file_list = NULL;
3950
3951     FreeCustomArtworkList(artwork_info, &artwork_info->dynamic_artwork_list,
3952                           &artwork_info->num_dynamic_file_list_entries);
3953   }
3954
3955   // free previous property mapping
3956   if (artwork_info->property_mapping != NULL)
3957   {
3958     free(artwork_info->property_mapping);
3959
3960     artwork_info->property_mapping = NULL;
3961     artwork_info->num_property_mapping_entries = 0;
3962   }
3963
3964   if (!GFX_OVERRIDE_ARTWORK(artwork_info->type))
3965   {
3966     // first look for special artwork configured in level series config
3967     filename_base = getCustomArtworkLevelConfigFilename(artwork_info->type);
3968
3969     if (fileExists(filename_base))
3970       LoadArtworkConfigFromFilename(artwork_info, filename_base);
3971   }
3972
3973   filename_local = getCustomArtworkConfigFilename(artwork_info->type);
3974
3975   if (filename_local != NULL && !strEqual(filename_base, filename_local))
3976     LoadArtworkConfigFromFilename(artwork_info, filename_local);
3977 }
3978
3979 static void deleteArtworkListEntry(struct ArtworkListInfo *artwork_info,
3980                                    struct ListNodeInfo **listnode)
3981 {
3982   if (*listnode)
3983   {
3984     char *filename = (*listnode)->source_filename;
3985
3986     if (--(*listnode)->num_references <= 0)
3987       deleteNodeFromList(&artwork_info->content_list, filename,
3988                          artwork_info->free_artwork);
3989
3990     *listnode = NULL;
3991   }
3992 }
3993
3994 static void replaceArtworkListEntry(struct ArtworkListInfo *artwork_info,
3995                                     struct ListNodeInfo **listnode,
3996                                     struct FileInfo *file_list_entry)
3997 {
3998   char *init_text[] =
3999   {
4000     "Loading graphics",
4001     "Loading sounds",
4002     "Loading music"
4003   };
4004
4005   ListNode *node;
4006   char *basename = file_list_entry->filename;
4007   char *filename = getCustomArtworkFilename(basename, artwork_info->type);
4008
4009   // mark all images from non-default graphics directory as "redefined"
4010   if (artwork_info->type == ARTWORK_TYPE_GRAPHICS &&
4011       !strPrefix(filename, options.graphics_directory))
4012     file_list_entry->redefined = TRUE;
4013
4014   if (filename == NULL)
4015   {
4016     Warn("cannot find artwork file '%s'", basename);
4017
4018     basename = file_list_entry->default_filename;
4019
4020     // fail for cloned default artwork that has no default filename defined
4021     if (file_list_entry->default_is_cloned &&
4022         strEqual(basename, UNDEFINED_FILENAME))
4023     {
4024       void (*error_func)(char *, ...) = Warn;
4025
4026       // we can get away without sounds and music, but not without graphics
4027       if (*listnode == NULL && artwork_info->type == ARTWORK_TYPE_GRAPHICS)
4028         error_func = Fail;
4029
4030       error_func("token '%s' was cloned and has no default filename",
4031                  file_list_entry->token);
4032
4033       return;
4034     }
4035
4036     // dynamic artwork has no default filename / skip empty default artwork
4037     if (basename == NULL || strEqual(basename, UNDEFINED_FILENAME))
4038       return;
4039
4040     file_list_entry->fallback_to_default = TRUE;
4041
4042     Warn("trying default artwork file '%s'", basename);
4043
4044     filename = getCustomArtworkFilename(basename, artwork_info->type);
4045
4046     if (filename == NULL)
4047     {
4048       void (*error_func)(char *, ...) = Warn;
4049
4050       // we can get away without sounds and music, but not without graphics
4051       if (*listnode == NULL && artwork_info->type == ARTWORK_TYPE_GRAPHICS)
4052         error_func = Fail;
4053
4054       error_func("cannot find default artwork file '%s'", basename);
4055
4056       return;
4057     }
4058   }
4059
4060   // check if the old and the new artwork file are the same
4061   if (*listnode && strEqual((*listnode)->source_filename, filename))
4062   {
4063     // The old and new artwork are the same (have the same filename and path).
4064     // This usually means that this artwork does not exist in this artwork set
4065     // and a fallback to the existing artwork is done.
4066
4067     return;
4068   }
4069
4070   // delete existing artwork file entry
4071   deleteArtworkListEntry(artwork_info, listnode);
4072
4073   // check if the new artwork file already exists in the list of artwork
4074   if ((node = getNodeFromKey(artwork_info->content_list, filename)) != NULL)
4075   {
4076       *listnode = (struct ListNodeInfo *)node->content;
4077       (*listnode)->num_references++;
4078
4079       return;
4080   }
4081
4082   DrawInitTextHead(init_text[artwork_info->type]);
4083   DrawInitTextItem(basename);
4084
4085   if ((*listnode = artwork_info->load_artwork(filename)) != NULL)
4086   {
4087     // add new artwork file entry to the list of artwork files
4088     (*listnode)->num_references = 1;
4089     addNodeToList(&artwork_info->content_list, (*listnode)->source_filename,
4090                   *listnode);
4091   }
4092   else
4093   {
4094     void (*error_func)(char *, ...) = Warn;
4095
4096     // we can get away without sounds and music, but not without graphics
4097     if (artwork_info->type == ARTWORK_TYPE_GRAPHICS)
4098       error_func = Fail;
4099
4100     error_func("cannot load artwork file '%s'", basename);
4101
4102     return;
4103   }
4104 }
4105
4106 static void LoadCustomArtwork(struct ArtworkListInfo *artwork_info,
4107                               struct ListNodeInfo **listnode,
4108                               struct FileInfo *file_list_entry)
4109 {
4110   if (strEqual(file_list_entry->filename, UNDEFINED_FILENAME))
4111   {
4112     deleteArtworkListEntry(artwork_info, listnode);
4113
4114     return;
4115   }
4116
4117   replaceArtworkListEntry(artwork_info, listnode, file_list_entry);
4118 }
4119
4120 void ReloadCustomArtworkList(struct ArtworkListInfo *artwork_info)
4121 {
4122   struct FileInfo *file_list = artwork_info->file_list;
4123   struct FileInfo *dynamic_file_list = artwork_info->dynamic_file_list;
4124   int num_file_list_entries = artwork_info->num_file_list_entries;
4125   int num_dynamic_file_list_entries =
4126     artwork_info->num_dynamic_file_list_entries;
4127   int i;
4128
4129   print_timestamp_init("ReloadCustomArtworkList");
4130
4131   for (i = 0; i < num_file_list_entries; i++)
4132     LoadCustomArtwork(artwork_info, &artwork_info->artwork_list[i],
4133                       &file_list[i]);
4134
4135   for (i = 0; i < num_dynamic_file_list_entries; i++)
4136     LoadCustomArtwork(artwork_info, &artwork_info->dynamic_artwork_list[i],
4137                       &dynamic_file_list[i]);
4138
4139   print_timestamp_done("ReloadCustomArtworkList");
4140
4141 #if 0
4142   dumpList(artwork_info->content_list);
4143 #endif
4144 }
4145
4146 static void FreeCustomArtworkList(struct ArtworkListInfo *artwork_info,
4147                                   struct ListNodeInfo ***list,
4148                                   int *num_list_entries)
4149 {
4150   int i;
4151
4152   if (*list == NULL)
4153     return;
4154
4155   for (i = 0; i < *num_list_entries; i++)
4156     deleteArtworkListEntry(artwork_info, &(*list)[i]);
4157   free(*list);
4158
4159   *list = NULL;
4160   *num_list_entries = 0;
4161 }
4162
4163 void FreeCustomArtworkLists(struct ArtworkListInfo *artwork_info)
4164 {
4165   if (artwork_info == NULL)
4166     return;
4167
4168   FreeCustomArtworkList(artwork_info, &artwork_info->artwork_list,
4169                         &artwork_info->num_file_list_entries);
4170
4171   FreeCustomArtworkList(artwork_info, &artwork_info->dynamic_artwork_list,
4172                         &artwork_info->num_dynamic_file_list_entries);
4173 }
4174
4175
4176 // ----------------------------------------------------------------------------
4177 // functions only needed for non-Unix (non-command-line) systems
4178 // (MS-DOS only; SDL/Windows creates files "stdout.txt" and "stderr.txt")
4179 // (now also added for Windows, to create files in user data directory)
4180 // ----------------------------------------------------------------------------
4181
4182 char *getLogFilename(char *basename)
4183 {
4184   return getPath2(getMainUserGameDataDir(), basename);
4185 }
4186
4187 void OpenLogFile(void)
4188 {
4189   InitMainUserDataDirectory();
4190
4191   if ((program.log_file = fopen(program.log_filename, MODE_WRITE)) == NULL)
4192   {
4193     program.log_file = program.log_file_default;   // reset to default
4194
4195     Warn("cannot open file '%s' for writing: %s",
4196          program.log_filename, strerror(errno));
4197   }
4198
4199   // output should be unbuffered so it is not truncated in a crash
4200   setbuf(program.log_file, NULL);
4201 }
4202
4203 void CloseLogFile(void)
4204 {
4205   if (program.log_file != program.log_file_default)
4206     fclose(program.log_file);
4207 }
4208
4209 void DumpLogFile(void)
4210 {
4211   FILE *log_file = fopen(program.log_filename, MODE_READ);
4212
4213   if (log_file == NULL)
4214     return;
4215
4216   while (!feof(log_file))
4217     fputc(fgetc(log_file), stdout);
4218
4219   fclose(log_file);
4220 }
4221
4222 void NotifyUserAboutErrorFile(void)
4223 {
4224 #if defined(PLATFORM_WINDOWS)
4225   char *title_text = getStringCat2(program.program_title, " Error Message");
4226   char *error_text = getStringCat2("The program was aborted due to an error; "
4227                                    "for details, see the following error file:"
4228                                    STRING_NEWLINE,
4229                                    program.log_filename);
4230
4231   MessageBox(NULL, error_text, title_text, MB_OK);
4232 #endif
4233 }
4234
4235
4236 // ----------------------------------------------------------------------------
4237 // the following is only for debugging purpose and normally not used
4238 // ----------------------------------------------------------------------------
4239
4240 #if DEBUG
4241
4242 #define DEBUG_PRINT_INIT_TIMESTAMPS             TRUE
4243 #define DEBUG_PRINT_INIT_TIMESTAMPS_DEPTH       10
4244
4245 #define DEBUG_NUM_TIMESTAMPS                    10
4246 #define DEBUG_TIME_IN_MICROSECONDS              0
4247
4248 #if DEBUG_TIME_IN_MICROSECONDS
4249 static double Counter_Microseconds(void)
4250 {
4251   static struct timeval base_time = { 0, 0 };
4252   struct timeval current_time;
4253   double counter;
4254
4255   gettimeofday(&current_time, NULL);
4256
4257   // reset base time in case of wrap-around
4258   if (current_time.tv_sec < base_time.tv_sec)
4259     base_time = current_time;
4260
4261   counter =
4262     ((double)(current_time.tv_sec  - base_time.tv_sec)) * 1000000 +
4263     ((double)(current_time.tv_usec - base_time.tv_usec));
4264
4265   return counter;               // return microseconds since last init
4266 }
4267 #endif
4268
4269 static char *debug_print_timestamp_get_padding(int padding_size)
4270 {
4271   static char *padding = NULL;
4272   int max_padding_size = 100;
4273
4274   if (padding == NULL)
4275   {
4276     padding = checked_calloc(max_padding_size + 1);
4277     memset(padding, ' ', max_padding_size);
4278   }
4279
4280   return &padding[MAX(0, max_padding_size - padding_size)];
4281 }
4282
4283 void debug_print_timestamp(int counter_nr, char *message)
4284 {
4285   int indent_size = 8;
4286   int padding_size = 40;
4287   float timestamp_interval;
4288
4289   if (counter_nr < 0)
4290     Fail("debugging: invalid negative counter");
4291   else if (counter_nr >= DEBUG_NUM_TIMESTAMPS)
4292     Fail("debugging: increase DEBUG_NUM_TIMESTAMPS in misc.c");
4293
4294 #if DEBUG_TIME_IN_MICROSECONDS
4295   static double counter[DEBUG_NUM_TIMESTAMPS][2];
4296   char *unit = "ms";
4297
4298   counter[counter_nr][0] = Counter_Microseconds();
4299 #else
4300   static int counter[DEBUG_NUM_TIMESTAMPS][2];
4301   char *unit = "s";
4302
4303   counter[counter_nr][0] = Counter();
4304 #endif
4305
4306   timestamp_interval = counter[counter_nr][0] - counter[counter_nr][1];
4307   counter[counter_nr][1] = counter[counter_nr][0];
4308
4309   if (message)
4310     Debug("time:init", "%s%s%s %.3f %s",
4311           debug_print_timestamp_get_padding(counter_nr * indent_size),
4312           message,
4313           debug_print_timestamp_get_padding(padding_size - strlen(message)),
4314           timestamp_interval / 1000,
4315           unit);
4316 }
4317
4318 #if 0
4319 static void debug_print_parent_only(char *format, ...)
4320 {
4321   if (!IS_PARENT_PROCESS())
4322     return;
4323
4324   if (format)
4325   {
4326     va_list ap;
4327
4328     va_start(ap, format);
4329     vprintf(format, ap);
4330     va_end(ap);
4331
4332     printf("\n");
4333   }
4334 }
4335 #endif
4336
4337 #endif  // DEBUG
4338
4339 static void print_timestamp_ext(char *message, char *mode)
4340 {
4341 #if DEBUG_PRINT_INIT_TIMESTAMPS
4342   static char *debug_message = NULL;
4343   static char *last_message = NULL;
4344   static int counter_nr = 0;
4345   int max_depth = DEBUG_PRINT_INIT_TIMESTAMPS_DEPTH;
4346
4347   checked_free(debug_message);
4348   debug_message = getStringCat3(mode, " ", message);
4349
4350   if (strEqual(mode, "INIT"))
4351   {
4352     debug_print_timestamp(counter_nr, NULL);
4353
4354     if (counter_nr + 1 < max_depth)
4355       debug_print_timestamp(counter_nr, debug_message);
4356
4357     counter_nr++;
4358
4359     debug_print_timestamp(counter_nr, NULL);
4360   }
4361   else if (strEqual(mode, "DONE"))
4362   {
4363     counter_nr--;
4364
4365     if (counter_nr + 1 < max_depth ||
4366         (counter_nr == 0 && max_depth == 1))
4367     {
4368       last_message = message;
4369
4370       if (counter_nr == 0 && max_depth == 1)
4371       {
4372         checked_free(debug_message);
4373         debug_message = getStringCat3("TIME", " ", message);
4374       }
4375
4376       debug_print_timestamp(counter_nr, debug_message);
4377     }
4378   }
4379   else if (!strEqual(mode, "TIME") ||
4380            !strEqual(message, last_message))
4381   {
4382     if (counter_nr < max_depth)
4383       debug_print_timestamp(counter_nr, debug_message);
4384   }
4385 #endif
4386 }
4387
4388 void print_timestamp_init(char *message)
4389 {
4390   print_timestamp_ext(message, "INIT");
4391 }
4392
4393 void print_timestamp_time(char *message)
4394 {
4395   print_timestamp_ext(message, "TIME");
4396 }
4397
4398 void print_timestamp_done(char *message)
4399 {
4400   print_timestamp_ext(message, "DONE");
4401 }