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