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