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