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