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