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