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