rnd-20050203-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 /* This function checks if a string <s> of the format "string1, string2, ..."
1619    exactly contains a string <s_contained>. */
1620
1621 static boolean string_has_parameter(char *s, char *s_contained)
1622 {
1623   char *substring;
1624
1625   if (s == NULL || s_contained == NULL)
1626     return FALSE;
1627
1628   if (strlen(s_contained) > strlen(s))
1629     return FALSE;
1630
1631   if (strncmp(s, s_contained, strlen(s_contained)) == 0)
1632   {
1633     char next_char = s[strlen(s_contained)];
1634
1635     /* check if next character is delimiter or whitespace */
1636     return (next_char == ',' || next_char == '\0' ||
1637             next_char == ' ' || next_char == '\t' ? TRUE : FALSE);
1638   }
1639
1640   /* check if string contains another parameter string after a comma */
1641   substring = strchr(s, ',');
1642   if (substring == NULL)        /* string does not contain a comma */
1643     return FALSE;
1644
1645   /* advance string pointer to next character after the comma */
1646   substring++;
1647
1648   /* skip potential whitespaces after the comma */
1649   while (*substring == ' ' || *substring == '\t')
1650     substring++;
1651
1652   return string_has_parameter(substring, s_contained);
1653 }
1654
1655 int get_parameter_value(char *suffix, char *value_raw, int type)
1656 {
1657   char *value = getStringToLower(value_raw);
1658   int result = 0;       /* probably a save default value */
1659
1660   if (strcmp(suffix, ".direction") == 0)
1661   {
1662     result = (strcmp(value, "left")  == 0 ? MV_LEFT :
1663               strcmp(value, "right") == 0 ? MV_RIGHT :
1664               strcmp(value, "up")    == 0 ? MV_UP :
1665               strcmp(value, "down")  == 0 ? MV_DOWN : MV_NO_MOVING);
1666   }
1667   else if (strcmp(suffix, ".anim_mode") == 0)
1668   {
1669     result = (string_has_parameter(value, "none")       ? ANIM_NONE :
1670               string_has_parameter(value, "loop")       ? ANIM_LOOP :
1671               string_has_parameter(value, "linear")     ? ANIM_LINEAR :
1672               string_has_parameter(value, "pingpong")   ? ANIM_PINGPONG :
1673               string_has_parameter(value, "pingpong2")  ? ANIM_PINGPONG2 :
1674               string_has_parameter(value, "random")     ? ANIM_RANDOM :
1675               string_has_parameter(value, "horizontal") ? ANIM_HORIZONTAL :
1676               string_has_parameter(value, "vertical")   ? ANIM_VERTICAL :
1677               ANIM_DEFAULT);
1678
1679     if (string_has_parameter(value, "reverse"))
1680       result |= ANIM_REVERSE;
1681   }
1682   else          /* generic parameter of type integer or boolean */
1683   {
1684     result = (strcmp(value, ARG_UNDEFINED) == 0 ? ARG_UNDEFINED_VALUE :
1685               type == TYPE_INTEGER ? get_integer_from_string(value) :
1686               type == TYPE_BOOLEAN ? get_boolean_from_string(value) :
1687               ARG_UNDEFINED_VALUE);
1688   }
1689
1690   free(value);
1691
1692   return result;
1693 }
1694
1695 int get_auto_parameter_value(char *token, char *value_raw)
1696 {
1697   char *suffix;
1698
1699   if (token == NULL || value_raw == NULL)
1700     return ARG_UNDEFINED_VALUE;
1701
1702   suffix = strrchr(token, '.');
1703   if (suffix == NULL)
1704     suffix = token;
1705
1706   return get_parameter_value(suffix, value_raw, TYPE_INTEGER);
1707 }
1708
1709 static void FreeCustomArtworkList(struct ArtworkListInfo *,
1710                                   struct ListNodeInfo ***, int *);
1711
1712 struct FileInfo *getFileListFromConfigList(struct ConfigInfo *config_list,
1713                                            struct ConfigTypeInfo *suffix_list,
1714                                            char **ignore_tokens,
1715                                            int num_file_list_entries)
1716 {
1717   struct FileInfo *file_list;
1718   int num_file_list_entries_found = 0;
1719   int num_suffix_list_entries = 0;
1720   int list_pos;
1721   int i, j;
1722
1723   file_list = checked_calloc(num_file_list_entries * sizeof(struct FileInfo));
1724
1725   for (i = 0; suffix_list[i].token != NULL; i++)
1726     num_suffix_list_entries++;
1727
1728   /* always start with reliable default values */
1729   for (i = 0; i < num_file_list_entries; i++)
1730   {
1731     file_list[i].token = NULL;
1732
1733     file_list[i].default_filename = NULL;
1734     file_list[i].filename = NULL;
1735
1736     if (num_suffix_list_entries > 0)
1737     {
1738       int parameter_array_size = num_suffix_list_entries * sizeof(char *);
1739
1740       file_list[i].default_parameter = checked_calloc(parameter_array_size);
1741       file_list[i].parameter = checked_calloc(parameter_array_size);
1742
1743       for (j = 0; j < num_suffix_list_entries; j++)
1744       {
1745         setString(&file_list[i].default_parameter[j], suffix_list[j].value);
1746         setString(&file_list[i].parameter[j], suffix_list[j].value);
1747       }
1748
1749       file_list[i].redefined = FALSE;
1750       file_list[i].fallback_to_default = FALSE;
1751     }
1752   }
1753
1754   list_pos = 0;
1755   for (i = 0; config_list[i].token != NULL; i++)
1756   {
1757     int len_config_token = strlen(config_list[i].token);
1758     int len_config_value = strlen(config_list[i].value);
1759     boolean is_file_entry = TRUE;
1760
1761     for (j = 0; suffix_list[j].token != NULL; j++)
1762     {
1763       int len_suffix = strlen(suffix_list[j].token);
1764
1765       if (len_suffix < len_config_token &&
1766           strcmp(&config_list[i].token[len_config_token - len_suffix],
1767                  suffix_list[j].token) == 0)
1768       {
1769         setString(&file_list[list_pos].default_parameter[j],
1770                   config_list[i].value);
1771
1772         is_file_entry = FALSE;
1773         break;
1774       }
1775     }
1776
1777     /* the following tokens are no file definitions, but other config tokens */
1778     for (j = 0; ignore_tokens[j] != NULL; j++)
1779       if (strcmp(config_list[i].token, ignore_tokens[j]) == 0)
1780         is_file_entry = FALSE;
1781
1782     if (is_file_entry)
1783     {
1784       if (i > 0)
1785         list_pos++;
1786
1787       if (list_pos >= num_file_list_entries)
1788         break;
1789
1790       /* simple sanity check if this is really a file definition */
1791       if (strcmp(&config_list[i].value[len_config_value - 4], ".pcx") != 0 &&
1792           strcmp(&config_list[i].value[len_config_value - 4], ".wav") != 0 &&
1793           strcmp(config_list[i].value, UNDEFINED_FILENAME) != 0)
1794       {
1795         Error(ERR_RETURN, "Configuration directive '%s' -> '%s':",
1796               config_list[i].token, config_list[i].value);
1797         Error(ERR_EXIT, "This seems to be no valid definition -- please fix");
1798       }
1799
1800       file_list[list_pos].token = config_list[i].token;
1801       file_list[list_pos].default_filename = config_list[i].value;
1802     }
1803   }
1804
1805   num_file_list_entries_found = list_pos + 1;
1806   if (num_file_list_entries_found != num_file_list_entries)
1807   {
1808     Error(ERR_RETURN_LINE, "-");
1809     Error(ERR_RETURN, "inconsistant config list information:");
1810     Error(ERR_RETURN, "- should be:   %d (according to 'src/conf_gfx.h')",
1811           num_file_list_entries);
1812     Error(ERR_RETURN, "- found to be: %d (according to 'src/conf_gfx.c')",
1813           num_file_list_entries_found);
1814     Error(ERR_EXIT,   "please fix");
1815   }
1816
1817   return file_list;
1818 }
1819
1820 static boolean token_suffix_match(char *token, char *suffix, int start_pos)
1821 {
1822   int len_token = strlen(token);
1823   int len_suffix = strlen(suffix);
1824
1825 #if 0
1826   if (IS_PARENT_PROCESS())
1827     printf(":::::::::: check '%s' for '%s' ::::::::::\n", token, suffix);
1828 #endif
1829
1830   if (start_pos < 0)    /* compare suffix from end of string */
1831     start_pos += len_token;
1832
1833   if (start_pos < 0 || start_pos + len_suffix > len_token)
1834     return FALSE;
1835
1836   if (strncmp(&token[start_pos], suffix, len_suffix) != 0)
1837     return FALSE;
1838
1839   if (token[start_pos + len_suffix] == '\0')
1840     return TRUE;
1841
1842   if (token[start_pos + len_suffix] == '.')
1843     return TRUE;
1844
1845   return FALSE;
1846 }
1847
1848 #define KNOWN_TOKEN_VALUE       "[KNOWN_TOKEN_VALUE]"
1849
1850 static void read_token_parameters(SetupFileHash *setup_file_hash,
1851                                   struct ConfigTypeInfo *suffix_list,
1852                                   struct FileInfo *file_list_entry)
1853 {
1854   /* check for config token that is the base token without any suffixes */
1855   char *filename = getHashEntry(setup_file_hash, file_list_entry->token);
1856   char *known_token_value = KNOWN_TOKEN_VALUE;
1857   int i;
1858
1859   if (filename != NULL)
1860   {
1861     setString(&file_list_entry->filename, filename);
1862
1863     /* when file definition found, set all parameters to default values */
1864     for (i = 0; suffix_list[i].token != NULL; i++)
1865       setString(&file_list_entry->parameter[i], suffix_list[i].value);
1866
1867     file_list_entry->redefined = TRUE;
1868
1869     /* mark config file token as well known from default config */
1870     setHashEntry(setup_file_hash, file_list_entry->token, known_token_value);
1871   }
1872 #if 0
1873   else
1874   {
1875     if (strcmp(file_list_entry->filename,
1876                file_list_entry->default_filename) != 0)
1877       printf("___ resetting '%s' to default\n", file_list_entry->token);
1878
1879     setString(&file_list_entry->filename, file_list_entry->default_filename);
1880   }
1881 #endif
1882
1883   /* check for config tokens that can be build by base token and suffixes */
1884   for (i = 0; suffix_list[i].token != NULL; i++)
1885   {
1886     char *token = getStringCat2(file_list_entry->token, suffix_list[i].token);
1887     char *value = getHashEntry(setup_file_hash, token);
1888
1889     if (value != NULL)
1890     {
1891       setString(&file_list_entry->parameter[i], value);
1892
1893       /* mark config file token as well known from default config */
1894       setHashEntry(setup_file_hash, token, known_token_value);
1895     }
1896
1897     free(token);
1898   }
1899 }
1900
1901 static void add_dynamic_file_list_entry(struct FileInfo **list,
1902                                         int *num_list_entries,
1903                                         SetupFileHash *extra_file_hash,
1904                                         struct ConfigTypeInfo *suffix_list,
1905                                         int num_suffix_list_entries,
1906                                         char *token)
1907 {
1908   struct FileInfo *new_list_entry;
1909   int parameter_array_size = num_suffix_list_entries * sizeof(char *);
1910
1911 #if 0
1912   if (IS_PARENT_PROCESS())
1913     printf("===> found dynamic definition '%s'\n", token);
1914 #endif
1915
1916   (*num_list_entries)++;
1917   *list = checked_realloc(*list, *num_list_entries * sizeof(struct FileInfo));
1918   new_list_entry = &(*list)[*num_list_entries - 1];
1919
1920   new_list_entry->token = getStringCopy(token);
1921   new_list_entry->default_filename = NULL;
1922   new_list_entry->filename = NULL;
1923   new_list_entry->parameter = checked_calloc(parameter_array_size);
1924
1925   new_list_entry->redefined = FALSE;
1926   new_list_entry->fallback_to_default = FALSE;
1927
1928   read_token_parameters(extra_file_hash, suffix_list, new_list_entry);
1929 }
1930
1931 static void add_property_mapping(struct PropertyMapping **list,
1932                                  int *num_list_entries,
1933                                  int base_index, int ext1_index,
1934                                  int ext2_index, int ext3_index,
1935                                  int artwork_index)
1936 {
1937   struct PropertyMapping *new_list_entry;
1938
1939   (*num_list_entries)++;
1940   *list = checked_realloc(*list,
1941                           *num_list_entries * sizeof(struct PropertyMapping));
1942   new_list_entry = &(*list)[*num_list_entries - 1];
1943
1944   new_list_entry->base_index = base_index;
1945   new_list_entry->ext1_index = ext1_index;
1946   new_list_entry->ext2_index = ext2_index;
1947   new_list_entry->ext3_index = ext3_index;
1948
1949   new_list_entry->artwork_index = artwork_index;
1950 }
1951
1952 static void LoadArtworkConfigFromFilename(struct ArtworkListInfo *artwork_info,
1953                                           char *filename)
1954 {
1955   struct FileInfo *file_list = artwork_info->file_list;
1956   struct ConfigTypeInfo *suffix_list = artwork_info->suffix_list;
1957   char **base_prefixes = artwork_info->base_prefixes;
1958   char **ext1_suffixes = artwork_info->ext1_suffixes;
1959   char **ext2_suffixes = artwork_info->ext2_suffixes;
1960   char **ext3_suffixes = artwork_info->ext3_suffixes;
1961   char **ignore_tokens = artwork_info->ignore_tokens;
1962   int num_file_list_entries = artwork_info->num_file_list_entries;
1963   int num_suffix_list_entries = artwork_info->num_suffix_list_entries;
1964   int num_base_prefixes = artwork_info->num_base_prefixes;
1965   int num_ext1_suffixes = artwork_info->num_ext1_suffixes;
1966   int num_ext2_suffixes = artwork_info->num_ext2_suffixes;
1967   int num_ext3_suffixes = artwork_info->num_ext3_suffixes;
1968   int num_ignore_tokens = artwork_info->num_ignore_tokens;
1969   SetupFileHash *setup_file_hash, *valid_file_hash;
1970   SetupFileHash *extra_file_hash, *empty_file_hash;
1971   char *known_token_value = KNOWN_TOKEN_VALUE;
1972   int i, j, k, l;
1973
1974   if (filename == NULL)
1975     return;
1976
1977 #if 0
1978   printf("::: LoadArtworkConfigFromFilename: '%s'\n", filename);
1979 #endif
1980
1981   if ((setup_file_hash = loadSetupFileHash(filename)) == NULL)
1982     return;
1983
1984   /* separate valid (defined) from empty (undefined) config token values */
1985   valid_file_hash = newSetupFileHash();
1986   empty_file_hash = newSetupFileHash();
1987   BEGIN_HASH_ITERATION(setup_file_hash, itr)
1988   {
1989     char *value = HASH_ITERATION_VALUE(itr);
1990
1991     setHashEntry(*value ? valid_file_hash : empty_file_hash,
1992                  HASH_ITERATION_TOKEN(itr), value);
1993   }
1994   END_HASH_ITERATION(setup_file_hash, itr)
1995
1996   /* at this point, we do not need the setup file hash anymore -- free it */
1997   freeSetupFileHash(setup_file_hash);
1998
1999 #if 1
2000   /* map deprecated to current tokens (using prefix match and replace) */
2001   BEGIN_HASH_ITERATION(valid_file_hash, itr)
2002   {
2003     /* !!! make this dynamically configurable (init.c:InitArtworkConfig) !!! */
2004     static char *map_token_prefix[][2] =
2005     {   /* old prefix   ->      new prefix      */
2006       { "char_procent",         "char_percent"  },
2007       { NULL,                   NULL            }
2008     };
2009     char *token = HASH_ITERATION_TOKEN(itr);
2010
2011     for (i = 0; map_token_prefix[i][0] != NULL; i++)
2012     {
2013       int token_prefix_length = strlen(map_token_prefix[i][0]);
2014
2015       if (strncmp(token, map_token_prefix[i][0], token_prefix_length) == 0)
2016       {
2017         char *value = HASH_ITERATION_VALUE(itr);
2018         char *mapped_token = getStringCat2(map_token_prefix[i][1],
2019                                            &token[token_prefix_length]);
2020
2021         /* add mapped token */
2022         setHashEntry(valid_file_hash, mapped_token, value);
2023
2024         /* ignore old token (by setting it to "known" keyword) */
2025         setHashEntry(valid_file_hash, token, known_token_value);
2026
2027         free(mapped_token);
2028
2029         break;
2030       }
2031     }
2032   }
2033   END_HASH_ITERATION(valid_file_hash, itr)
2034 #endif
2035
2036   /* read parameters for all known config file tokens */
2037   for (i = 0; i < num_file_list_entries; i++)
2038     read_token_parameters(valid_file_hash, suffix_list, &file_list[i]);
2039
2040   /* set all tokens that can be ignored here to "known" keyword */
2041   for (i = 0; i < num_ignore_tokens; i++)
2042     setHashEntry(valid_file_hash, ignore_tokens[i], known_token_value);
2043
2044   /* copy all unknown config file tokens to extra config hash */
2045   extra_file_hash = newSetupFileHash();
2046   BEGIN_HASH_ITERATION(valid_file_hash, itr)
2047   {
2048     char *value = HASH_ITERATION_VALUE(itr);
2049
2050     if (strcmp(value, known_token_value) != 0)
2051       setHashEntry(extra_file_hash, HASH_ITERATION_TOKEN(itr), value);
2052   }
2053   END_HASH_ITERATION(valid_file_hash, itr)
2054
2055   /* at this point, we do not need the valid file hash anymore -- free it */
2056   freeSetupFileHash(valid_file_hash);
2057
2058   /* now try to determine valid, dynamically defined config tokens */
2059
2060   BEGIN_HASH_ITERATION(extra_file_hash, itr)
2061   {
2062     struct FileInfo **dynamic_file_list =
2063       &artwork_info->dynamic_file_list;
2064     int *num_dynamic_file_list_entries =
2065       &artwork_info->num_dynamic_file_list_entries;
2066     struct PropertyMapping **property_mapping =
2067       &artwork_info->property_mapping;
2068     int *num_property_mapping_entries =
2069       &artwork_info->num_property_mapping_entries;
2070     int current_summarized_file_list_entry =
2071       artwork_info->num_file_list_entries +
2072       artwork_info->num_dynamic_file_list_entries;
2073     char *token = HASH_ITERATION_TOKEN(itr);
2074     int len_token = strlen(token);
2075     int start_pos;
2076     boolean base_prefix_found = FALSE;
2077     boolean parameter_suffix_found = FALSE;
2078
2079     /* skip all parameter definitions (handled by read_token_parameters()) */
2080     for (i = 0; i < num_suffix_list_entries && !parameter_suffix_found; i++)
2081     {
2082       int len_suffix = strlen(suffix_list[i].token);
2083
2084       if (token_suffix_match(token, suffix_list[i].token, -len_suffix))
2085         parameter_suffix_found = TRUE;
2086     }
2087
2088 #if 0
2089     if (IS_PARENT_PROCESS())
2090     {
2091       if (parameter_suffix_found)
2092         printf("---> skipping token '%s' (parameter token)\n", token);
2093       else
2094         printf("---> examining token '%s': search prefix ...\n", token);
2095     }
2096 #endif
2097
2098     if (parameter_suffix_found)
2099       continue;
2100
2101     /* ---------- step 0: search for matching base prefix ---------- */
2102
2103     start_pos = 0;
2104     for (i = 0; i < num_base_prefixes && !base_prefix_found; i++)
2105     {
2106       char *base_prefix = base_prefixes[i];
2107       int len_base_prefix = strlen(base_prefix);
2108       boolean ext1_suffix_found = FALSE;
2109       boolean ext2_suffix_found = FALSE;
2110       boolean ext3_suffix_found = FALSE;
2111       boolean exact_match = FALSE;
2112       int base_index = -1;
2113       int ext1_index = -1;
2114       int ext2_index = -1;
2115       int ext3_index = -1;
2116
2117       base_prefix_found = token_suffix_match(token, base_prefix, start_pos);
2118
2119       if (!base_prefix_found)
2120         continue;
2121
2122       base_index = i;
2123
2124       if (start_pos + len_base_prefix == len_token)     /* exact match */
2125       {
2126         exact_match = TRUE;
2127
2128         add_dynamic_file_list_entry(dynamic_file_list,
2129                                     num_dynamic_file_list_entries,
2130                                     extra_file_hash,
2131                                     suffix_list,
2132                                     num_suffix_list_entries,
2133                                     token);
2134         add_property_mapping(property_mapping,
2135                              num_property_mapping_entries,
2136                              base_index, -1, -1, -1,
2137                              current_summarized_file_list_entry);
2138         continue;
2139       }
2140
2141 #if 0
2142       if (IS_PARENT_PROCESS())
2143         printf("---> examining token '%s': search 1st suffix ...\n", token);
2144 #endif
2145
2146       /* ---------- step 1: search for matching first suffix ---------- */
2147
2148       start_pos += len_base_prefix;
2149       for (j = 0; j < num_ext1_suffixes && !ext1_suffix_found; j++)
2150       {
2151         char *ext1_suffix = ext1_suffixes[j];
2152         int len_ext1_suffix = strlen(ext1_suffix);
2153
2154         ext1_suffix_found = token_suffix_match(token, ext1_suffix, start_pos);
2155
2156         if (!ext1_suffix_found)
2157           continue;
2158
2159         ext1_index = j;
2160
2161         if (start_pos + len_ext1_suffix == len_token)   /* exact match */
2162         {
2163           exact_match = TRUE;
2164
2165           add_dynamic_file_list_entry(dynamic_file_list,
2166                                       num_dynamic_file_list_entries,
2167                                       extra_file_hash,
2168                                       suffix_list,
2169                                       num_suffix_list_entries,
2170                                       token);
2171           add_property_mapping(property_mapping,
2172                                num_property_mapping_entries,
2173                                base_index, ext1_index, -1, -1,
2174                                current_summarized_file_list_entry);
2175           continue;
2176         }
2177
2178         start_pos += len_ext1_suffix;
2179       }
2180
2181       if (exact_match)
2182         break;
2183
2184 #if 0
2185       if (IS_PARENT_PROCESS())
2186         printf("---> examining token '%s': search 2nd suffix ...\n", token);
2187 #endif
2188
2189       /* ---------- step 2: search for matching second suffix ---------- */
2190
2191       for (k = 0; k < num_ext2_suffixes && !ext2_suffix_found; k++)
2192       {
2193         char *ext2_suffix = ext2_suffixes[k];
2194         int len_ext2_suffix = strlen(ext2_suffix);
2195
2196         ext2_suffix_found = token_suffix_match(token, ext2_suffix, start_pos);
2197
2198         if (!ext2_suffix_found)
2199           continue;
2200
2201         ext2_index = k;
2202
2203         if (start_pos + len_ext2_suffix == len_token)   /* exact match */
2204         {
2205           exact_match = TRUE;
2206
2207           add_dynamic_file_list_entry(dynamic_file_list,
2208                                       num_dynamic_file_list_entries,
2209                                       extra_file_hash,
2210                                       suffix_list,
2211                                       num_suffix_list_entries,
2212                                       token);
2213           add_property_mapping(property_mapping,
2214                                num_property_mapping_entries,
2215                                base_index, ext1_index, ext2_index, -1,
2216                                current_summarized_file_list_entry);
2217           continue;
2218         }
2219
2220         start_pos += len_ext2_suffix;
2221       }
2222
2223       if (exact_match)
2224         break;
2225
2226 #if 0
2227       if (IS_PARENT_PROCESS())
2228         printf("---> examining token '%s': search 3rd suffix ...\n",token);
2229 #endif
2230
2231       /* ---------- step 3: search for matching third suffix ---------- */
2232
2233       for (l = 0; l < num_ext3_suffixes && !ext3_suffix_found; l++)
2234       {
2235         char *ext3_suffix = ext3_suffixes[l];
2236         int len_ext3_suffix = strlen(ext3_suffix);
2237
2238         ext3_suffix_found = token_suffix_match(token, ext3_suffix, start_pos);
2239
2240         if (!ext3_suffix_found)
2241           continue;
2242
2243         ext3_index = l;
2244
2245         if (start_pos + len_ext3_suffix == len_token) /* exact match */
2246         {
2247           exact_match = TRUE;
2248
2249           add_dynamic_file_list_entry(dynamic_file_list,
2250                                       num_dynamic_file_list_entries,
2251                                       extra_file_hash,
2252                                       suffix_list,
2253                                       num_suffix_list_entries,
2254                                       token);
2255           add_property_mapping(property_mapping,
2256                                num_property_mapping_entries,
2257                                base_index, ext1_index, ext2_index, ext3_index,
2258                                current_summarized_file_list_entry);
2259           continue;
2260         }
2261       }
2262     }
2263   }
2264   END_HASH_ITERATION(extra_file_hash, itr)
2265
2266   if (artwork_info->num_dynamic_file_list_entries > 0)
2267   {
2268     artwork_info->dynamic_artwork_list =
2269       checked_calloc(artwork_info->num_dynamic_file_list_entries *
2270                      artwork_info->sizeof_artwork_list_entry);
2271   }
2272
2273   if (options.verbose && IS_PARENT_PROCESS())
2274   {
2275     SetupFileList *setup_file_list, *list;
2276     boolean dynamic_tokens_found = FALSE;
2277     boolean unknown_tokens_found = FALSE;
2278     boolean undefined_values_found = (hashtable_count(empty_file_hash) != 0);
2279
2280     if ((setup_file_list = loadSetupFileList(filename)) == NULL)
2281       Error(ERR_EXIT, "loadSetupFileHash works, but loadSetupFileList fails");
2282
2283     BEGIN_HASH_ITERATION(extra_file_hash, itr)
2284     {
2285       if (strcmp(HASH_ITERATION_VALUE(itr), known_token_value) == 0)
2286         dynamic_tokens_found = TRUE;
2287       else
2288         unknown_tokens_found = TRUE;
2289     }
2290     END_HASH_ITERATION(extra_file_hash, itr)
2291
2292     if (options.debug && dynamic_tokens_found)
2293     {
2294       Error(ERR_RETURN_LINE, "-");
2295       Error(ERR_RETURN, "dynamic token(s) found in config file:");
2296       Error(ERR_RETURN, "- config file: '%s'", filename);
2297
2298       for (list = setup_file_list; list != NULL; list = list->next)
2299       {
2300         char *value = getHashEntry(extra_file_hash, list->token);
2301
2302         if (value != NULL && strcmp(value, known_token_value) == 0)
2303           Error(ERR_RETURN, "- dynamic token: '%s'", list->token);
2304       }
2305
2306       Error(ERR_RETURN_LINE, "-");
2307     }
2308
2309     if (unknown_tokens_found)
2310     {
2311       Error(ERR_RETURN_LINE, "-");
2312       Error(ERR_RETURN, "warning: unknown token(s) found in config file:");
2313       Error(ERR_RETURN, "- config file: '%s'", filename);
2314
2315       for (list = setup_file_list; list != NULL; list = list->next)
2316       {
2317         char *value = getHashEntry(extra_file_hash, list->token);
2318
2319         if (value != NULL && strcmp(value, known_token_value) != 0)
2320           Error(ERR_RETURN, "- dynamic token: '%s'", list->token);
2321       }
2322
2323       Error(ERR_RETURN_LINE, "-");
2324     }
2325
2326     if (undefined_values_found)
2327     {
2328       Error(ERR_RETURN_LINE, "-");
2329       Error(ERR_RETURN, "warning: undefined values found in config file:");
2330       Error(ERR_RETURN, "- config file: '%s'", filename);
2331
2332       for (list = setup_file_list; list != NULL; list = list->next)
2333       {
2334         char *value = getHashEntry(empty_file_hash, list->token);
2335
2336         if (value != NULL)
2337           Error(ERR_RETURN, "- undefined value for token: '%s'", list->token);
2338       }
2339
2340       Error(ERR_RETURN_LINE, "-");
2341     }
2342
2343     freeSetupFileList(setup_file_list);
2344   }
2345
2346   freeSetupFileHash(extra_file_hash);
2347   freeSetupFileHash(empty_file_hash);
2348
2349 #if 0
2350   for (i = 0; i < num_file_list_entries; i++)
2351   {
2352     printf("'%s' ", file_list[i].token);
2353     if (file_list[i].filename)
2354       printf("-> '%s'\n", file_list[i].filename);
2355     else
2356       printf("-> UNDEFINED [-> '%s']\n", file_list[i].default_filename);
2357   }
2358 #endif
2359 }
2360
2361 void LoadArtworkConfig(struct ArtworkListInfo *artwork_info)
2362 {
2363   struct FileInfo *file_list = artwork_info->file_list;
2364   int num_file_list_entries = artwork_info->num_file_list_entries;
2365   int num_suffix_list_entries = artwork_info->num_suffix_list_entries;
2366   char *filename_base = UNDEFINED_FILENAME, *filename_local;
2367   int i, j;
2368
2369 #if 0
2370   printf("GOT CUSTOM ARTWORK CONFIG FILE '%s'\n", filename);
2371 #endif
2372
2373   DrawInitText("Loading artwork config:", 120, FC_GREEN);
2374   DrawInitText(ARTWORKINFO_FILENAME(artwork_info->type), 150, FC_YELLOW);
2375
2376   /* always start with reliable default values */
2377   for (i = 0; i < num_file_list_entries; i++)
2378   {
2379     setString(&file_list[i].filename, file_list[i].default_filename);
2380
2381     for (j = 0; j < num_suffix_list_entries; j++)
2382       setString(&file_list[i].parameter[j], file_list[i].default_parameter[j]);
2383
2384     file_list[i].redefined = FALSE;
2385     file_list[i].fallback_to_default = FALSE;
2386   }
2387
2388   /* free previous dynamic artwork file array */
2389   if (artwork_info->dynamic_file_list != NULL)
2390   {
2391     for (i = 0; i < artwork_info->num_dynamic_file_list_entries; i++)
2392     {
2393       free(artwork_info->dynamic_file_list[i].token);
2394       free(artwork_info->dynamic_file_list[i].filename);
2395       free(artwork_info->dynamic_file_list[i].parameter);
2396     }
2397
2398     free(artwork_info->dynamic_file_list);
2399     artwork_info->dynamic_file_list = NULL;
2400
2401     FreeCustomArtworkList(artwork_info, &artwork_info->dynamic_artwork_list,
2402                           &artwork_info->num_dynamic_file_list_entries);
2403   }
2404
2405   /* free previous property mapping */
2406   if (artwork_info->property_mapping != NULL)
2407   {
2408     free(artwork_info->property_mapping);
2409
2410     artwork_info->property_mapping = NULL;
2411     artwork_info->num_property_mapping_entries = 0;
2412   }
2413
2414   if (!SETUP_OVERRIDE_ARTWORK(setup, artwork_info->type))
2415   {
2416     /* first look for special artwork configured in level series config */
2417     filename_base = getCustomArtworkLevelConfigFilename(artwork_info->type);
2418
2419     if (fileExists(filename_base))
2420       LoadArtworkConfigFromFilename(artwork_info, filename_base);
2421   }
2422
2423   filename_local = getCustomArtworkConfigFilename(artwork_info->type);
2424
2425   if (filename_local != NULL && strcmp(filename_base, filename_local) != 0)
2426     LoadArtworkConfigFromFilename(artwork_info, filename_local);
2427 }
2428
2429 static void deleteArtworkListEntry(struct ArtworkListInfo *artwork_info,
2430                                    struct ListNodeInfo **listnode)
2431 {
2432   if (*listnode)
2433   {
2434     char *filename = (*listnode)->source_filename;
2435
2436 #if 0
2437     printf("[decrementing reference counter of artwork '%s']\n", filename);
2438 #endif
2439
2440     if (--(*listnode)->num_references <= 0)
2441     {
2442 #if 0
2443       printf("[deleting artwork '%s']\n", filename);
2444 #endif
2445
2446       deleteNodeFromList(&artwork_info->content_list, filename,
2447                          artwork_info->free_artwork);
2448     }
2449
2450     *listnode = NULL;
2451   }
2452 }
2453
2454 #if 1
2455 static void replaceArtworkListEntry(struct ArtworkListInfo *artwork_info,
2456                                     struct ListNodeInfo **listnode,
2457                                     struct FileInfo *file_list_entry)
2458 {
2459   char *init_text[] =
2460   {
2461     "Loading graphics:",
2462     "Loading sounds:",
2463     "Loading music:"
2464   };
2465
2466   ListNode *node;
2467   char *basename = file_list_entry->filename;
2468   char *filename = getCustomArtworkFilename(basename, artwork_info->type);
2469
2470   if (filename == NULL)
2471   {
2472     Error(ERR_WARN, "cannot find artwork file '%s'", basename);
2473
2474     basename = file_list_entry->default_filename;
2475
2476     /* dynamic artwork has no default filename / skip empty default artwork */
2477     if (basename == NULL || strcmp(basename, UNDEFINED_FILENAME) == 0)
2478       return;
2479
2480     file_list_entry->fallback_to_default = TRUE;
2481
2482     Error(ERR_WARN, "trying default artwork file '%s'", basename);
2483
2484     filename = getCustomArtworkFilename(basename, artwork_info->type);
2485
2486     if (filename == NULL)
2487     {
2488       int error_mode = ERR_WARN;
2489
2490       /* we can get away without sounds and music, but not without graphics */
2491       if (*listnode == NULL && artwork_info->type == ARTWORK_TYPE_GRAPHICS)
2492         error_mode = ERR_EXIT;
2493
2494       Error(error_mode, "cannot find default artwork file '%s'", basename);
2495
2496       return;
2497     }
2498   }
2499
2500   /* check if the old and the new artwork file are the same */
2501   if (*listnode && strcmp((*listnode)->source_filename, filename) == 0)
2502   {
2503     /* The old and new artwork are the same (have the same filename and path).
2504        This usually means that this artwork does not exist in this artwork set
2505        and a fallback to the existing artwork is done. */
2506
2507 #if 0
2508     printf("[artwork '%s' already exists (same list entry)]\n", filename);
2509 #endif
2510
2511     return;
2512   }
2513
2514   /* delete existing artwork file entry */
2515   deleteArtworkListEntry(artwork_info, listnode);
2516
2517   /* check if the new artwork file already exists in the list of artworks */
2518   if ((node = getNodeFromKey(artwork_info->content_list, filename)) != NULL)
2519   {
2520 #if 0
2521       printf("[artwork '%s' already exists (other list entry)]\n", filename);
2522 #endif
2523
2524       *listnode = (struct ListNodeInfo *)node->content;
2525       (*listnode)->num_references++;
2526
2527       return;
2528   }
2529
2530 #if 0
2531   printf("::: %s: '%s'\n", init_text[artwork_info->type], basename);
2532 #endif
2533
2534   DrawInitText(init_text[artwork_info->type], 120, FC_GREEN);
2535   DrawInitText(basename, 150, FC_YELLOW);
2536
2537   if ((*listnode = artwork_info->load_artwork(filename)) != NULL)
2538   {
2539 #if 0
2540       printf("[adding new artwork '%s']\n", filename);
2541 #endif
2542
2543     (*listnode)->num_references = 1;
2544     addNodeToList(&artwork_info->content_list, (*listnode)->source_filename,
2545                   *listnode);
2546   }
2547   else
2548   {
2549     int error_mode = ERR_WARN;
2550
2551 #if 1
2552     /* we can get away without sounds and music, but not without graphics */
2553     if (artwork_info->type == ARTWORK_TYPE_GRAPHICS)
2554       error_mode = ERR_EXIT;
2555 #endif
2556
2557     Error(error_mode, "cannot load artwork file '%s'", basename);
2558     return;
2559   }
2560 }
2561
2562 #else
2563
2564 static void replaceArtworkListEntry(struct ArtworkListInfo *artwork_info,
2565                                     struct ListNodeInfo **listnode,
2566                                     char *basename)
2567 {
2568   char *init_text[] =
2569   {
2570     "Loading graphics:",
2571     "Loading sounds:",
2572     "Loading music:"
2573   };
2574
2575   ListNode *node;
2576   char *filename = getCustomArtworkFilename(basename, artwork_info->type);
2577
2578   if (filename == NULL)
2579   {
2580     int error_mode = ERR_WARN;
2581
2582 #if 1
2583     /* !!! NEW ARTWORK FALLBACK CODE !!! NEARLY UNTESTED !!! */
2584     /* before failing, try fallback to default artwork */
2585 #else
2586     /* we can get away without sounds and music, but not without graphics */
2587     if (*listnode == NULL && artwork_info->type == ARTWORK_TYPE_GRAPHICS)
2588       error_mode = ERR_EXIT;
2589 #endif
2590
2591     Error(error_mode, "cannot find artwork file '%s'", basename);
2592     return;
2593   }
2594
2595   /* check if the old and the new artwork file are the same */
2596   if (*listnode && strcmp((*listnode)->source_filename, filename) == 0)
2597   {
2598     /* The old and new artwork are the same (have the same filename and path).
2599        This usually means that this artwork does not exist in this artwork set
2600        and a fallback to the existing artwork is done. */
2601
2602 #if 0
2603     printf("[artwork '%s' already exists (same list entry)]\n", filename);
2604 #endif
2605
2606     return;
2607   }
2608
2609   /* delete existing artwork file entry */
2610   deleteArtworkListEntry(artwork_info, listnode);
2611
2612   /* check if the new artwork file already exists in the list of artworks */
2613   if ((node = getNodeFromKey(artwork_info->content_list, filename)) != NULL)
2614   {
2615 #if 0
2616       printf("[artwork '%s' already exists (other list entry)]\n", filename);
2617 #endif
2618
2619       *listnode = (struct ListNodeInfo *)node->content;
2620       (*listnode)->num_references++;
2621
2622       return;
2623   }
2624
2625 #if 0
2626   printf("::: %s: '%s'\n", init_text[artwork_info->type], basename);
2627 #endif
2628
2629   DrawInitText(init_text[artwork_info->type], 120, FC_GREEN);
2630   DrawInitText(basename, 150, FC_YELLOW);
2631
2632   if ((*listnode = artwork_info->load_artwork(filename)) != NULL)
2633   {
2634 #if 0
2635       printf("[adding new artwork '%s']\n", filename);
2636 #endif
2637
2638     (*listnode)->num_references = 1;
2639     addNodeToList(&artwork_info->content_list, (*listnode)->source_filename,
2640                   *listnode);
2641   }
2642   else
2643   {
2644     int error_mode = ERR_WARN;
2645
2646 #if 1
2647     /* we can get away without sounds and music, but not without graphics */
2648     if (artwork_info->type == ARTWORK_TYPE_GRAPHICS)
2649       error_mode = ERR_EXIT;
2650 #endif
2651
2652     Error(error_mode, "cannot load artwork file '%s'", basename);
2653     return;
2654   }
2655 }
2656 #endif
2657
2658 #if 1
2659 static void LoadCustomArtwork(struct ArtworkListInfo *artwork_info,
2660                               struct ListNodeInfo **listnode,
2661                               struct FileInfo *file_list_entry)
2662 {
2663 #if 0
2664   printf("GOT CUSTOM ARTWORK FILE '%s'\n", filename);
2665 #endif
2666
2667   if (strcmp(file_list_entry->filename, UNDEFINED_FILENAME) == 0)
2668   {
2669     deleteArtworkListEntry(artwork_info, listnode);
2670     return;
2671   }
2672
2673   replaceArtworkListEntry(artwork_info, listnode, file_list_entry);
2674 }
2675
2676 #else
2677
2678 static void LoadCustomArtwork(struct ArtworkListInfo *artwork_info,
2679                               struct ListNodeInfo **listnode,
2680                               char *basename)
2681 {
2682 #if 0
2683   printf("GOT CUSTOM ARTWORK FILE '%s'\n", filename);
2684 #endif
2685
2686   if (strcmp(basename, UNDEFINED_FILENAME) == 0)
2687   {
2688     deleteArtworkListEntry(artwork_info, listnode);
2689     return;
2690   }
2691
2692   replaceArtworkListEntry(artwork_info, listnode, basename);
2693 }
2694 #endif
2695
2696 #if 1
2697 static void LoadArtworkToList(struct ArtworkListInfo *artwork_info,
2698                               struct ListNodeInfo **listnode,
2699                               struct FileInfo *file_list_entry)
2700 {
2701 #if 0
2702   if (artwork_info->artwork_list == NULL ||
2703       list_pos >= artwork_info->num_file_list_entries)
2704     return;
2705 #endif
2706
2707 #if 0
2708   printf("loading artwork '%s' ...  [%d]\n",
2709          basename, getNumNodes(artwork_info->content_list));
2710 #endif
2711
2712 #if 1
2713   LoadCustomArtwork(artwork_info, listnode, file_list_entry);
2714 #else
2715   LoadCustomArtwork(artwork_info, &artwork_info->artwork_list[list_pos],
2716                     basename);
2717 #endif
2718
2719 #if 0
2720   printf("loading artwork '%s' done [%d]\n",
2721          basename, getNumNodes(artwork_info->content_list));
2722 #endif
2723 }
2724
2725 #else
2726
2727 static void LoadArtworkToList(struct ArtworkListInfo *artwork_info,
2728                               struct ListNodeInfo **listnode,
2729                               char *basename, int list_pos)
2730 {
2731 #if 0
2732   if (artwork_info->artwork_list == NULL ||
2733       list_pos >= artwork_info->num_file_list_entries)
2734     return;
2735 #endif
2736
2737 #if 0
2738   printf("loading artwork '%s' ...  [%d]\n",
2739          basename, getNumNodes(artwork_info->content_list));
2740 #endif
2741
2742 #if 1
2743   LoadCustomArtwork(artwork_info, listnode, basename);
2744 #else
2745   LoadCustomArtwork(artwork_info, &artwork_info->artwork_list[list_pos],
2746                     basename);
2747 #endif
2748
2749 #if 0
2750   printf("loading artwork '%s' done [%d]\n",
2751          basename, getNumNodes(artwork_info->content_list));
2752 #endif
2753 }
2754 #endif
2755
2756 void ReloadCustomArtworkList(struct ArtworkListInfo *artwork_info)
2757 {
2758   struct FileInfo *file_list = artwork_info->file_list;
2759   struct FileInfo *dynamic_file_list = artwork_info->dynamic_file_list;
2760   int num_file_list_entries = artwork_info->num_file_list_entries;
2761   int num_dynamic_file_list_entries =
2762     artwork_info->num_dynamic_file_list_entries;
2763   int i;
2764
2765 #if 0
2766   printf("DEBUG: reloading %d static artwork files ...\n",
2767          num_file_list_entries);
2768 #endif
2769
2770   for (i = 0; i < num_file_list_entries; i++)
2771   {
2772 #if 0
2773     if (strcmp(file_list[i].token, "background") == 0)
2774       printf("::: '%s' -> '%s'\n", file_list[i].token, file_list[i].filename);
2775 #endif
2776
2777 #if 1
2778     LoadArtworkToList(artwork_info, &artwork_info->artwork_list[i],
2779                       &file_list[i]);
2780 #else
2781     LoadArtworkToList(artwork_info, &artwork_info->artwork_list[i],
2782                       file_list[i].filename, i);
2783 #endif
2784
2785 #if 0
2786     /* !!! NEW ARTWORK FALLBACK CODE !!! NEARLY UNTESTED !!! */
2787     if (artwork_info->artwork_list[i] == NULL &&
2788         strcmp(file_list[i].filename, UNDEFINED_FILENAME) != 0 &&
2789         strcmp(file_list[i].default_filename, file_list[i].filename) != 0 &&
2790         strcmp(file_list[i].default_filename, UNDEFINED_FILENAME) != 0)
2791     {
2792       Error(ERR_WARN, "trying default artwork file '%s'",
2793             file_list[i].default_filename);
2794
2795       LoadArtworkToList(artwork_info, &artwork_info->artwork_list[i],
2796                         file_list[i].default_filename, i);
2797
2798       /* even the fallback to default artwork was not successful -- fail now */
2799       if (artwork_info->artwork_list[i] == NULL &&
2800           artwork_info->type == ARTWORK_TYPE_GRAPHICS)
2801         Error(ERR_EXIT, "cannot find artwork file '%s' or default file '%s'",
2802               file_list[i].filename, file_list[i].default_filename);
2803
2804       file_list[i].fallback_to_default = TRUE;
2805     }
2806 #endif
2807   }
2808
2809 #if 0
2810   printf("DEBUG: reloading %d dynamic artwork files ...\n",
2811          num_dynamic_file_list_entries);
2812 #endif
2813
2814   for (i = 0; i < num_dynamic_file_list_entries; i++)
2815   {
2816 #if 1
2817     LoadArtworkToList(artwork_info, &artwork_info->dynamic_artwork_list[i],
2818                       &dynamic_file_list[i]);
2819 #else
2820     LoadArtworkToList(artwork_info, &artwork_info->dynamic_artwork_list[i],
2821                       dynamic_file_list[i].filename, i);
2822 #endif
2823
2824 #if 0
2825     printf("::: '%s', '0x%08x'\n",
2826            dynamic_file_list[i].filename,
2827            dynamic_file_list[i].default_filename);
2828 #endif
2829
2830     /* dynamic artwork does not have default filename! */
2831   }
2832
2833 #if 0
2834   dumpList(artwork_info->content_list);
2835 #endif
2836 }
2837
2838 static void FreeCustomArtworkList(struct ArtworkListInfo *artwork_info,
2839                                   struct ListNodeInfo ***list,
2840                                   int *num_list_entries)
2841 {
2842   int i;
2843
2844   if (*list == NULL)
2845     return;
2846
2847   for (i = 0; i < *num_list_entries; i++)
2848     deleteArtworkListEntry(artwork_info, &(*list)[i]);
2849   free(*list);
2850
2851   *list = NULL;
2852   *num_list_entries = 0;
2853 }
2854
2855 void FreeCustomArtworkLists(struct ArtworkListInfo *artwork_info)
2856 {
2857   if (artwork_info == NULL)
2858     return;
2859
2860 #if 0
2861   printf("%s: FREEING ARTWORK ...\n",
2862          IS_CHILD_PROCESS() ? "CHILD" : "PARENT");
2863 #endif
2864
2865   FreeCustomArtworkList(artwork_info, &artwork_info->artwork_list,
2866                         &artwork_info->num_file_list_entries);
2867
2868   FreeCustomArtworkList(artwork_info, &artwork_info->dynamic_artwork_list,
2869                         &artwork_info->num_dynamic_file_list_entries);
2870
2871 #if 0
2872   printf("%s: FREEING ARTWORK -- DONE\n",
2873          IS_CHILD_PROCESS() ? "CHILD" : "PARENT");
2874 #endif
2875 }
2876
2877
2878 /* ------------------------------------------------------------------------- */
2879 /* functions only needed for non-Unix (non-command-line) systems             */
2880 /* (MS-DOS only; SDL/Windows creates files "stdout.txt" and "stderr.txt")    */
2881 /* ------------------------------------------------------------------------- */
2882
2883 #if defined(PLATFORM_MSDOS)
2884
2885 #define ERROR_FILENAME          "stderr.txt"
2886
2887 void initErrorFile()
2888 {
2889   unlink(ERROR_FILENAME);
2890 }
2891
2892 FILE *openErrorFile()
2893 {
2894   return fopen(ERROR_FILENAME, MODE_APPEND);
2895 }
2896
2897 void dumpErrorFile()
2898 {
2899   FILE *error_file = fopen(ERROR_FILENAME, MODE_READ);
2900
2901   if (error_file != NULL)
2902   {
2903     while (!feof(error_file))
2904       fputc(fgetc(error_file), stderr);
2905
2906     fclose(error_file);
2907   }
2908 }
2909 #endif
2910
2911
2912 /* ------------------------------------------------------------------------- */
2913 /* the following is only for debugging purpose and normally not used         */
2914 /* ------------------------------------------------------------------------- */
2915
2916 #define DEBUG_NUM_TIMESTAMPS    3
2917
2918 void debug_print_timestamp(int counter_nr, char *message)
2919 {
2920   static long counter[DEBUG_NUM_TIMESTAMPS][2];
2921
2922   if (counter_nr >= DEBUG_NUM_TIMESTAMPS)
2923     Error(ERR_EXIT, "debugging: increase DEBUG_NUM_TIMESTAMPS in misc.c");
2924
2925   counter[counter_nr][0] = Counter();
2926
2927   if (message)
2928     printf("%s %.2f seconds\n", message,
2929            (float)(counter[counter_nr][0] - counter[counter_nr][1]) / 1000);
2930
2931   counter[counter_nr][1] = Counter();
2932 }
2933
2934 void debug_print_parent_only(char *format, ...)
2935 {
2936   if (!IS_PARENT_PROCESS())
2937     return;
2938
2939   if (format)
2940   {
2941     va_list ap;
2942
2943     va_start(ap, format);
2944     vprintf(format, ap);
2945     va_end(ap);
2946
2947     printf("\n");
2948   }
2949 }