8719e145160eb018f0520edc92a5ba8ba494f16f
[rocksndiamonds.git] / src / libgame / misc.c
1 // ============================================================================
2 // Artsoft Retro-Game Library
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
5 //                  Holger Schemel
6 //                  info@artsoft.org
7 //                  https://www.artsoft.org/
8 // ----------------------------------------------------------------------------
9 // misc.c
10 // ============================================================================
11
12 #include <time.h>
13 #include <sys/time.h>
14 #include <sys/types.h>
15 #include <sys/stat.h>
16 #include <stdarg.h>
17 #include <ctype.h>
18 #include <string.h>
19 #include <unistd.h>
20 #include <errno.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 // ----------------------------------------------------------------------------
41 // logging functions
42 // ----------------------------------------------------------------------------
43
44 #define DUPLICATE_LOG_OUT_TO_STDOUT             TRUE
45 #define DUPLICATE_LOG_ERR_TO_STDERR             TRUE
46
47
48 #if defined(PLATFORM_ANDROID)
49 static int android_log_prio = ANDROID_LOG_INFO;
50 static char *android_log_buffer = NULL;
51
52 static void append_to_android_log_buffer(char *format, va_list ap)
53 {
54   char text_new[MAX_OUTPUT_LINESIZE];
55
56   // print text to temporary string
57   vsnprintf(text_new, MAX_OUTPUT_LINESIZE, format, ap);
58
59   if (android_log_buffer == NULL)
60   {
61     android_log_buffer = getStringCopy(text_new);
62   }
63   else
64   {
65     char *android_log_buffer_old = android_log_buffer;
66
67     // append new text to existing text
68     android_log_buffer = getStringCat2(android_log_buffer, text_new);
69
70     checked_free(android_log_buffer_old);
71   }
72 }
73
74 static void vprintf_log_nonewline(char *format, va_list ap)
75 {
76   // add log output to buffer until text with newline is printed
77   append_to_android_log_buffer(format, ap);
78 }
79
80 static void vprintf_log(char *format, va_list ap)
81 {
82   // add log output to buffer
83   append_to_android_log_buffer(format, ap);
84
85   // __android_log_vprint(android_log_prio, program.program_title, format, ap);
86   __android_log_write(android_log_prio, program.program_title,
87                       android_log_buffer);
88
89   checked_free(android_log_buffer);
90   android_log_buffer = NULL;
91 }
92
93 #else
94
95 static void vprintf_log_nonewline(char *format, va_list ap)
96 {
97   FILE *file = program.log_file[LOG_ERR_ID];
98
99 #if DUPLICATE_LOG_ERR_TO_STDERR
100   if (file != program.log_file_default[LOG_ERR_ID])
101   {
102     va_list ap2;
103     va_copy(ap2, ap);
104
105     vfprintf(program.log_file_default[LOG_ERR_ID], format, ap2);
106
107     va_end(ap2);
108   }
109 #endif
110
111   vfprintf(file, format, ap);
112 }
113
114 static void vprintf_log(char *format, va_list ap)
115 {
116   FILE *file = program.log_file[LOG_ERR_ID];
117   char *newline = STRING_NEWLINE;
118
119 #if DUPLICATE_LOG_ERR_TO_STDERR
120   if (file != program.log_file_default[LOG_ERR_ID])
121   {
122     va_list ap2;
123     va_copy(ap2, ap);
124
125     vfprintf(program.log_file_default[LOG_ERR_ID], format, ap2);
126     fprintf(program.log_file_default[LOG_ERR_ID], "%s", newline);
127
128     va_end(ap2);
129   }
130 #endif
131
132   vfprintf(file, format, ap);
133   fprintf(file, "%s", newline);
134 }
135 #endif
136
137 static void printf_log_nonewline(char *format, ...)
138 {
139   va_list ap;
140
141   va_start(ap, format);
142   vprintf_log_nonewline(format, ap);
143   va_end(ap);
144 }
145
146 static void printf_log(char *format, ...)
147 {
148   va_list ap;
149
150   va_start(ap, format);
151   vprintf_log(format, ap);
152   va_end(ap);
153 }
154
155 static void printf_log_line(char *line_chars, int line_length)
156 {
157   int i;
158
159   for (i = 0; i < line_length; i++)
160     printf_log_nonewline("%s", line_chars);
161
162   printf_log("");
163 }
164
165
166 // ----------------------------------------------------------------------------
167 // platform independent wrappers for printf() et al.
168 // ----------------------------------------------------------------------------
169
170 void fprintf_line(FILE *file, char *line_chars, int line_length)
171 {
172   int i;
173
174   for (i = 0; i < line_length; i++)
175     fprintf(file, "%s", line_chars);
176
177   fprintf(file, "\n");
178 }
179
180 void fprintf_line_with_prefix(FILE *file, char *prefix, char *line_chars,
181                               int line_length)
182 {
183   fprintf(file, "%s", prefix);
184   fprintf_line(file, line_chars, line_length);
185 }
186
187 void printf_line(char *line_chars, int line_length)
188 {
189   fprintf_line(stdout, line_chars, line_length);
190 }
191
192 void printf_line_with_prefix(char *prefix, char *line_chars, int line_length)
193 {
194   fprintf_line_with_prefix(stdout, prefix, line_chars, line_length);
195 }
196
197 static void vPrint(char *format, va_list ap)
198 {
199   FILE *file = program.log_file[LOG_OUT_ID];
200
201 #if DUPLICATE_LOG_OUT_TO_STDOUT
202   if (file != program.log_file_default[LOG_OUT_ID])
203   {
204     va_list ap2;
205     va_copy(ap2, ap);
206
207     vfprintf(program.log_file_default[LOG_OUT_ID], format, ap2);
208
209     va_end(ap2);
210   }
211 #endif
212
213   vfprintf(file, format, ap);
214 }
215
216 void Print(char *format, ...)
217 {
218   va_list ap;
219
220   va_start(ap, format);
221   vPrint(format, ap);
222   va_end(ap);
223 }
224
225 void PrintNoLog(char *format, ...)
226 {
227   FILE *file = program.log_file_default[LOG_OUT_ID];
228   va_list ap;
229
230   va_start(ap, format);
231   vfprintf(file, format, ap);
232   va_end(ap);
233
234   fflush(file);
235 }
236
237 void PrintLine(char *line_chars, int line_length)
238 {
239   int i;
240
241   for (i = 0; i < line_length; i++)
242     Print(line_chars);
243
244   Print("\n");
245 }
246
247 void PrintLineWithPrefix(char *prefix, char *line_chars, int line_length)
248 {
249   Print(prefix);
250   PrintLine(line_chars, line_length);
251 }
252
253
254 // ----------------------------------------------------------------------------
255 // generic logging functions
256 // ----------------------------------------------------------------------------
257
258 enum log_levels
259 {
260   LOG_UNKNOWN = 0,
261   LOG_DEBUG,
262   LOG_INFO,
263   LOG_WARN,
264   LOG_ERROR,
265   LOG_FATAL
266 };
267
268 static char *log_tokens[] =
269 {
270   "UNKNOWN",
271   "DEBUG",
272   "INFO",
273   "WARN",
274   "ERROR",
275   "FATAL"
276 };
277
278 static void printf_log_prefix(int log_level, char *mode)
279 {
280   if (log_level < 0 || log_level > LOG_FATAL)
281     return;
282
283   char *log_token = log_tokens[log_level];
284
285   if (log_level == LOG_DEBUG)
286     printf_log_nonewline("[%s] [%s] ", log_token, mode);
287   else
288     printf_log_nonewline("[%s] ", log_token);
289 }
290
291 static void Log(int log_level, char *mode, char *format, va_list ap)
292 {
293   if (log_level < 0 || log_level > LOG_FATAL)
294     return;
295
296 #if defined(PLATFORM_ANDROID)
297   android_log_prio = (log_level == LOG_DEBUG ? ANDROID_LOG_DEBUG :
298                       log_level == LOG_INFO  ? ANDROID_LOG_INFO :
299                       log_level == LOG_WARN  ? ANDROID_LOG_WARN :
300                       log_level == LOG_ERROR ? ANDROID_LOG_ERROR :
301                       log_level == LOG_FATAL ? ANDROID_LOG_FATAL :
302                       ANDROID_LOG_UNKNOWN);
303 #endif
304
305   static boolean last_line_was_separator = FALSE;
306   char *log_token = log_tokens[log_level];
307
308   if (strEqual(format, "===") ||
309       strEqual(format, "---"))
310   {
311     static char *mode_last = NULL;
312     char line_char[2] = { format[0], '\0' };
313     int line_length = 80 - strlen(log_token) - 3;
314
315     if (log_level == LOG_DEBUG)
316       line_length -= strlen(mode) + 3;
317
318     if (last_line_was_separator && strEqual(mode, mode_last))
319       return;
320
321     printf_log_prefix(log_level, mode);
322     printf_log_line(line_char, line_length);
323
324     if (!strEqual(mode, mode_last))
325       setString(&mode_last, mode);
326
327     last_line_was_separator = TRUE;
328
329     return;
330   }
331
332   last_line_was_separator = FALSE;
333
334   printf_log_prefix(log_level, mode);
335
336   vprintf_log(format, ap);
337 }
338
339 void Debug(char *mode, char *format, ...)
340 {
341   va_list ap;
342
343   if (!options.debug)
344     return;
345
346   // if optional debug mode specified, limit debug output accordingly
347   if (options.debug_mode != NULL &&
348       !strEqual(options.debug_mode, mode))
349     return;
350
351   va_start(ap, format);
352   Log(LOG_DEBUG, mode, format, ap);
353   va_end(ap);
354 }
355
356 void Info(char *format, ...)
357 {
358   va_list ap;
359
360   va_start(ap, format);
361   Log(LOG_INFO, NULL, format, ap);
362   va_end(ap);
363 }
364
365 void Warn(char *format, ...)
366 {
367   va_list ap;
368
369   va_start(ap, format);
370   Log(LOG_WARN, NULL, format, ap);
371   va_end(ap);
372 }
373
374
375 // ----------------------------------------------------------------------------
376 // string functions
377 // ----------------------------------------------------------------------------
378
379 /* int2str() returns a number converted to a string;
380    the used memory is static, but will be overwritten by later calls,
381    so if you want to save the result, copy it to a private string buffer;
382    there can be 10 local calls of int2str() without buffering the result --
383    the 11th call will then destroy the result from the first call and so on. */
384
385 char *int2str(int number, int size)
386 {
387   static char shift_array[10][40];
388   static int shift_counter = 0;
389   char *s = shift_array[shift_counter];
390
391   shift_counter = (shift_counter + 1) % 10;
392
393   if (size > 20)
394     size = 20;
395
396   if (size > 0)
397   {
398     sprintf(s, "                    %09d", number);
399     return &s[strlen(s) - size];
400   }
401   else
402   {
403     sprintf(s, "%d", number);
404     return s;
405   }
406 }
407
408
409 // something similar to "int2str()" above, but allocates its own memory
410 // and has a different interface; we cannot use "itoa()", because this
411 // seems to be already defined when cross-compiling to the win32 target
412
413 char *i_to_a(unsigned int i)
414 {
415   static char *a = NULL;
416
417   checked_free(a);
418
419   if (i > 2147483647)   // yes, this is a kludge
420     i = 2147483647;
421
422   a = checked_malloc(10 + 1);
423
424   sprintf(a, "%d", i);
425
426   return a;
427 }
428
429
430 // calculate base-2 logarithm of argument (rounded down to integer;
431 // this function returns the number of the highest bit set in argument)
432
433 int log_2(unsigned int x)
434 {
435   int e = 0;
436
437   while ((1 << e) < x)
438   {
439     x -= (1 << e);      // for rounding down (rounding up: remove this line)
440     e++;
441   }
442
443   return e;
444 }
445
446 boolean getTokenValueFromString(char *string, char **token, char **value)
447 {
448   return getTokenValueFromSetupLine(string, token, value);
449 }
450
451
452 // ----------------------------------------------------------------------------
453 // counter functions
454 // ----------------------------------------------------------------------------
455
456 // maximal allowed length of a command line option
457 #define MAX_OPTION_LEN          256
458
459 static unsigned int getCurrentMS(void)
460 {
461   return SDL_GetTicks();
462 }
463
464 static unsigned int mainCounter(int mode)
465 {
466   static unsigned int base_ms = 0;
467   unsigned int current_ms;
468
469   // get current system milliseconds
470   current_ms = getCurrentMS();
471
472   // reset base timestamp in case of counter reset or wrap-around
473   if (mode == INIT_COUNTER || current_ms < base_ms)
474     base_ms = current_ms;
475
476   // return milliseconds since last counter reset
477   return current_ms - base_ms;
478 }
479
480 void InitCounter()              // set counter back to zero
481 {
482   mainCounter(INIT_COUNTER);
483 }
484
485 unsigned int Counter()  // get milliseconds since last call of InitCounter()
486 {
487   return mainCounter(READ_COUNTER);
488 }
489
490 static void sleep_milliseconds(unsigned int milliseconds_delay)
491 {
492   SDL_Delay(milliseconds_delay);
493 }
494
495 void Delay(unsigned int delay)  // Sleep specified number of milliseconds
496 {
497   sleep_milliseconds(delay);
498 }
499
500 boolean DelayReachedExt(unsigned int *counter_var, unsigned int delay,
501                         unsigned int actual_counter)
502 {
503   if (actual_counter >= *counter_var &&
504       actual_counter < *counter_var + delay)
505     return FALSE;
506
507   *counter_var = actual_counter;
508
509   return TRUE;
510 }
511
512 boolean FrameReached(unsigned int *frame_counter_var, unsigned int frame_delay)
513 {
514   return DelayReachedExt(frame_counter_var, frame_delay, FrameCounter);
515 }
516
517 boolean DelayReached(unsigned int *counter_var, unsigned int delay)
518 {
519   return DelayReachedExt(counter_var, delay, Counter());
520 }
521
522 void ResetDelayCounterExt(unsigned int *counter_var,
523                           unsigned int actual_counter)
524 {
525   DelayReachedExt(counter_var, 0, actual_counter);
526 }
527
528 void ResetFrameCounter(unsigned int *frame_counter_var)
529 {
530   FrameReached(frame_counter_var, 0);
531 }
532
533 void ResetDelayCounter(unsigned int *counter_var)
534 {
535   DelayReached(counter_var, 0);
536 }
537
538 int WaitUntilDelayReached(unsigned int *counter_var, unsigned int delay)
539 {
540   unsigned int actual_counter;
541   int skip_frames = 0;
542
543   while (1)
544   {
545     actual_counter = Counter();
546
547     if (actual_counter >= *counter_var &&
548         actual_counter < *counter_var + delay)
549       sleep_milliseconds((*counter_var + delay - actual_counter) / 2);
550     else
551       break;
552   }
553
554   if (*counter_var != 0 &&
555       delay != 0 &&
556       actual_counter >= *counter_var + delay)
557   {
558     int lag = actual_counter - (*counter_var + delay);
559     int delay2 = (delay + 1) / 2;
560
561     if (lag >= delay2)
562       skip_frames = (lag + delay2) / delay;
563   }
564
565   *counter_var = actual_counter;
566
567   return skip_frames;
568 }
569
570 void SkipUntilDelayReached(unsigned int *counter_var, unsigned int delay,
571                            int *loop_var, int last_loop_value)
572 {
573   int skip_frames = WaitUntilDelayReached(counter_var, delay);
574
575 #if 0
576 #if DEBUG
577   printf("::: %d: %d ms", *loop_var, delay);
578   if (skip_frames)
579     printf(" -> SKIP %d FRAME(S) [%d ms]", skip_frames, skip_frames * delay);
580   printf("\n");
581 #endif
582 #endif
583
584   if (skip_frames == 0)
585     return;
586
587   // when skipping frames, make sure to never skip the last frame, as
588   // this may be needed for animations to reach a defined end state;
589   // furthermore, we assume that this function is called at the end
590   // of a "for" loop, which continues by incrementing the loop variable
591   // by one before checking the loop condition again; therefore we have
592   // to check against the last loop value minus one here
593
594   last_loop_value--;
595
596   if (*loop_var < last_loop_value)      // never skip the last frame
597   {
598     *loop_var += skip_frames;
599
600     if (*loop_var > last_loop_value)    // never skip the last frame
601       *loop_var = last_loop_value;
602   }
603 }
604
605
606 // ----------------------------------------------------------------------------
607 // random generator functions
608 // ----------------------------------------------------------------------------
609
610 unsigned int init_random_number(int nr, int seed)
611 {
612   if (seed == NEW_RANDOMIZE)
613   {
614     // default random seed
615     seed = (int)time(NULL);                     // seconds since the epoch
616
617 #if !defined(PLATFORM_WIN32)
618     // add some more randomness
619     struct timeval current_time;
620
621     gettimeofday(&current_time, NULL);
622
623     seed += (int)current_time.tv_usec;          // microseconds since the epoch
624 #endif
625
626     // add some more randomness
627     seed += (int)SDL_GetTicks();                // milliseconds since SDL init
628
629     // add some more randomness
630     seed += GetSimpleRandom(1000000);
631   }
632
633   srandom_linux_libc(nr, (unsigned int) seed);
634
635   return (unsigned int) seed;
636 }
637
638 unsigned int get_random_number(int nr, int max)
639 {
640   return (max > 0 ? random_linux_libc(nr) % max : 0);
641 }
642
643
644 // ----------------------------------------------------------------------------
645 // system info functions
646 // ----------------------------------------------------------------------------
647
648 #if !defined(PLATFORM_ANDROID)
649 static char *get_corrected_real_name(char *real_name)
650 {
651   char *real_name_new = checked_malloc(MAX_USERNAME_LEN + 1);
652   char *from_ptr = real_name;
653   char *to_ptr   = real_name_new;
654
655   // copy the name string, but not more than MAX_USERNAME_LEN characters
656   while (*from_ptr && (int)(to_ptr - real_name_new) < MAX_USERNAME_LEN - 1)
657   {
658     // the name field read from "passwd" file may also contain additional
659     // user information, separated by commas, which will be removed here
660     if (*from_ptr == ',')
661       break;
662
663     // the user's real name may contain 'german sharp s' characters,
664     // which have no equivalent in upper case letters (used by our fonts)
665     if (*from_ptr == CHAR_BYTE_SHARP_S)
666     {
667       from_ptr++;
668       *to_ptr++ = 's';
669       *to_ptr++ = 's';
670     }
671     else
672       *to_ptr++ = *from_ptr++;
673   }
674
675   *to_ptr = '\0';
676
677   return real_name_new;
678 }
679 #endif
680
681 char *getLoginName(void)
682 {
683   static char *login_name = NULL;
684
685 #if defined(PLATFORM_WIN32)
686   if (login_name == NULL)
687   {
688     unsigned long buffer_size = MAX_USERNAME_LEN + 1;
689     login_name = checked_malloc(buffer_size);
690
691     if (GetUserName(login_name, &buffer_size) == 0)
692       strcpy(login_name, ANONYMOUS_NAME);
693   }
694 #elif defined(PLATFORM_UNIX) && !defined(PLATFORM_ANDROID)
695   if (login_name == NULL)
696   {
697     struct passwd *pwd;
698
699     if ((pwd = getpwuid(getuid())) == NULL)
700       login_name = ANONYMOUS_NAME;
701     else
702       login_name = getStringCopy(pwd->pw_name);
703   }
704 #else
705   login_name = ANONYMOUS_NAME;
706 #endif
707
708   return login_name;
709 }
710
711 char *getRealName(void)
712 {
713   static char *real_name = NULL;
714
715 #if defined(PLATFORM_WIN32)
716   if (real_name == NULL)
717   {
718     static char buffer[MAX_USERNAME_LEN + 1];
719     unsigned long buffer_size = MAX_USERNAME_LEN + 1;
720
721     if (GetUserName(buffer, &buffer_size) != 0)
722       real_name = get_corrected_real_name(buffer);
723     else
724       real_name = ANONYMOUS_NAME;
725   }
726 #elif defined(PLATFORM_UNIX) && !defined(PLATFORM_ANDROID)
727   if (real_name == NULL)
728   {
729     struct passwd *pwd;
730
731     if ((pwd = getpwuid(getuid())) != NULL && strlen(pwd->pw_gecos) != 0)
732       real_name = get_corrected_real_name(pwd->pw_gecos);
733     else
734       real_name = ANONYMOUS_NAME;
735   }
736 #else
737   real_name = ANONYMOUS_NAME;
738 #endif
739
740   return real_name;
741 }
742
743 time_t getFileTimestampEpochSeconds(char *filename)
744 {
745   struct stat file_status;
746
747   if (stat(filename, &file_status) != 0)        // cannot stat file
748     return 0;
749
750   return file_status.st_mtime;
751 }
752
753
754 // ----------------------------------------------------------------------------
755 // path manipulation functions
756 // ----------------------------------------------------------------------------
757
758 static char *getLastPathSeparatorPtr(char *filename)
759 {
760   char *last_separator = strrchr(filename, CHAR_PATH_SEPARATOR_UNIX);
761
762   if (last_separator == NULL)   // also try DOS/Windows variant
763     last_separator = strrchr(filename, CHAR_PATH_SEPARATOR_DOS);
764
765   return last_separator;
766 }
767
768 char *getBaseNamePtr(char *filename)
769 {
770   char *last_separator = getLastPathSeparatorPtr(filename);
771
772   if (last_separator != NULL)
773     return last_separator + 1;  // separator found: strip base path
774   else
775     return filename;            // no separator found: filename has no path
776 }
777
778 char *getBaseName(char *filename)
779 {
780   return getStringCopy(getBaseNamePtr(filename));
781 }
782
783 char *getBaseNameNoSuffix(char *filename)
784 {
785   char *basename = getStringCopy(getBaseNamePtr(filename));
786
787   // remove trailing suffix (separated by dot or hyphen)
788   if (basename[0] != '.' && basename[0] != '-')
789   {
790     if (strchr(basename, '.') != NULL)
791       *strchr(basename, '.') = '\0';
792
793     if (strchr(basename, '-') != NULL)
794       *strchr(basename, '-') = '\0';
795   }
796
797   return basename;
798 }
799
800 char *getBasePath(char *filename)
801 {
802   char *basepath = getStringCopy(filename);
803   char *last_separator = getLastPathSeparatorPtr(basepath);
804
805   // if no separator was found, use current directory
806   if (last_separator == NULL)
807   {
808     free(basepath);
809
810     return getStringCopy(".");
811   }
812
813   // separator found: strip basename
814   *last_separator = '\0';
815
816   return basepath;
817 }
818
819
820 // ----------------------------------------------------------------------------
821 // various string functions
822 // ----------------------------------------------------------------------------
823
824 char *getStringCat2WithSeparator(char *s1, char *s2, char *sep)
825 {
826   if (s1 == NULL || s2 == NULL || sep == NULL)
827     return NULL;
828
829   char *complete_string = checked_malloc(strlen(s1) + strlen(sep) +
830                                          strlen(s2) + 1);
831
832   sprintf(complete_string, "%s%s%s", s1, sep, s2);
833
834   return complete_string;
835 }
836
837 char *getStringCat3WithSeparator(char *s1, char *s2, char *s3, char *sep)
838 {
839   if (s1 == NULL || s2 == NULL || s3 == NULL || sep == NULL)
840     return NULL;
841
842   char *complete_string = checked_malloc(strlen(s1) + strlen(sep) +
843                                          strlen(s2) + strlen(sep) +
844                                          strlen(s3) + 1);
845
846   sprintf(complete_string, "%s%s%s%s%s", s1, sep, s2, sep, s3);
847
848   return complete_string;
849 }
850
851 char *getStringCat2(char *s1, char *s2)
852 {
853   return getStringCat2WithSeparator(s1, s2, "");
854 }
855
856 char *getStringCat3(char *s1, char *s2, char *s3)
857 {
858   return getStringCat3WithSeparator(s1, s2, s3, "");
859 }
860
861 char *getPath2(char *path1, char *path2)
862 {
863 #if defined(PLATFORM_ANDROID)
864   // workaround for reading from assets directory -- skip "." subdirs in path
865   if (strEqual(path1, "."))
866     return getStringCopy(path2);
867   else if (strEqual(path2, "."))
868     return getStringCopy(path1);
869 #endif
870
871   return getStringCat2WithSeparator(path1, path2, STRING_PATH_SEPARATOR);
872 }
873
874 char *getPath3(char *path1, char *path2, char *path3)
875 {
876 #if defined(PLATFORM_ANDROID)
877   // workaround for reading from assets directory -- skip "." subdirs in path
878   if (strEqual(path1, "."))
879     return getStringCat2WithSeparator(path2, path3, STRING_PATH_SEPARATOR);
880   else if (strEqual(path2, "."))
881     return getStringCat2WithSeparator(path1, path3, STRING_PATH_SEPARATOR);
882   else if (strEqual(path3, "."))
883     return getStringCat2WithSeparator(path1, path2, STRING_PATH_SEPARATOR);
884 #endif
885
886   return getStringCat3WithSeparator(path1, path2, path3, STRING_PATH_SEPARATOR);
887 }
888
889 static char *getPngOrPcxIfNotExists(char *filename)
890 {
891   // switch from PNG to PCX file and vice versa, if file does not exist
892   // (backwards compatibility with PCX files used in previous versions)
893
894   if (!fileExists(filename) && strSuffix(filename, ".png"))
895     strcpy(&filename[strlen(filename) - 3], "pcx");
896   else if (!fileExists(filename) && strSuffix(filename, ".pcx"))
897     strcpy(&filename[strlen(filename) - 3], "png");
898
899   return filename;
900 }
901
902 char *getImg2(char *path1, char *path2)
903 {
904   return getPngOrPcxIfNotExists(getPath2(path1, path2));
905 }
906
907 char *getImg3(char *path1, char *path2, char *path3)
908 {
909   return getPngOrPcxIfNotExists(getPath3(path1, path2, path3));
910 }
911
912 char *getStringCopy(const char *s)
913 {
914   char *s_copy;
915
916   if (s == NULL)
917     return NULL;
918
919   s_copy = checked_malloc(strlen(s) + 1);
920   strcpy(s_copy, s);
921
922   return s_copy;
923 }
924
925 char *getStringCopyN(const char *s, int n)
926 {
927   char *s_copy;
928   int s_len = MAX(0, n);
929
930   if (s == NULL)
931     return NULL;
932
933   s_copy = checked_malloc(s_len + 1);
934   strncpy(s_copy, s, s_len);
935   s_copy[s_len] = '\0';
936
937   return s_copy;
938 }
939
940 char *getStringCopyNStatic(const char *s, int n)
941 {
942   static char *s_copy = NULL;
943
944   checked_free(s_copy);
945
946   s_copy = getStringCopyN(s, n);
947
948   return s_copy;
949 }
950
951 char *getStringToLower(const char *s)
952 {
953   char *s_copy = checked_malloc(strlen(s) + 1);
954   char *s_ptr = s_copy;
955
956   while (*s)
957     *s_ptr++ = tolower(*s++);
958   *s_ptr = '\0';
959
960   return s_copy;
961 }
962
963 void setString(char **old_value, char *new_value)
964 {
965   checked_free(*old_value);
966
967   *old_value = getStringCopy(new_value);
968 }
969
970 boolean strEqual(char *s1, char *s2)
971 {
972   return (s1 == NULL && s2 == NULL ? TRUE  :
973           s1 == NULL && s2 != NULL ? FALSE :
974           s1 != NULL && s2 == NULL ? FALSE :
975           strcmp(s1, s2) == 0);
976 }
977
978 boolean strEqualN(char *s1, char *s2, int n)
979 {
980   return (s1 == NULL && s2 == NULL ? TRUE  :
981           s1 == NULL && s2 != NULL ? FALSE :
982           s1 != NULL && s2 == NULL ? FALSE :
983           strncmp(s1, s2, n) == 0);
984 }
985
986 boolean strPrefix(char *s, char *prefix)
987 {
988   return (s == NULL && prefix == NULL ? TRUE  :
989           s == NULL && prefix != NULL ? FALSE :
990           s != NULL && prefix == NULL ? FALSE :
991           strncmp(s, prefix, strlen(prefix)) == 0);
992 }
993
994 boolean strSuffix(char *s, char *suffix)
995 {
996   return (s == NULL && suffix == NULL ? TRUE  :
997           s == NULL && suffix != NULL ? FALSE :
998           s != NULL && suffix == NULL ? FALSE :
999           strlen(s) < strlen(suffix)  ? FALSE :
1000           strcmp(&s[strlen(s) - strlen(suffix)], suffix) == 0);
1001 }
1002
1003 boolean strPrefixLower(char *s, char *prefix)
1004 {
1005   char *s_lower = getStringToLower(s);
1006   boolean match = strPrefix(s_lower, prefix);
1007
1008   free(s_lower);
1009
1010   return match;
1011 }
1012
1013 boolean strSuffixLower(char *s, char *suffix)
1014 {
1015   char *s_lower = getStringToLower(s);
1016   boolean match = strSuffix(s_lower, suffix);
1017
1018   free(s_lower);
1019
1020   return match;
1021 }
1022
1023
1024 // ----------------------------------------------------------------------------
1025 // command line option handling functions
1026 // ----------------------------------------------------------------------------
1027
1028 void GetOptions(int argc, char *argv[],
1029                 void (*print_usage_function)(void),
1030                 void (*print_version_function)(void))
1031 {
1032   char *ro_base_path = getProgramMainDataPath(argv[0], RO_BASE_PATH);
1033   char *rw_base_path = getProgramMainDataPath(argv[0], RW_BASE_PATH);
1034   char **argvplus = checked_calloc((argc + 1) * sizeof(char **));
1035   char **options_left = &argvplus[1];
1036
1037   // replace original "argv" with null-terminated array of string pointers
1038   while (argc--)
1039     argvplus[argc] = argv[argc];
1040
1041   // initialize global program options
1042   options.server_host = NULL;
1043   options.server_port = 0;
1044
1045   options.ro_base_directory = ro_base_path;
1046   options.rw_base_directory = rw_base_path;
1047   options.level_directory    = getPath2(ro_base_path, LEVELS_DIRECTORY);
1048   options.graphics_directory = getPath2(ro_base_path, GRAPHICS_DIRECTORY);
1049   options.sounds_directory   = getPath2(ro_base_path, SOUNDS_DIRECTORY);
1050   options.music_directory    = getPath2(ro_base_path, MUSIC_DIRECTORY);
1051   options.docs_directory     = getPath2(ro_base_path, DOCS_DIRECTORY);
1052   options.conf_directory     = getPath2(ro_base_path, CONF_DIRECTORY);
1053
1054   options.execute_command = NULL;
1055   options.special_flags = NULL;
1056   options.debug_mode = NULL;
1057
1058   options.mytapes = FALSE;
1059   options.serveronly = FALSE;
1060   options.network = FALSE;
1061   options.verbose = FALSE;
1062   options.debug = FALSE;
1063
1064 #if 1
1065   options.verbose = TRUE;
1066 #else
1067 #if !defined(PLATFORM_UNIX)
1068   if (*options_left == NULL)    // no options given -- enable verbose mode
1069     options.verbose = TRUE;
1070 #endif
1071 #endif
1072
1073 #if DEBUG
1074 #if defined(PLATFORM_ANDROID)
1075   options.debug = TRUE;
1076 #endif
1077 #endif
1078
1079   while (*options_left)
1080   {
1081     char option_str[MAX_OPTION_LEN];
1082     char *option = options_left[0];
1083     char *next_option = options_left[1];
1084     char *option_arg = NULL;
1085     int option_len = strlen(option);
1086
1087     if (option_len >= MAX_OPTION_LEN)
1088       Error(ERR_EXIT_HELP, "unrecognized option '%s'", option);
1089
1090     strcpy(option_str, option);                 // copy argument into buffer
1091     option = option_str;
1092
1093     if (strEqual(option, "--"))                 // stop scanning arguments
1094       break;
1095
1096     if (strPrefix(option, "--"))                // treat '--' like '-'
1097       option++;
1098
1099     option_arg = strchr(option, '=');
1100     if (option_arg == NULL)                     // no '=' in option
1101       option_arg = next_option;
1102     else
1103     {
1104       *option_arg++ = '\0';                     // cut argument from option
1105       if (*option_arg == '\0')                  // no argument after '='
1106         Error(ERR_EXIT_HELP, "option '%s' has invalid argument", option_str);
1107     }
1108
1109     option_len = strlen(option);
1110
1111     if (strEqual(option, "-"))
1112     {
1113       Error(ERR_EXIT_HELP, "unrecognized option '%s'", option);
1114     }
1115     else if (strncmp(option, "-help", option_len) == 0)
1116     {
1117       print_usage_function();
1118
1119       exit(0);
1120     }
1121     else if (strncmp(option, "-basepath", option_len) == 0)
1122     {
1123       if (option_arg == NULL)
1124         Error(ERR_EXIT_HELP, "option '%s' requires an argument", option_str);
1125
1126       // this should be extended to separate options for ro and rw data
1127       options.ro_base_directory = ro_base_path = getStringCopy(option_arg);
1128       options.rw_base_directory = rw_base_path = getStringCopy(option_arg);
1129       if (option_arg == next_option)
1130         options_left++;
1131
1132       // adjust paths for sub-directories in base directory accordingly
1133       options.level_directory    = getPath2(ro_base_path, LEVELS_DIRECTORY);
1134       options.graphics_directory = getPath2(ro_base_path, GRAPHICS_DIRECTORY);
1135       options.sounds_directory   = getPath2(ro_base_path, SOUNDS_DIRECTORY);
1136       options.music_directory    = getPath2(ro_base_path, MUSIC_DIRECTORY);
1137       options.docs_directory     = getPath2(ro_base_path, DOCS_DIRECTORY);
1138       options.conf_directory     = getPath2(ro_base_path, CONF_DIRECTORY);
1139     }
1140     else if (strncmp(option, "-levels", option_len) == 0)
1141     {
1142       if (option_arg == NULL)
1143         Error(ERR_EXIT_HELP, "option '%s' requires an argument", option_str);
1144
1145       options.level_directory = getStringCopy(option_arg);
1146       if (option_arg == next_option)
1147         options_left++;
1148     }
1149     else if (strncmp(option, "-graphics", option_len) == 0)
1150     {
1151       if (option_arg == NULL)
1152         Error(ERR_EXIT_HELP, "option '%s' requires an argument", option_str);
1153
1154       options.graphics_directory = getStringCopy(option_arg);
1155       if (option_arg == next_option)
1156         options_left++;
1157     }
1158     else if (strncmp(option, "-sounds", option_len) == 0)
1159     {
1160       if (option_arg == NULL)
1161         Error(ERR_EXIT_HELP, "option '%s' requires an argument", option_str);
1162
1163       options.sounds_directory = getStringCopy(option_arg);
1164       if (option_arg == next_option)
1165         options_left++;
1166     }
1167     else if (strncmp(option, "-music", option_len) == 0)
1168     {
1169       if (option_arg == NULL)
1170         Error(ERR_EXIT_HELP, "option '%s' requires an argument", option_str);
1171
1172       options.music_directory = getStringCopy(option_arg);
1173       if (option_arg == next_option)
1174         options_left++;
1175     }
1176     else if (strncmp(option, "-mytapes", option_len) == 0)
1177     {
1178       options.mytapes = TRUE;
1179     }
1180     else if (strncmp(option, "-network", option_len) == 0)
1181     {
1182       options.network = TRUE;
1183     }
1184     else if (strncmp(option, "-serveronly", option_len) == 0)
1185     {
1186       options.serveronly = TRUE;
1187     }
1188     else if (strncmp(option, "-debug", option_len) == 0)
1189     {
1190       options.debug = TRUE;
1191
1192       // optionally, debug output can be limited to a specific debug mode
1193       if (option_arg != next_option)
1194         options.debug_mode = getStringCopy(option_arg);
1195     }
1196     else if (strncmp(option, "-verbose", option_len) == 0)
1197     {
1198       options.verbose = TRUE;
1199     }
1200     else if (strncmp(option, "-version", option_len) == 0 ||
1201              strncmp(option, "-V", option_len) == 0)
1202     {
1203       print_version_function();
1204
1205       exit(0);
1206     }
1207     else if (strPrefix(option, "-D"))
1208     {
1209       options.special_flags = getStringCopy(&option[2]);
1210     }
1211     else if (strncmp(option, "-execute", option_len) == 0)
1212     {
1213       if (option_arg == NULL)
1214         Error(ERR_EXIT_HELP, "option '%s' requires an argument", option_str);
1215
1216       options.execute_command = getStringCopy(option_arg);
1217       if (option_arg == next_option)
1218         options_left++;
1219
1220       // when doing batch processing, always enable verbose mode (warnings)
1221       options.verbose = TRUE;
1222     }
1223 #if defined(PLATFORM_MACOSX)
1224     else if (strPrefix(option, "-psn"))
1225     {
1226       // ignore process serial number when launched via GUI on Mac OS X
1227     }
1228 #endif
1229     else if (*option == '-')
1230     {
1231       Error(ERR_EXIT_HELP, "unrecognized option '%s'", option_str);
1232     }
1233     else if (options.server_host == NULL)
1234     {
1235       options.server_host = *options_left;
1236     }
1237     else if (options.server_port == 0)
1238     {
1239       options.server_port = atoi(*options_left);
1240       if (options.server_port < 1024)
1241         Error(ERR_EXIT_HELP, "bad port number '%d'", options.server_port);
1242     }
1243     else
1244       Error(ERR_EXIT_HELP, "too many arguments");
1245
1246     options_left++;
1247   }
1248 }
1249
1250
1251 // ----------------------------------------------------------------------------
1252 // error handling functions
1253 // ----------------------------------------------------------------------------
1254
1255 #define MAX_INTERNAL_ERROR_SIZE         1024
1256
1257 // used by SetError() and GetError() to store internal error messages
1258 static char internal_error[MAX_INTERNAL_ERROR_SIZE];
1259
1260 void SetError(char *format, ...)
1261 {
1262   va_list ap;
1263
1264   va_start(ap, format);
1265   vsnprintf(internal_error, MAX_INTERNAL_ERROR_SIZE, format, ap);
1266   va_end(ap);
1267 }
1268
1269 char *GetError(void)
1270 {
1271   return internal_error;
1272 }
1273
1274 void Error(int mode, char *format, ...)
1275 {
1276   static boolean last_line_was_separator = FALSE;
1277   char *process_name = "";
1278
1279   if (program.log_file[LOG_ERR_ID] == NULL)
1280     return;
1281
1282 #if defined(PLATFORM_ANDROID)
1283   android_log_prio = (mode & ERR_DEBUG ? ANDROID_LOG_DEBUG :
1284                       mode & ERR_INFO ? ANDROID_LOG_INFO :
1285                       mode & ERR_WARN ? ANDROID_LOG_WARN :
1286                       mode & ERR_EXIT ? ANDROID_LOG_FATAL :
1287                       ANDROID_LOG_UNKNOWN);
1288 #endif
1289
1290   // display debug messages only when running in debug mode
1291   if (mode & ERR_DEBUG && !options.debug)
1292     return;
1293
1294   // display warnings only when running in verbose mode
1295   if (mode & ERR_WARN && !options.verbose)
1296     return;
1297
1298   if (mode == ERR_INFO_LINE)
1299   {
1300     if (!last_line_was_separator)
1301       printf_log_line(format, 79);
1302
1303     last_line_was_separator = TRUE;
1304
1305     return;
1306   }
1307
1308   last_line_was_separator = FALSE;
1309
1310   if (mode & ERR_SOUND_SERVER)
1311     process_name = " sound server";
1312   else if (mode & ERR_NETWORK_SERVER)
1313     process_name = " network server";
1314   else if (mode & ERR_NETWORK_CLIENT)
1315     process_name = " network client **";
1316
1317   if (format)
1318   {
1319 #if !defined(PLATFORM_ANDROID)
1320     printf_log_nonewline("%s%s: ", program.command_basename, process_name);
1321 #endif
1322
1323     if (mode & ERR_WARN)
1324       printf_log_nonewline("warning: ");
1325
1326     if (mode & ERR_EXIT)
1327       printf_log_nonewline("fatal error: ");
1328
1329     va_list ap;
1330
1331     va_start(ap, format);
1332     vprintf_log(format, ap);
1333     va_end(ap);
1334
1335     if ((mode & ERR_EXIT) && !(mode & ERR_FROM_SERVER))
1336     {
1337       va_start(ap, format);
1338       program.exit_message_function(format, ap);
1339       va_end(ap);
1340     }
1341   }
1342   
1343   if (mode & ERR_HELP)
1344     printf_log("%s: Try option '--help' for more information.",
1345                program.command_basename);
1346
1347   if (mode & ERR_EXIT)
1348     printf_log("%s%s: aborting", program.command_basename, process_name);
1349
1350   if (mode & ERR_EXIT)
1351   {
1352     if (mode & ERR_FROM_SERVER)
1353       exit(1);                          // child process: normal exit
1354     else
1355       program.exit_function(1);         // main process: clean up stuff
1356   }
1357 }
1358
1359
1360 // ----------------------------------------------------------------------------
1361 // checked memory allocation and freeing functions
1362 // ----------------------------------------------------------------------------
1363
1364 void *checked_malloc(unsigned int size)
1365 {
1366   void *ptr;
1367
1368   ptr = malloc(size);
1369
1370   if (ptr == NULL)
1371     Error(ERR_EXIT, "cannot allocate %d bytes -- out of memory", size);
1372
1373   return ptr;
1374 }
1375
1376 void *checked_calloc(unsigned int size)
1377 {
1378   void *ptr;
1379
1380   ptr = calloc(1, size);
1381
1382   if (ptr == NULL)
1383     Error(ERR_EXIT, "cannot allocate %d bytes -- out of memory", size);
1384
1385   return ptr;
1386 }
1387
1388 void *checked_realloc(void *ptr, unsigned int size)
1389 {
1390   ptr = realloc(ptr, size);
1391
1392   if (ptr == NULL)
1393     Error(ERR_EXIT, "cannot allocate %d bytes -- out of memory", size);
1394
1395   return ptr;
1396 }
1397
1398 void checked_free(void *ptr)
1399 {
1400   if (ptr != NULL)      // this check should be done by free() anyway
1401     free(ptr);
1402 }
1403
1404 void clear_mem(void *ptr, unsigned int size)
1405 {
1406 #if defined(PLATFORM_WIN32)
1407   // for unknown reason, memset() sometimes crashes when compiled with MinGW
1408   char *cptr = (char *)ptr;
1409
1410   while (size--)
1411     *cptr++ = 0;
1412 #else
1413   memset(ptr, 0, size);
1414 #endif
1415 }
1416
1417
1418 // ----------------------------------------------------------------------------
1419 // various helper functions
1420 // ----------------------------------------------------------------------------
1421
1422 void swap_numbers(int *i1, int *i2)
1423 {
1424   int help = *i1;
1425
1426   *i1 = *i2;
1427   *i2 = help;
1428 }
1429
1430 void swap_number_pairs(int *x1, int *y1, int *x2, int *y2)
1431 {
1432   int help_x = *x1;
1433   int help_y = *y1;
1434
1435   *x1 = *x2;
1436   *x2 = help_x;
1437
1438   *y1 = *y2;
1439   *y2 = help_y;
1440 }
1441
1442 /* the "put" variants of the following file access functions check for the file
1443    pointer being != NULL and return the number of bytes they have or would have
1444    written; this allows for chunk writing functions to first determine the size
1445    of the (not yet written) chunk, write the correct chunk size and finally
1446    write the chunk itself */
1447
1448 int getFile8BitInteger(File *file)
1449 {
1450   return getByteFromFile(file);
1451 }
1452
1453 int putFile8BitInteger(FILE *file, int value)
1454 {
1455   if (file != NULL)
1456     fputc(value, file);
1457
1458   return 1;
1459 }
1460
1461 int getFile16BitInteger(File *file, int byte_order)
1462 {
1463   if (byte_order == BYTE_ORDER_BIG_ENDIAN)
1464     return ((getByteFromFile(file) << 8) |
1465             (getByteFromFile(file) << 0));
1466   else           // BYTE_ORDER_LITTLE_ENDIAN
1467     return ((getByteFromFile(file) << 0) |
1468             (getByteFromFile(file) << 8));
1469 }
1470
1471 int putFile16BitInteger(FILE *file, int value, int byte_order)
1472 {
1473   if (file != NULL)
1474   {
1475     if (byte_order == BYTE_ORDER_BIG_ENDIAN)
1476     {
1477       fputc((value >> 8) & 0xff, file);
1478       fputc((value >> 0) & 0xff, file);
1479     }
1480     else           // BYTE_ORDER_LITTLE_ENDIAN
1481     {
1482       fputc((value >> 0) & 0xff, file);
1483       fputc((value >> 8) & 0xff, file);
1484     }
1485   }
1486
1487   return 2;
1488 }
1489
1490 int getFile32BitInteger(File *file, int byte_order)
1491 {
1492   if (byte_order == BYTE_ORDER_BIG_ENDIAN)
1493     return ((getByteFromFile(file) << 24) |
1494             (getByteFromFile(file) << 16) |
1495             (getByteFromFile(file) <<  8) |
1496             (getByteFromFile(file) <<  0));
1497   else           // BYTE_ORDER_LITTLE_ENDIAN
1498     return ((getByteFromFile(file) <<  0) |
1499             (getByteFromFile(file) <<  8) |
1500             (getByteFromFile(file) << 16) |
1501             (getByteFromFile(file) << 24));
1502 }
1503
1504 int putFile32BitInteger(FILE *file, int value, int byte_order)
1505 {
1506   if (file != NULL)
1507   {
1508     if (byte_order == BYTE_ORDER_BIG_ENDIAN)
1509     {
1510       fputc((value >> 24) & 0xff, file);
1511       fputc((value >> 16) & 0xff, file);
1512       fputc((value >>  8) & 0xff, file);
1513       fputc((value >>  0) & 0xff, file);
1514     }
1515     else           // BYTE_ORDER_LITTLE_ENDIAN
1516     {
1517       fputc((value >>  0) & 0xff, file);
1518       fputc((value >>  8) & 0xff, file);
1519       fputc((value >> 16) & 0xff, file);
1520       fputc((value >> 24) & 0xff, file);
1521     }
1522   }
1523
1524   return 4;
1525 }
1526
1527 boolean getFileChunk(File *file, char *chunk_name, int *chunk_size,
1528                      int byte_order)
1529 {
1530   const int chunk_name_length = 4;
1531
1532   // read chunk name
1533   if (getStringFromFile(file, chunk_name, chunk_name_length + 1) == NULL)
1534     return FALSE;
1535
1536   if (chunk_size != NULL)
1537   {
1538     // read chunk size
1539     *chunk_size = getFile32BitInteger(file, byte_order);
1540   }
1541
1542   return (checkEndOfFile(file) ? FALSE : TRUE);
1543 }
1544
1545 int putFileChunk(FILE *file, char *chunk_name, int chunk_size,
1546                  int byte_order)
1547 {
1548   int num_bytes = 0;
1549
1550   // write chunk name
1551   if (file != NULL)
1552     fputs(chunk_name, file);
1553
1554   num_bytes += strlen(chunk_name);
1555
1556   if (chunk_size >= 0)
1557   {
1558     // write chunk size
1559     if (file != NULL)
1560       putFile32BitInteger(file, chunk_size, byte_order);
1561
1562     num_bytes += 4;
1563   }
1564
1565   return num_bytes;
1566 }
1567
1568 int getFileVersion(File *file)
1569 {
1570   int version_super = getByteFromFile(file);
1571   int version_major = getByteFromFile(file);
1572   int version_minor = getByteFromFile(file);
1573   int version_patch = getByteFromFile(file);
1574
1575   return VERSION_IDENT(version_super, version_major, version_minor,
1576                        version_patch);
1577 }
1578
1579 int putFileVersion(FILE *file, int version)
1580 {
1581   if (file != NULL)
1582   {
1583     int version_super = VERSION_SUPER(version);
1584     int version_major = VERSION_MAJOR(version);
1585     int version_minor = VERSION_MINOR(version);
1586     int version_patch = VERSION_PATCH(version);
1587
1588     fputc(version_super, file);
1589     fputc(version_major, file);
1590     fputc(version_minor, file);
1591     fputc(version_patch, file);
1592   }
1593
1594   return 4;
1595 }
1596
1597 void ReadBytesFromFile(File *file, byte *buffer, unsigned int bytes)
1598 {
1599   int i;
1600
1601   for (i = 0; i < bytes && !checkEndOfFile(file); i++)
1602     buffer[i] = getByteFromFile(file);
1603 }
1604
1605 void WriteBytesToFile(FILE *file, byte *buffer, unsigned int bytes)
1606 {
1607   int i;
1608
1609   for (i = 0; i < bytes; i++)
1610     fputc(buffer[i], file);
1611 }
1612
1613 void ReadUnusedBytesFromFile(File *file, unsigned int bytes)
1614 {
1615   while (bytes-- && !checkEndOfFile(file))
1616     getByteFromFile(file);
1617 }
1618
1619 void WriteUnusedBytesToFile(FILE *file, unsigned int bytes)
1620 {
1621   while (bytes--)
1622     fputc(0, file);
1623 }
1624
1625
1626 // ----------------------------------------------------------------------------
1627 // functions to translate key identifiers between different format
1628 // ----------------------------------------------------------------------------
1629
1630 #define TRANSLATE_KEYSYM_TO_KEYNAME     0
1631 #define TRANSLATE_KEYSYM_TO_X11KEYNAME  1
1632 #define TRANSLATE_KEYNAME_TO_KEYSYM     2
1633 #define TRANSLATE_X11KEYNAME_TO_KEYSYM  3
1634
1635 static void translate_keyname(Key *keysym, char **x11name, char **name, int mode)
1636 {
1637   static struct
1638   {
1639     Key key;
1640     char *x11name;
1641     char *name;
1642   } translate_key[] =
1643   {
1644     // return and escape keys
1645     { KSYM_Return,      "XK_Return",            "return" },
1646     { KSYM_Escape,      "XK_Escape",            "escape" },
1647
1648     // normal cursor keys
1649     { KSYM_Left,        "XK_Left",              "cursor left" },
1650     { KSYM_Right,       "XK_Right",             "cursor right" },
1651     { KSYM_Up,          "XK_Up",                "cursor up" },
1652     { KSYM_Down,        "XK_Down",              "cursor down" },
1653
1654     // keypad cursor keys
1655 #ifdef KSYM_KP_Left
1656     { KSYM_KP_Left,     "XK_KP_Left",           "keypad left" },
1657     { KSYM_KP_Right,    "XK_KP_Right",          "keypad right" },
1658     { KSYM_KP_Up,       "XK_KP_Up",             "keypad up" },
1659     { KSYM_KP_Down,     "XK_KP_Down",           "keypad down" },
1660 #endif
1661
1662     // other keypad keys
1663 #ifdef KSYM_KP_Enter
1664     { KSYM_KP_Enter,    "XK_KP_Enter",          "keypad enter" },
1665     { KSYM_KP_Add,      "XK_KP_Add",            "keypad +" },
1666     { KSYM_KP_Subtract, "XK_KP_Subtract",       "keypad -" },
1667     { KSYM_KP_Multiply, "XK_KP_Multiply",       "keypad mltply" },
1668     { KSYM_KP_Divide,   "XK_KP_Divide",         "keypad /" },
1669     { KSYM_KP_Separator,"XK_KP_Separator",      "keypad ," },
1670 #endif
1671
1672     // modifier keys
1673     { KSYM_Shift_L,     "XK_Shift_L",           "left shift" },
1674     { KSYM_Shift_R,     "XK_Shift_R",           "right shift" },
1675     { KSYM_Control_L,   "XK_Control_L",         "left control" },
1676     { KSYM_Control_R,   "XK_Control_R",         "right control" },
1677     { KSYM_Meta_L,      "XK_Meta_L",            "left meta" },
1678     { KSYM_Meta_R,      "XK_Meta_R",            "right meta" },
1679     { KSYM_Alt_L,       "XK_Alt_L",             "left alt" },
1680     { KSYM_Alt_R,       "XK_Alt_R",             "right alt" },
1681     { KSYM_Mode_switch, "XK_Mode_switch",       "mode switch" }, // Alt-R
1682     { KSYM_Multi_key,   "XK_Multi_key",         "multi key" },   // Ctrl-R
1683
1684     // some special keys
1685     { KSYM_BackSpace,   "XK_BackSpace",         "backspace" },
1686     { KSYM_Delete,      "XK_Delete",            "delete" },
1687     { KSYM_Insert,      "XK_Insert",            "insert" },
1688     { KSYM_Tab,         "XK_Tab",               "tab" },
1689     { KSYM_Home,        "XK_Home",              "home" },
1690     { KSYM_End,         "XK_End",               "end" },
1691     { KSYM_Page_Up,     "XK_Page_Up",           "page up" },
1692     { KSYM_Page_Down,   "XK_Page_Down",         "page down" },
1693
1694     { KSYM_Select,      "XK_Select",            "select" },
1695     { KSYM_Menu,        "XK_Menu",              "menu" },        // menu key
1696     { KSYM_Back,        "XK_Back",              "back" },        // back key
1697     { KSYM_PlayPause,   "XK_PlayPause",         "play/pause" },
1698 #if defined(PLATFORM_ANDROID)
1699     { KSYM_Rewind,      "XK_Rewind",            "rewind" },
1700     { KSYM_FastForward, "XK_FastForward",       "fast forward" },
1701 #endif
1702
1703     // ASCII 0x20 to 0x40 keys (except numbers)
1704     { KSYM_space,       "XK_space",             "space" },
1705     { KSYM_exclam,      "XK_exclam",            "!" },
1706     { KSYM_quotedbl,    "XK_quotedbl",          "\"" },
1707     { KSYM_numbersign,  "XK_numbersign",        "#" },
1708     { KSYM_dollar,      "XK_dollar",            "$" },
1709     { KSYM_percent,     "XK_percent",           "%" },
1710     { KSYM_ampersand,   "XK_ampersand",         "&" },
1711     { KSYM_apostrophe,  "XK_apostrophe",        "'" },
1712     { KSYM_parenleft,   "XK_parenleft",         "(" },
1713     { KSYM_parenright,  "XK_parenright",        ")" },
1714     { KSYM_asterisk,    "XK_asterisk",          "*" },
1715     { KSYM_plus,        "XK_plus",              "+" },
1716     { KSYM_comma,       "XK_comma",             "," },
1717     { KSYM_minus,       "XK_minus",             "-" },
1718     { KSYM_period,      "XK_period",            "." },
1719     { KSYM_slash,       "XK_slash",             "/" },
1720     { KSYM_colon,       "XK_colon",             ":" },
1721     { KSYM_semicolon,   "XK_semicolon",         ";" },
1722     { KSYM_less,        "XK_less",              "<" },
1723     { KSYM_equal,       "XK_equal",             "=" },
1724     { KSYM_greater,     "XK_greater",           ">" },
1725     { KSYM_question,    "XK_question",          "?" },
1726     { KSYM_at,          "XK_at",                "@" },
1727
1728     // more ASCII keys
1729     { KSYM_bracketleft, "XK_bracketleft",       "[" },
1730     { KSYM_backslash,   "XK_backslash",         "\\" },
1731     { KSYM_bracketright,"XK_bracketright",      "]" },
1732     { KSYM_asciicircum, "XK_asciicircum",       "^" },
1733     { KSYM_underscore,  "XK_underscore",        "_" },
1734     { KSYM_grave,       "XK_grave",             "grave" },
1735     { KSYM_quoteleft,   "XK_quoteleft",         "quote left" },
1736     { KSYM_braceleft,   "XK_braceleft",         "brace left" },
1737     { KSYM_bar,         "XK_bar",               "bar" },
1738     { KSYM_braceright,  "XK_braceright",        "brace right" },
1739     { KSYM_asciitilde,  "XK_asciitilde",        "~" },
1740
1741     // special (non-ASCII) keys
1742     { KSYM_degree,      "XK_degree",            "degree" },
1743     { KSYM_Adiaeresis,  "XK_Adiaeresis",        "A umlaut" },
1744     { KSYM_Odiaeresis,  "XK_Odiaeresis",        "O umlaut" },
1745     { KSYM_Udiaeresis,  "XK_Udiaeresis",        "U umlaut" },
1746     { KSYM_adiaeresis,  "XK_adiaeresis",        "a umlaut" },
1747     { KSYM_odiaeresis,  "XK_odiaeresis",        "o umlaut" },
1748     { KSYM_udiaeresis,  "XK_udiaeresis",        "u umlaut" },
1749     { KSYM_ssharp,      "XK_ssharp",            "sharp s" },
1750
1751     // special (non-ASCII) keys (UTF-8, for reverse mapping only)
1752     { KSYM_degree,      "XK_degree",            "\xc2\xb0" },
1753     { KSYM_Adiaeresis,  "XK_Adiaeresis",        "\xc3\x84" },
1754     { KSYM_Odiaeresis,  "XK_Odiaeresis",        "\xc3\x96" },
1755     { KSYM_Udiaeresis,  "XK_Udiaeresis",        "\xc3\x9c" },
1756     { KSYM_adiaeresis,  "XK_adiaeresis",        "\xc3\xa4" },
1757     { KSYM_odiaeresis,  "XK_odiaeresis",        "\xc3\xb6" },
1758     { KSYM_udiaeresis,  "XK_udiaeresis",        "\xc3\xbc" },
1759     { KSYM_ssharp,      "XK_ssharp",            "\xc3\x9f" },
1760
1761     // other keys (for reverse mapping only)
1762     { KSYM_space,       "XK_space",             " " },
1763
1764     // keypad keys are not in numerical order in SDL2
1765     { KSYM_KP_0,        "XK_KP_0",              "keypad 0" },
1766     { KSYM_KP_1,        "XK_KP_1",              "keypad 1" },
1767     { KSYM_KP_2,        "XK_KP_2",              "keypad 2" },
1768     { KSYM_KP_3,        "XK_KP_3",              "keypad 3" },
1769     { KSYM_KP_4,        "XK_KP_4",              "keypad 4" },
1770     { KSYM_KP_5,        "XK_KP_5",              "keypad 5" },
1771     { KSYM_KP_6,        "XK_KP_6",              "keypad 6" },
1772     { KSYM_KP_7,        "XK_KP_7",              "keypad 7" },
1773     { KSYM_KP_8,        "XK_KP_8",              "keypad 8" },
1774     { KSYM_KP_9,        "XK_KP_9",              "keypad 9" },
1775
1776     // end-of-array identifier
1777     { 0,                NULL,                   NULL }
1778   };
1779
1780   int i;
1781
1782   if (mode == TRANSLATE_KEYSYM_TO_KEYNAME)
1783   {
1784     static char name_buffer[30];
1785     Key key = *keysym;
1786
1787     if (key >= KSYM_A && key <= KSYM_Z)
1788       sprintf(name_buffer, "%c", 'A' + (char)(key - KSYM_A));
1789     else if (key >= KSYM_a && key <= KSYM_z)
1790       sprintf(name_buffer, "%c", 'a' + (char)(key - KSYM_a));
1791     else if (key >= KSYM_0 && key <= KSYM_9)
1792       sprintf(name_buffer, "%c", '0' + (char)(key - KSYM_0));
1793     else if (key >= KSYM_FKEY_FIRST && key <= KSYM_FKEY_LAST)
1794       sprintf(name_buffer, "F%d", (int)(key - KSYM_FKEY_FIRST + 1));
1795     else if (key == KSYM_UNDEFINED)
1796       strcpy(name_buffer, "(undefined)");
1797     else
1798     {
1799       i = 0;
1800
1801       do
1802       {
1803         if (key == translate_key[i].key)
1804         {
1805           strcpy(name_buffer, translate_key[i].name);
1806           break;
1807         }
1808       }
1809       while (translate_key[++i].name);
1810
1811       if (!translate_key[i].name)
1812         strcpy(name_buffer, "(unknown)");
1813     }
1814
1815     *name = name_buffer;
1816   }
1817   else if (mode == TRANSLATE_KEYSYM_TO_X11KEYNAME)
1818   {
1819     static char name_buffer[30];
1820     Key key = *keysym;
1821
1822     if (key >= KSYM_A && key <= KSYM_Z)
1823       sprintf(name_buffer, "XK_%c", 'A' + (char)(key - KSYM_A));
1824     else if (key >= KSYM_a && key <= KSYM_z)
1825       sprintf(name_buffer, "XK_%c", 'a' + (char)(key - KSYM_a));
1826     else if (key >= KSYM_0 && key <= KSYM_9)
1827       sprintf(name_buffer, "XK_%c", '0' + (char)(key - KSYM_0));
1828     else if (key >= KSYM_FKEY_FIRST && key <= KSYM_FKEY_LAST)
1829       sprintf(name_buffer, "XK_F%d", (int)(key - KSYM_FKEY_FIRST + 1));
1830     else if (key == KSYM_UNDEFINED)
1831       strcpy(name_buffer, "[undefined]");
1832     else
1833     {
1834       i = 0;
1835
1836       do
1837       {
1838         if (key == translate_key[i].key)
1839         {
1840           strcpy(name_buffer, translate_key[i].x11name);
1841           break;
1842         }
1843       }
1844       while (translate_key[++i].x11name);
1845
1846       if (!translate_key[i].x11name)
1847         sprintf(name_buffer, "0x%04x", (unsigned int)key);
1848     }
1849
1850     *x11name = name_buffer;
1851   }
1852   else if (mode == TRANSLATE_KEYNAME_TO_KEYSYM)
1853   {
1854     Key key = KSYM_UNDEFINED;
1855     char *name_ptr = *name;
1856
1857     if (strlen(*name) == 1)
1858     {
1859       char c = name_ptr[0];
1860
1861       if (c >= 'A' && c <= 'Z')
1862         key = KSYM_A + (Key)(c - 'A');
1863       else if (c >= 'a' && c <= 'z')
1864         key = KSYM_a + (Key)(c - 'a');
1865       else if (c >= '0' && c <= '9')
1866         key = KSYM_0 + (Key)(c - '0');
1867     }
1868
1869     if (key == KSYM_UNDEFINED)
1870     {
1871       i = 0;
1872
1873       do
1874       {
1875         if (strEqual(translate_key[i].name, *name))
1876         {
1877           key = translate_key[i].key;
1878           break;
1879         }
1880       }
1881       while (translate_key[++i].x11name);
1882     }
1883
1884     if (key == KSYM_UNDEFINED)
1885       Warn("getKeyFromKeyName(): not completely implemented");
1886
1887     *keysym = key;
1888   }
1889   else if (mode == TRANSLATE_X11KEYNAME_TO_KEYSYM)
1890   {
1891     Key key = KSYM_UNDEFINED;
1892     char *name_ptr = *x11name;
1893
1894     if (strPrefix(name_ptr, "XK_") && strlen(name_ptr) == 4)
1895     {
1896       char c = name_ptr[3];
1897
1898       if (c >= 'A' && c <= 'Z')
1899         key = KSYM_A + (Key)(c - 'A');
1900       else if (c >= 'a' && c <= 'z')
1901         key = KSYM_a + (Key)(c - 'a');
1902       else if (c >= '0' && c <= '9')
1903         key = KSYM_0 + (Key)(c - '0');
1904     }
1905     else if (strPrefix(name_ptr, "XK_F") && strlen(name_ptr) <= 6)
1906     {
1907       char c1 = name_ptr[4];
1908       char c2 = name_ptr[5];
1909       int d = 0;
1910
1911       if ((c1 >= '0' && c1 <= '9') &&
1912           ((c2 >= '0' && c1 <= '9') || c2 == '\0'))
1913         d = atoi(&name_ptr[4]);
1914
1915       if (d >= 1 && d <= KSYM_NUM_FKEYS)
1916         key = KSYM_F1 + (Key)(d - 1);
1917     }
1918     else if (strPrefix(name_ptr, "XK_"))
1919     {
1920       i = 0;
1921
1922       do
1923       {
1924         if (strEqual(name_ptr, translate_key[i].x11name))
1925         {
1926           key = translate_key[i].key;
1927           break;
1928         }
1929       }
1930       while (translate_key[++i].x11name);
1931     }
1932     else if (strPrefix(name_ptr, "0x"))
1933     {
1934       unsigned int value = 0;
1935
1936       name_ptr += 2;
1937
1938       while (name_ptr)
1939       {
1940         char c = *name_ptr++;
1941         int d = -1;
1942
1943         if (c >= '0' && c <= '9')
1944           d = (int)(c - '0');
1945         else if (c >= 'a' && c <= 'f')
1946           d = (int)(c - 'a' + 10);
1947         else if (c >= 'A' && c <= 'F')
1948           d = (int)(c - 'A' + 10);
1949
1950         if (d == -1)
1951         {
1952           value = -1;
1953           break;
1954         }
1955
1956         value = value * 16 + d;
1957       }
1958
1959       if (value != -1)
1960         key = (Key)value;
1961     }
1962
1963     *keysym = key;
1964   }
1965 }
1966
1967 char *getKeyNameFromKey(Key key)
1968 {
1969   char *name;
1970
1971   translate_keyname(&key, NULL, &name, TRANSLATE_KEYSYM_TO_KEYNAME);
1972   return name;
1973 }
1974
1975 char *getX11KeyNameFromKey(Key key)
1976 {
1977   char *x11name;
1978
1979   translate_keyname(&key, &x11name, NULL, TRANSLATE_KEYSYM_TO_X11KEYNAME);
1980   return x11name;
1981 }
1982
1983 Key getKeyFromKeyName(char *name)
1984 {
1985   Key key;
1986
1987   translate_keyname(&key, NULL, &name, TRANSLATE_KEYNAME_TO_KEYSYM);
1988   return key;
1989 }
1990
1991 Key getKeyFromX11KeyName(char *x11name)
1992 {
1993   Key key;
1994
1995   translate_keyname(&key, &x11name, NULL, TRANSLATE_X11KEYNAME_TO_KEYSYM);
1996   return key;
1997 }
1998
1999 char getCharFromKey(Key key)
2000 {
2001   static struct
2002   {
2003     Key key;
2004     byte key_char;
2005   } translate_key_char[] =
2006   {
2007     // special (non-ASCII) keys (ISO-8859-1)
2008     { KSYM_degree,      CHAR_BYTE_DEGREE        },
2009     { KSYM_Adiaeresis,  CHAR_BYTE_UMLAUT_A      },
2010     { KSYM_Odiaeresis,  CHAR_BYTE_UMLAUT_O      },
2011     { KSYM_Udiaeresis,  CHAR_BYTE_UMLAUT_U      },
2012     { KSYM_adiaeresis,  CHAR_BYTE_UMLAUT_a      },
2013     { KSYM_odiaeresis,  CHAR_BYTE_UMLAUT_o      },
2014     { KSYM_udiaeresis,  CHAR_BYTE_UMLAUT_u      },
2015     { KSYM_ssharp,      CHAR_BYTE_SHARP_S       },
2016
2017     // end-of-array identifier
2018     { 0,                0                       }
2019   };
2020
2021   char *keyname = getKeyNameFromKey(key);
2022   char c = 0;
2023
2024   if (strlen(keyname) == 1)
2025     c = keyname[0];
2026   else if (strEqual(keyname, "space"))
2027     c = ' ';
2028   else
2029   {
2030     int i = 0;
2031
2032     do
2033     {
2034       if (key == translate_key_char[i].key)
2035       {
2036         c = translate_key_char[i].key_char;
2037
2038         break;
2039       }
2040     }
2041     while (translate_key_char[++i].key_char);
2042   }
2043
2044   return c;
2045 }
2046
2047 char getValidConfigValueChar(char c)
2048 {
2049   if (c == '#' ||       // used to mark comments
2050       c == '\\')        // used to mark continued lines
2051     c = 0;
2052
2053   return c;
2054 }
2055
2056
2057 // ----------------------------------------------------------------------------
2058 // functions to translate string identifiers to integer or boolean value
2059 // ----------------------------------------------------------------------------
2060
2061 int get_integer_from_string(char *s)
2062 {
2063   static char *number_text[][3] =
2064   {
2065     { "0",      "zero",         "null",         },
2066     { "1",      "one",          "first"         },
2067     { "2",      "two",          "second"        },
2068     { "3",      "three",        "third"         },
2069     { "4",      "four",         "fourth"        },
2070     { "5",      "five",         "fifth"         },
2071     { "6",      "six",          "sixth"         },
2072     { "7",      "seven",        "seventh"       },
2073     { "8",      "eight",        "eighth"        },
2074     { "9",      "nine",         "ninth"         },
2075     { "10",     "ten",          "tenth"         },
2076     { "11",     "eleven",       "eleventh"      },
2077     { "12",     "twelve",       "twelfth"       },
2078
2079     { NULL,     NULL,           NULL            },
2080   };
2081
2082   int i, j;
2083   char *s_lower = getStringToLower(s);
2084   int result = -1;
2085
2086   for (i = 0; number_text[i][0] != NULL; i++)
2087     for (j = 0; j < 3; j++)
2088       if (strEqual(s_lower, number_text[i][j]))
2089         result = i;
2090
2091   if (result == -1)
2092   {
2093     if (strEqual(s_lower, "false") ||
2094         strEqual(s_lower, "no") ||
2095         strEqual(s_lower, "off"))
2096       result = 0;
2097     else if (strEqual(s_lower, "true") ||
2098              strEqual(s_lower, "yes") ||
2099              strEqual(s_lower, "on"))
2100       result = 1;
2101     else
2102       result = atoi(s);
2103   }
2104
2105   free(s_lower);
2106
2107   return result;
2108 }
2109
2110 boolean get_boolean_from_string(char *s)
2111 {
2112   char *s_lower = getStringToLower(s);
2113   boolean result = FALSE;
2114
2115   if (strEqual(s_lower, "true") ||
2116       strEqual(s_lower, "yes") ||
2117       strEqual(s_lower, "on") ||
2118       get_integer_from_string(s) == 1)
2119     result = TRUE;
2120
2121   free(s_lower);
2122
2123   return result;
2124 }
2125
2126 int get_switch3_from_string(char *s)
2127 {
2128   char *s_lower = getStringToLower(s);
2129   int result = FALSE;
2130
2131   if (strEqual(s_lower, "true") ||
2132       strEqual(s_lower, "yes") ||
2133       strEqual(s_lower, "on") ||
2134       get_integer_from_string(s) == 1)
2135     result = TRUE;
2136   else if (strEqual(s_lower, "auto"))
2137     result = AUTO;
2138
2139   free(s_lower);
2140
2141   return result;
2142 }
2143
2144 int get_player_nr_from_string(char *s)
2145 {
2146   static char *player_text[] =
2147   {
2148     "player_1",
2149     "player_2",
2150     "player_3",
2151     "player_4",
2152
2153     NULL
2154   };
2155
2156   char *s_lower = getStringToLower(s);
2157   int result = 0;
2158   int i;
2159
2160   for (i = 0; player_text[i] != NULL; i++)
2161     if (strEqual(s_lower, player_text[i]))
2162       result = i;
2163
2164   free(s_lower);
2165
2166   return result;
2167 }
2168
2169
2170 // ----------------------------------------------------------------------------
2171 // functions for generic lists
2172 // ----------------------------------------------------------------------------
2173
2174 ListNode *newListNode(void)
2175 {
2176   return checked_calloc(sizeof(ListNode));
2177 }
2178
2179 void addNodeToList(ListNode **node_first, char *key, void *content)
2180 {
2181   ListNode *node_new = newListNode();
2182
2183   node_new->key = getStringCopy(key);
2184   node_new->content = content;
2185   node_new->next = *node_first;
2186
2187   if (*node_first)
2188     (*node_first)->prev = node_new;
2189
2190   *node_first = node_new;
2191 }
2192
2193 void deleteNodeFromList(ListNode **node_first, char *key,
2194                         void (*destructor_function)(void *))
2195 {
2196   if (node_first == NULL || *node_first == NULL)
2197     return;
2198
2199   if (strEqual((*node_first)->key, key))
2200   {
2201     // after first recursion, (*node_first)->prev->next == *node_first,
2202     // so *node_first would be overwritten with (*node_first)->next
2203     // => use a copy of *node_first (and later of (*node_first)->next)
2204     ListNode *node = *node_first;
2205     ListNode *node_next = node->next;
2206
2207     checked_free(node->key);
2208
2209     if (destructor_function)
2210       destructor_function(node->content);
2211
2212     if (node->prev)
2213       node->prev->next = node->next;
2214
2215     if (node->next)
2216       node->next->prev = node->prev;
2217
2218     checked_free(node);
2219
2220     // after removing node, set list pointer to next valid list node
2221     // (this is important if the first node of the list was deleted)
2222     *node_first = node_next;
2223   }
2224   else
2225   {
2226     deleteNodeFromList(&(*node_first)->next, key, destructor_function);
2227   }
2228 }
2229
2230 ListNode *getNodeFromKey(ListNode *node_first, char *key)
2231 {
2232   if (node_first == NULL)
2233     return NULL;
2234
2235   if (strEqual(node_first->key, key))
2236     return node_first;
2237   else
2238     return getNodeFromKey(node_first->next, key);
2239 }
2240
2241 int getNumNodes(ListNode *node_first)
2242 {
2243   return (node_first ? 1 + getNumNodes(node_first->next) : 0);
2244 }
2245
2246 #if 0
2247 static void dumpList(ListNode *node_first)
2248 {
2249   ListNode *node = node_first;
2250
2251   while (node)
2252   {
2253     printf("['%s' (%d)]\n", node->key,
2254            ((struct ListNodeInfo *)node->content)->num_references);
2255     node = node->next;
2256   }
2257
2258   printf("[%d nodes]\n", getNumNodes(node_first));
2259 }
2260 #endif
2261
2262
2263 // ----------------------------------------------------------------------------
2264 // functions for file handling
2265 // ----------------------------------------------------------------------------
2266
2267 #define MAX_BUFFER_SIZE                 4096
2268
2269 File *openFile(char *filename, char *mode)
2270 {
2271   File *file = checked_calloc(sizeof(File));
2272
2273   file->file = fopen(filename, mode);
2274
2275   if (file->file != NULL)
2276   {
2277     file->filename = getStringCopy(filename);
2278
2279     return file;
2280   }
2281
2282 #if defined(PLATFORM_ANDROID)
2283   file->asset_file = SDL_RWFromFile(filename, mode);
2284
2285   if (file->asset_file != NULL)
2286   {
2287     file->file_is_asset = TRUE;
2288     file->filename = getStringCopy(filename);
2289
2290     return file;
2291   }
2292 #endif
2293
2294   checked_free(file);
2295
2296   return NULL;
2297 }
2298
2299 int closeFile(File *file)
2300 {
2301   if (file == NULL)
2302     return -1;
2303
2304   int result = 0;
2305
2306 #if defined(PLATFORM_ANDROID)
2307   if (file->asset_file)
2308     result = SDL_RWclose(file->asset_file);
2309 #endif
2310
2311   if (file->file)
2312     result = fclose(file->file);
2313
2314   checked_free(file->filename);
2315   checked_free(file);
2316
2317   return result;
2318 }
2319
2320 int checkEndOfFile(File *file)
2321 {
2322 #if defined(PLATFORM_ANDROID)
2323   if (file->file_is_asset)
2324     return file->end_of_file;
2325 #endif
2326
2327   return feof(file->file);
2328 }
2329
2330 size_t readFile(File *file, void *buffer, size_t item_size, size_t num_items)
2331 {
2332 #if defined(PLATFORM_ANDROID)
2333   if (file->file_is_asset)
2334   {
2335     if (file->end_of_file)
2336       return 0;
2337
2338     size_t num_items_read =
2339       SDL_RWread(file->asset_file, buffer, item_size, num_items);
2340
2341     if (num_items_read < num_items)
2342       file->end_of_file = TRUE;
2343
2344     return num_items_read;
2345   }
2346 #endif
2347
2348   return fread(buffer, item_size, num_items, file->file);
2349 }
2350
2351 size_t writeFile(File *file, void *buffer, size_t item_size, size_t num_items)
2352 {
2353   return fwrite(buffer, item_size, num_items, file->file);
2354 }
2355
2356 int seekFile(File *file, long offset, int whence)
2357 {
2358 #if defined(PLATFORM_ANDROID)
2359   if (file->file_is_asset)
2360   {
2361     int sdl_whence = (whence == SEEK_SET ? RW_SEEK_SET :
2362                       whence == SEEK_CUR ? RW_SEEK_CUR :
2363                       whence == SEEK_END ? RW_SEEK_END : 0);
2364
2365     return (SDL_RWseek(file->asset_file, offset, sdl_whence) == -1 ? -1 : 0);
2366   }
2367 #endif
2368
2369   return fseek(file->file, offset, whence);
2370 }
2371
2372 int getByteFromFile(File *file)
2373 {
2374 #if defined(PLATFORM_ANDROID)
2375   if (file->file_is_asset)
2376   {
2377     if (file->end_of_file)
2378       return EOF;
2379
2380     byte c;
2381     size_t num_bytes_read = SDL_RWread(file->asset_file, &c, 1, 1);
2382
2383     if (num_bytes_read < 1)
2384       file->end_of_file = TRUE;
2385
2386     return (file->end_of_file ? EOF : (int)c);
2387   }
2388 #endif
2389
2390   return fgetc(file->file);
2391 }
2392
2393 char *getStringFromFile(File *file, char *line, int size)
2394 {
2395 #if defined(PLATFORM_ANDROID)
2396   if (file->file_is_asset)
2397   {
2398     if (file->end_of_file)
2399       return NULL;
2400
2401     char *line_ptr = line;
2402     int num_bytes_read = 0;
2403
2404     while (num_bytes_read < size - 1 &&
2405            SDL_RWread(file->asset_file, line_ptr, 1, 1) == 1 &&
2406            *line_ptr++ != '\n')
2407       num_bytes_read++;
2408
2409     *line_ptr = '\0';
2410
2411     if (strlen(line) == 0)
2412     {
2413       file->end_of_file = TRUE;
2414
2415       return NULL;
2416     }
2417
2418     return line;
2419   }
2420 #endif
2421
2422   return fgets(line, size, file->file);
2423 }
2424
2425 int copyFile(char *filename_from, char *filename_to)
2426 {
2427   File *file_from, *file_to;
2428
2429   if ((file_from = openFile(filename_from, MODE_READ)) == NULL)
2430   {
2431     return -1;
2432   }
2433
2434   if ((file_to = openFile(filename_to, MODE_WRITE)) == NULL)
2435   {
2436     closeFile(file_from);
2437
2438     return -1;
2439   }
2440
2441   while (!checkEndOfFile(file_from))
2442   {
2443     byte buffer[MAX_BUFFER_SIZE];
2444     size_t bytes_read = readFile(file_from, buffer, 1, MAX_BUFFER_SIZE);
2445
2446     writeFile(file_to, buffer, 1, bytes_read);
2447   }
2448
2449   closeFile(file_from);
2450   closeFile(file_to);
2451
2452   return 0;
2453 }
2454
2455
2456 // ----------------------------------------------------------------------------
2457 // functions for directory handling
2458 // ----------------------------------------------------------------------------
2459
2460 Directory *openDirectory(char *dir_name)
2461 {
2462   Directory *dir = checked_calloc(sizeof(Directory));
2463
2464   dir->dir = opendir(dir_name);
2465
2466   if (dir->dir != NULL)
2467   {
2468     dir->filename = getStringCopy(dir_name);
2469
2470     return dir;
2471   }
2472
2473 #if defined(PLATFORM_ANDROID)
2474   char *asset_toc_filename = getPath2(dir_name, ASSET_TOC_BASENAME);
2475
2476   dir->asset_toc_file = SDL_RWFromFile(asset_toc_filename, MODE_READ);
2477
2478   checked_free(asset_toc_filename);
2479
2480   if (dir->asset_toc_file != NULL)
2481   {
2482     dir->directory_is_asset = TRUE;
2483     dir->filename = getStringCopy(dir_name);
2484
2485     return dir;
2486   }
2487 #endif
2488
2489   checked_free(dir);
2490
2491   return NULL;
2492 }
2493
2494 int closeDirectory(Directory *dir)
2495 {
2496   if (dir == NULL)
2497     return -1;
2498
2499   int result = 0;
2500
2501 #if defined(PLATFORM_ANDROID)
2502   if (dir->asset_toc_file)
2503     result = SDL_RWclose(dir->asset_toc_file);
2504 #endif
2505
2506   if (dir->dir)
2507     result = closedir(dir->dir);
2508
2509   if (dir->dir_entry)
2510     freeDirectoryEntry(dir->dir_entry);
2511
2512   checked_free(dir->filename);
2513   checked_free(dir);
2514
2515   return result;
2516 }
2517
2518 DirectoryEntry *readDirectory(Directory *dir)
2519 {
2520   if (dir->dir_entry)
2521     freeDirectoryEntry(dir->dir_entry);
2522
2523   dir->dir_entry = NULL;
2524
2525 #if defined(PLATFORM_ANDROID)
2526   if (dir->directory_is_asset)
2527   {
2528     char line[MAX_LINE_LEN];
2529     char *line_ptr = line;
2530     int num_bytes_read = 0;
2531
2532     while (num_bytes_read < MAX_LINE_LEN - 1 &&
2533            SDL_RWread(dir->asset_toc_file, line_ptr, 1, 1) == 1 &&
2534            *line_ptr != '\n')
2535     {
2536       line_ptr++;
2537       num_bytes_read++;
2538     }
2539
2540     *line_ptr = '\0';
2541
2542     if (strlen(line) == 0)
2543       return NULL;
2544
2545     dir->dir_entry = checked_calloc(sizeof(DirectoryEntry));
2546
2547     dir->dir_entry->is_directory = FALSE;
2548     if (line[strlen(line) - 1] == '/')
2549     {
2550       dir->dir_entry->is_directory = TRUE;
2551
2552       line[strlen(line) - 1] = '\0';
2553     }
2554
2555     dir->dir_entry->basename = getStringCopy(line);
2556     dir->dir_entry->filename = getPath2(dir->filename, line);
2557
2558     return dir->dir_entry;
2559   }
2560 #endif
2561
2562   struct dirent *dir_entry = readdir(dir->dir);
2563
2564   if (dir_entry == NULL)
2565     return NULL;
2566
2567   dir->dir_entry = checked_calloc(sizeof(DirectoryEntry));
2568
2569   dir->dir_entry->basename = getStringCopy(dir_entry->d_name);
2570   dir->dir_entry->filename = getPath2(dir->filename, dir_entry->d_name);
2571
2572   struct stat file_status;
2573
2574   dir->dir_entry->is_directory =
2575     (stat(dir->dir_entry->filename, &file_status) == 0 &&
2576      S_ISDIR(file_status.st_mode));
2577
2578   return dir->dir_entry;
2579 }
2580
2581 void freeDirectoryEntry(DirectoryEntry *dir_entry)
2582 {
2583   if (dir_entry == NULL)
2584     return;
2585
2586   checked_free(dir_entry->basename);
2587   checked_free(dir_entry->filename);
2588   checked_free(dir_entry);
2589 }
2590
2591
2592 // ----------------------------------------------------------------------------
2593 // functions for checking files and filenames
2594 // ----------------------------------------------------------------------------
2595
2596 boolean directoryExists(char *dir_name)
2597 {
2598   if (dir_name == NULL)
2599     return FALSE;
2600
2601   struct stat file_status;
2602   boolean success = (stat(dir_name, &file_status) == 0 &&
2603                      S_ISDIR(file_status.st_mode));
2604
2605 #if defined(PLATFORM_ANDROID)
2606   if (!success)
2607   {
2608     // this might be an asset directory; check by trying to open toc file
2609     char *asset_toc_filename = getPath2(dir_name, ASSET_TOC_BASENAME);
2610     SDL_RWops *file = SDL_RWFromFile(asset_toc_filename, MODE_READ);
2611
2612     checked_free(asset_toc_filename);
2613
2614     success = (file != NULL);
2615
2616     if (success)
2617       SDL_RWclose(file);
2618   }
2619 #endif
2620
2621   return success;
2622 }
2623
2624 boolean fileExists(char *filename)
2625 {
2626   if (filename == NULL)
2627     return FALSE;
2628
2629   boolean success = (access(filename, F_OK) == 0);
2630
2631 #if defined(PLATFORM_ANDROID)
2632   if (!success)
2633   {
2634     // this might be an asset file; check by trying to open it
2635     SDL_RWops *file = SDL_RWFromFile(filename, MODE_READ);
2636
2637     success = (file != NULL);
2638
2639     if (success)
2640       SDL_RWclose(file);
2641   }
2642 #endif
2643
2644   return success;
2645 }
2646
2647 #if 0
2648 static boolean fileHasPrefix(char *basename, char *prefix)
2649 {
2650   static char *basename_lower = NULL;
2651   int basename_length, prefix_length;
2652
2653   checked_free(basename_lower);
2654
2655   if (basename == NULL || prefix == NULL)
2656     return FALSE;
2657
2658   basename_lower = getStringToLower(basename);
2659   basename_length = strlen(basename_lower);
2660   prefix_length = strlen(prefix);
2661
2662   if (basename_length > prefix_length + 1 &&
2663       basename_lower[prefix_length] == '.' &&
2664       strncmp(basename_lower, prefix, prefix_length) == 0)
2665     return TRUE;
2666
2667   return FALSE;
2668 }
2669 #endif
2670
2671 static boolean fileHasSuffix(char *basename, char *suffix)
2672 {
2673   static char *basename_lower = NULL;
2674   int basename_length, suffix_length;
2675
2676   checked_free(basename_lower);
2677
2678   if (basename == NULL || suffix == NULL)
2679     return FALSE;
2680
2681   basename_lower = getStringToLower(basename);
2682   basename_length = strlen(basename_lower);
2683   suffix_length = strlen(suffix);
2684
2685   if (basename_length > suffix_length + 1 &&
2686       basename_lower[basename_length - suffix_length - 1] == '.' &&
2687       strEqual(&basename_lower[basename_length - suffix_length], suffix))
2688     return TRUE;
2689
2690   return FALSE;
2691 }
2692
2693 static boolean FileCouldBeArtwork(char *filename)
2694 {
2695   char *basename = getBaseNamePtr(filename);
2696
2697   return (!strEqual(basename, ".") &&
2698           !strEqual(basename, "..") &&
2699           !fileHasSuffix(basename, "txt") &&
2700           !fileHasSuffix(basename, "conf") &&
2701           !directoryExists(filename));
2702 }
2703
2704 boolean FileIsGraphic(char *filename)
2705 {
2706   return FileCouldBeArtwork(filename);
2707 }
2708
2709 boolean FileIsSound(char *filename)
2710 {
2711   return FileCouldBeArtwork(filename);
2712 }
2713
2714 boolean FileIsMusic(char *filename)
2715 {
2716   return FileCouldBeArtwork(filename);
2717 }
2718
2719 boolean FileIsArtworkType(char *filename, int type)
2720 {
2721   if ((type == TREE_TYPE_GRAPHICS_DIR && FileIsGraphic(filename)) ||
2722       (type == TREE_TYPE_SOUNDS_DIR && FileIsSound(filename)) ||
2723       (type == TREE_TYPE_MUSIC_DIR && FileIsMusic(filename)))
2724     return TRUE;
2725
2726   return FALSE;
2727 }
2728
2729 // ----------------------------------------------------------------------------
2730 // functions for loading artwork configuration information
2731 // ----------------------------------------------------------------------------
2732
2733 char *get_mapped_token(char *token)
2734 {
2735   // !!! make this dynamically configurable (init.c:InitArtworkConfig) !!!
2736   static char *map_token_prefix[][2] =
2737   {
2738     { "char_procent",           "char_percent"                  },
2739     { "bd_magic_wall_filling",  "bd_magic_wall.filling"         },
2740     { "bd_magic_wall_emptying", "bd_magic_wall.emptying"        },
2741     { "bd_butterfly_left",      "bd_butterfly.left"             },
2742     { "bd_butterfly_right",     "bd_butterfly.right"            },
2743     { "bd_butterfly_up",        "bd_butterfly.up"               },
2744     { "bd_butterfly_down",      "bd_butterfly.down"             },
2745     { "bd_firefly_left",        "bd_firefly.left"               },
2746     { "bd_firefly_right",       "bd_firefly.right"              },
2747     { "bd_firefly_up",          "bd_firefly.up"                 },
2748     { "bd_firefly_down",        "bd_firefly.down"               },
2749
2750     { NULL,                                                     }
2751   };
2752   int i;
2753
2754   for (i = 0; map_token_prefix[i][0] != NULL; i++)
2755   {
2756     int len_token_prefix = strlen(map_token_prefix[i][0]);
2757
2758     if (strncmp(token, map_token_prefix[i][0], len_token_prefix) == 0)
2759       return getStringCat2(map_token_prefix[i][1], &token[len_token_prefix]);
2760   }
2761
2762   // change tokens containing ".gfx" by moving the "gfx" part to the very left
2763   char *gfx_substring = ".gfx";
2764   char *gfx_prefix = "gfx.";
2765   if (strstr(token, gfx_substring) != NULL)
2766   {
2767     char *token_prefix = getStringCopy(token);
2768     char *token_gfx_pos = strstr(token_prefix, gfx_substring);
2769     char *token_suffix = &token_gfx_pos[strlen(gfx_substring)];
2770     char *mapped_token;
2771
2772     // cut off token string at ".gfx" substring position
2773     *token_gfx_pos = '\0';
2774
2775     // put together prefix "gfx." and token prefix and suffix without ".gfx"
2776     mapped_token = getStringCat3(gfx_prefix, token_prefix, token_suffix);
2777
2778     free(token_prefix);
2779
2780     return mapped_token;
2781   }
2782
2783   return NULL;
2784 }
2785
2786 static char *get_special_base_token(struct ArtworkListInfo *artwork_info,
2787                                     char *token)
2788 {
2789   // !!! make this dynamically configurable (init.c:InitArtworkConfig) !!!
2790   static struct ConfigTypeInfo prefix_list[] =
2791   {
2792     { "global.anim_1"   },
2793     { "global.anim_2"   },
2794     { "global.anim_3"   },
2795     { "global.anim_4"   },
2796     { "global.anim_5"   },
2797     { "global.anim_6"   },
2798     { "global.anim_7"   },
2799     { "global.anim_8"   },
2800     { "global.anim_9"   },
2801     { "global.anim_10"  },
2802     { "global.anim_11"  },
2803     { "global.anim_12"  },
2804     { "global.anim_13"  },
2805     { "global.anim_14"  },
2806     { "global.anim_15"  },
2807     { "global.anim_16"  },
2808     { "global.anim_17"  },
2809     { "global.anim_18"  },
2810     { "global.anim_19"  },
2811     { "global.anim_20"  },
2812     { "global.anim_21"  },
2813     { "global.anim_22"  },
2814     { "global.anim_23"  },
2815     { "global.anim_24"  },
2816     { "global.anim_25"  },
2817     { "global.anim_26"  },
2818     { "global.anim_27"  },
2819     { "global.anim_28"  },
2820     { "global.anim_29"  },
2821     { "global.anim_30"  },
2822     { "global.anim_31"  },
2823     { "global.anim_32"  },
2824
2825     { NULL              }
2826   };
2827   struct ConfigTypeInfo *suffix_list = artwork_info->suffix_list;
2828   boolean prefix_found = FALSE;
2829   int len_suffix = 0;
2830   int i;
2831
2832   // search for prefix to check if base token has to be created
2833   for (i = 0; prefix_list[i].token != NULL; i++)
2834     if (strPrefix(token, prefix_list[i].token))
2835       prefix_found = TRUE;
2836
2837   if (!prefix_found)
2838     return NULL;
2839
2840   // search for suffix (parameter) to determine base token length
2841   for (i = 0; suffix_list[i].token != NULL; i++)
2842     if (strSuffix(token, suffix_list[i].token))
2843       len_suffix = strlen(suffix_list[i].token);
2844
2845   return getStringCopyN(token, strlen(token) - len_suffix);
2846 }
2847
2848 static void FreeCustomArtworkList(struct ArtworkListInfo *,
2849                                   struct ListNodeInfo ***, int *);
2850
2851 struct FileInfo *getFileListFromConfigList(struct ConfigInfo *config_list,
2852                                            struct ConfigTypeInfo *suffix_list,
2853                                            char **ignore_tokens,
2854                                            int num_file_list_entries)
2855 {
2856   SetupFileHash *ignore_tokens_hash;
2857   struct FileInfo *file_list;
2858   int num_file_list_entries_found = 0;
2859   int num_suffix_list_entries = 0;
2860   int list_pos;
2861   int i, j;
2862
2863   // create hash from list of tokens to be ignored (for quick access)
2864   ignore_tokens_hash = newSetupFileHash();
2865   for (i = 0; ignore_tokens[i] != NULL; i++)
2866     setHashEntry(ignore_tokens_hash, ignore_tokens[i], "");
2867
2868   file_list = checked_calloc(num_file_list_entries * sizeof(struct FileInfo));
2869
2870   for (i = 0; suffix_list[i].token != NULL; i++)
2871     num_suffix_list_entries++;
2872
2873   // always start with reliable default values
2874   for (i = 0; i < num_file_list_entries; i++)
2875   {
2876     file_list[i].token = NULL;
2877
2878     file_list[i].default_filename = NULL;
2879     file_list[i].filename = NULL;
2880
2881     if (num_suffix_list_entries > 0)
2882     {
2883       int parameter_array_size = num_suffix_list_entries * sizeof(char *);
2884
2885       file_list[i].default_parameter = checked_calloc(parameter_array_size);
2886       file_list[i].parameter = checked_calloc(parameter_array_size);
2887
2888       for (j = 0; j < num_suffix_list_entries; j++)
2889       {
2890         setString(&file_list[i].default_parameter[j], suffix_list[j].value);
2891         setString(&file_list[i].parameter[j], suffix_list[j].value);
2892       }
2893
2894       file_list[i].redefined = FALSE;
2895       file_list[i].fallback_to_default = FALSE;
2896       file_list[i].default_is_cloned = FALSE;
2897     }
2898   }
2899
2900   list_pos = 0;
2901
2902   for (i = 0; config_list[i].token != NULL; i++)
2903   {
2904     int len_config_token = strlen(config_list[i].token);
2905     boolean is_file_entry = TRUE;
2906
2907     for (j = 0; suffix_list[j].token != NULL; j++)
2908     {
2909       int len_suffix = strlen(suffix_list[j].token);
2910
2911       if (len_suffix < len_config_token &&
2912           strEqual(&config_list[i].token[len_config_token - len_suffix],
2913                    suffix_list[j].token))
2914       {
2915         setString(&file_list[list_pos].default_parameter[j],
2916                   config_list[i].value);
2917
2918         is_file_entry = FALSE;
2919
2920         break;
2921       }
2922     }
2923
2924     // the following tokens are no file definitions, but other config tokens
2925     if (getHashEntry(ignore_tokens_hash, config_list[i].token) != NULL)
2926       is_file_entry = FALSE;
2927
2928     if (is_file_entry)
2929     {
2930       if (i > 0)
2931         list_pos++;
2932
2933       if (list_pos >= num_file_list_entries)
2934         break;
2935
2936       file_list[list_pos].token = config_list[i].token;
2937       file_list[list_pos].default_filename = config_list[i].value;
2938     }
2939
2940     if (strSuffix(config_list[i].token, ".clone_from"))
2941       file_list[list_pos].default_is_cloned = TRUE;
2942   }
2943
2944   num_file_list_entries_found = list_pos + 1;
2945   if (num_file_list_entries_found != num_file_list_entries)
2946   {
2947     Error(ERR_INFO_LINE, "-");
2948     Error(ERR_INFO, "inconsistant config list information:");
2949     Error(ERR_INFO, "- should be:   %d (according to 'src/conf_xxx.h')",
2950           num_file_list_entries);
2951     Error(ERR_INFO, "- found to be: %d (according to 'src/conf_xxx.c')",
2952           num_file_list_entries_found);
2953     Error(ERR_EXIT,   "please fix");
2954   }
2955
2956   freeSetupFileHash(ignore_tokens_hash);
2957
2958   return file_list;
2959 }
2960
2961 static boolean token_suffix_match(char *token, char *suffix, int start_pos)
2962 {
2963   int len_token = strlen(token);
2964   int len_suffix = strlen(suffix);
2965
2966   if (start_pos < 0)    // compare suffix from end of string
2967     start_pos += len_token;
2968
2969   if (start_pos < 0 || start_pos + len_suffix > len_token)
2970     return FALSE;
2971
2972   if (strncmp(&token[start_pos], suffix, len_suffix) != 0)
2973     return FALSE;
2974
2975   if (token[start_pos + len_suffix] == '\0')
2976     return TRUE;
2977
2978   if (token[start_pos + len_suffix] == '.')
2979     return TRUE;
2980
2981   return FALSE;
2982 }
2983
2984 #define KNOWN_TOKEN_VALUE       "[KNOWN_TOKEN_VALUE]"
2985
2986 static void read_token_parameters(SetupFileHash *setup_file_hash,
2987                                   struct ConfigTypeInfo *suffix_list,
2988                                   struct FileInfo *file_list_entry)
2989 {
2990   // check for config token that is the base token without any suffixes
2991   char *filename = getHashEntry(setup_file_hash, file_list_entry->token);
2992   char *known_token_value = KNOWN_TOKEN_VALUE;
2993   int i;
2994
2995   if (filename != NULL)
2996   {
2997     setString(&file_list_entry->filename, filename);
2998
2999     // when file definition found, set all parameters to default values
3000     for (i = 0; suffix_list[i].token != NULL; i++)
3001       setString(&file_list_entry->parameter[i], suffix_list[i].value);
3002
3003     file_list_entry->redefined = TRUE;
3004
3005     // mark config file token as well known from default config
3006     setHashEntry(setup_file_hash, file_list_entry->token, known_token_value);
3007   }
3008
3009   // check for config tokens that can be build by base token and suffixes
3010   for (i = 0; suffix_list[i].token != NULL; i++)
3011   {
3012     char *token = getStringCat2(file_list_entry->token, suffix_list[i].token);
3013     char *value = getHashEntry(setup_file_hash, token);
3014
3015     if (value != NULL)
3016     {
3017       setString(&file_list_entry->parameter[i], value);
3018
3019       // mark config file token as well known from default config
3020       setHashEntry(setup_file_hash, token, known_token_value);
3021     }
3022
3023     free(token);
3024   }
3025 }
3026
3027 static void add_dynamic_file_list_entry(struct FileInfo **list,
3028                                         int *num_list_entries,
3029                                         SetupFileHash *extra_file_hash,
3030                                         struct ConfigTypeInfo *suffix_list,
3031                                         int num_suffix_list_entries,
3032                                         char *token)
3033 {
3034   struct FileInfo *new_list_entry;
3035   int parameter_array_size = num_suffix_list_entries * sizeof(char *);
3036
3037   (*num_list_entries)++;
3038   *list = checked_realloc(*list, *num_list_entries * sizeof(struct FileInfo));
3039   new_list_entry = &(*list)[*num_list_entries - 1];
3040
3041   new_list_entry->token = getStringCopy(token);
3042   new_list_entry->default_filename = NULL;
3043   new_list_entry->filename = NULL;
3044   new_list_entry->parameter = checked_calloc(parameter_array_size);
3045
3046   new_list_entry->redefined = FALSE;
3047   new_list_entry->fallback_to_default = FALSE;
3048   new_list_entry->default_is_cloned = FALSE;
3049
3050   read_token_parameters(extra_file_hash, suffix_list, new_list_entry);
3051 }
3052
3053 static void add_property_mapping(struct PropertyMapping **list,
3054                                  int *num_list_entries,
3055                                  int base_index, int ext1_index,
3056                                  int ext2_index, int ext3_index,
3057                                  int artwork_index)
3058 {
3059   struct PropertyMapping *new_list_entry;
3060
3061   (*num_list_entries)++;
3062   *list = checked_realloc(*list,
3063                           *num_list_entries * sizeof(struct PropertyMapping));
3064   new_list_entry = &(*list)[*num_list_entries - 1];
3065
3066   new_list_entry->base_index = base_index;
3067   new_list_entry->ext1_index = ext1_index;
3068   new_list_entry->ext2_index = ext2_index;
3069   new_list_entry->ext3_index = ext3_index;
3070
3071   new_list_entry->artwork_index = artwork_index;
3072 }
3073
3074 static void LoadArtworkConfigFromFilename(struct ArtworkListInfo *artwork_info,
3075                                           char *filename)
3076 {
3077   struct FileInfo *file_list = artwork_info->file_list;
3078   struct ConfigTypeInfo *suffix_list = artwork_info->suffix_list;
3079   char **base_prefixes = artwork_info->base_prefixes;
3080   char **ext1_suffixes = artwork_info->ext1_suffixes;
3081   char **ext2_suffixes = artwork_info->ext2_suffixes;
3082   char **ext3_suffixes = artwork_info->ext3_suffixes;
3083   char **ignore_tokens = artwork_info->ignore_tokens;
3084   int num_file_list_entries = artwork_info->num_file_list_entries;
3085   int num_suffix_list_entries = artwork_info->num_suffix_list_entries;
3086   int num_base_prefixes = artwork_info->num_base_prefixes;
3087   int num_ext1_suffixes = artwork_info->num_ext1_suffixes;
3088   int num_ext2_suffixes = artwork_info->num_ext2_suffixes;
3089   int num_ext3_suffixes = artwork_info->num_ext3_suffixes;
3090   int num_ignore_tokens = artwork_info->num_ignore_tokens;
3091   SetupFileHash *setup_file_hash, *valid_file_hash, *valid_file_hash_tmp;
3092   SetupFileHash *extra_file_hash, *empty_file_hash;
3093   char *known_token_value = KNOWN_TOKEN_VALUE;
3094   char *base_token_value = UNDEFINED_FILENAME;
3095   int i, j, k, l;
3096
3097   if (filename == NULL)
3098     return;
3099
3100   if ((setup_file_hash = loadSetupFileHash(filename)) == NULL)
3101     return;
3102
3103   // separate valid (defined) from empty (undefined) config token values
3104   valid_file_hash = newSetupFileHash();
3105   empty_file_hash = newSetupFileHash();
3106   BEGIN_HASH_ITERATION(setup_file_hash, itr)
3107   {
3108     char *value = HASH_ITERATION_VALUE(itr);
3109
3110     setHashEntry(*value ? valid_file_hash : empty_file_hash,
3111                  HASH_ITERATION_TOKEN(itr), value);
3112   }
3113   END_HASH_ITERATION(setup_file_hash, itr)
3114
3115   // at this point, we do not need the setup file hash anymore -- free it
3116   freeSetupFileHash(setup_file_hash);
3117
3118   // prevent changing hash while iterating over it by using a temporary copy
3119   valid_file_hash_tmp = newSetupFileHash();
3120   BEGIN_HASH_ITERATION(valid_file_hash, itr)
3121   {
3122     setHashEntry(valid_file_hash_tmp,
3123                  HASH_ITERATION_TOKEN(itr),
3124                  HASH_ITERATION_VALUE(itr));
3125   }
3126   END_HASH_ITERATION(valid_file_hash, itr)
3127
3128   // (iterate over same temporary hash, as modifications are independent)
3129
3130   // map deprecated to current tokens (using prefix match and replace)
3131   BEGIN_HASH_ITERATION(valid_file_hash_tmp, itr)
3132   {
3133     char *token = HASH_ITERATION_TOKEN(itr);
3134     char *mapped_token = get_mapped_token(token);
3135
3136     if (mapped_token != NULL)
3137     {
3138       char *value = HASH_ITERATION_VALUE(itr);
3139
3140       // add mapped token
3141       setHashEntry(valid_file_hash, mapped_token, value);
3142
3143       // ignore old token (by setting it to "known" keyword)
3144       setHashEntry(valid_file_hash, token, known_token_value);
3145
3146       free(mapped_token);
3147     }
3148   }
3149   END_HASH_ITERATION(valid_file_hash_tmp, itr)
3150
3151   // add special base tokens (using prefix match and replace)
3152   BEGIN_HASH_ITERATION(valid_file_hash_tmp, itr)
3153   {
3154     char *token = HASH_ITERATION_TOKEN(itr);
3155     char *base_token = get_special_base_token(artwork_info, token);
3156
3157     if (base_token != NULL)
3158     {
3159       // add base token only if it does not already exist
3160       if (getHashEntry(valid_file_hash, base_token) == NULL)
3161         setHashEntry(valid_file_hash, base_token, base_token_value);
3162
3163       free(base_token);
3164     }
3165   }
3166   END_HASH_ITERATION(valid_file_hash_tmp, itr)
3167
3168   // free temporary hash used for iteration
3169   freeSetupFileHash(valid_file_hash_tmp);
3170
3171   // read parameters for all known config file tokens
3172   for (i = 0; i < num_file_list_entries; i++)
3173     read_token_parameters(valid_file_hash, suffix_list, &file_list[i]);
3174
3175   // set all tokens that can be ignored here to "known" keyword
3176   for (i = 0; i < num_ignore_tokens; i++)
3177     setHashEntry(valid_file_hash, ignore_tokens[i], known_token_value);
3178
3179   // copy all unknown config file tokens to extra config hash
3180   extra_file_hash = newSetupFileHash();
3181   BEGIN_HASH_ITERATION(valid_file_hash, itr)
3182   {
3183     char *value = HASH_ITERATION_VALUE(itr);
3184
3185     if (!strEqual(value, known_token_value))
3186       setHashEntry(extra_file_hash, HASH_ITERATION_TOKEN(itr), value);
3187   }
3188   END_HASH_ITERATION(valid_file_hash, itr)
3189
3190   // at this point, we do not need the valid file hash anymore -- free it
3191   freeSetupFileHash(valid_file_hash);
3192
3193   // now try to determine valid, dynamically defined config tokens
3194
3195   BEGIN_HASH_ITERATION(extra_file_hash, itr)
3196   {
3197     struct FileInfo **dynamic_file_list =
3198       &artwork_info->dynamic_file_list;
3199     int *num_dynamic_file_list_entries =
3200       &artwork_info->num_dynamic_file_list_entries;
3201     struct PropertyMapping **property_mapping =
3202       &artwork_info->property_mapping;
3203     int *num_property_mapping_entries =
3204       &artwork_info->num_property_mapping_entries;
3205     int current_summarized_file_list_entry =
3206       artwork_info->num_file_list_entries +
3207       artwork_info->num_dynamic_file_list_entries;
3208     char *token = HASH_ITERATION_TOKEN(itr);
3209     int len_token = strlen(token);
3210     int start_pos;
3211     boolean base_prefix_found = FALSE;
3212     boolean parameter_suffix_found = FALSE;
3213
3214     // skip all parameter definitions (handled by read_token_parameters())
3215     for (i = 0; i < num_suffix_list_entries && !parameter_suffix_found; i++)
3216     {
3217       int len_suffix = strlen(suffix_list[i].token);
3218
3219       if (token_suffix_match(token, suffix_list[i].token, -len_suffix))
3220         parameter_suffix_found = TRUE;
3221     }
3222
3223     if (parameter_suffix_found)
3224       continue;
3225
3226     // ---------- step 0: search for matching base prefix ----------
3227
3228     start_pos = 0;
3229     for (i = 0; i < num_base_prefixes && !base_prefix_found; i++)
3230     {
3231       char *base_prefix = base_prefixes[i];
3232       int len_base_prefix = strlen(base_prefix);
3233       boolean ext1_suffix_found = FALSE;
3234       boolean ext2_suffix_found = FALSE;
3235       boolean ext3_suffix_found = FALSE;
3236       boolean exact_match = FALSE;
3237       int base_index = -1;
3238       int ext1_index = -1;
3239       int ext2_index = -1;
3240       int ext3_index = -1;
3241
3242       base_prefix_found = token_suffix_match(token, base_prefix, start_pos);
3243
3244       if (!base_prefix_found)
3245         continue;
3246
3247       base_index = i;
3248
3249       if (start_pos + len_base_prefix == len_token)     // exact match
3250       {
3251         exact_match = TRUE;
3252
3253         add_dynamic_file_list_entry(dynamic_file_list,
3254                                     num_dynamic_file_list_entries,
3255                                     extra_file_hash,
3256                                     suffix_list,
3257                                     num_suffix_list_entries,
3258                                     token);
3259         add_property_mapping(property_mapping,
3260                              num_property_mapping_entries,
3261                              base_index, -1, -1, -1,
3262                              current_summarized_file_list_entry);
3263         continue;
3264       }
3265
3266       // ---------- step 1: search for matching first suffix ----------
3267
3268       start_pos += len_base_prefix;
3269       for (j = 0; j < num_ext1_suffixes && !ext1_suffix_found; j++)
3270       {
3271         char *ext1_suffix = ext1_suffixes[j];
3272         int len_ext1_suffix = strlen(ext1_suffix);
3273
3274         ext1_suffix_found = token_suffix_match(token, ext1_suffix, start_pos);
3275
3276         if (!ext1_suffix_found)
3277           continue;
3278
3279         ext1_index = j;
3280
3281         if (start_pos + len_ext1_suffix == len_token)   // exact match
3282         {
3283           exact_match = TRUE;
3284
3285           add_dynamic_file_list_entry(dynamic_file_list,
3286                                       num_dynamic_file_list_entries,
3287                                       extra_file_hash,
3288                                       suffix_list,
3289                                       num_suffix_list_entries,
3290                                       token);
3291           add_property_mapping(property_mapping,
3292                                num_property_mapping_entries,
3293                                base_index, ext1_index, -1, -1,
3294                                current_summarized_file_list_entry);
3295           continue;
3296         }
3297
3298         start_pos += len_ext1_suffix;
3299       }
3300
3301       if (exact_match)
3302         break;
3303
3304       // ---------- step 2: search for matching second suffix ----------
3305
3306       for (k = 0; k < num_ext2_suffixes && !ext2_suffix_found; k++)
3307       {
3308         char *ext2_suffix = ext2_suffixes[k];
3309         int len_ext2_suffix = strlen(ext2_suffix);
3310
3311         ext2_suffix_found = token_suffix_match(token, ext2_suffix, start_pos);
3312
3313         if (!ext2_suffix_found)
3314           continue;
3315
3316         ext2_index = k;
3317
3318         if (start_pos + len_ext2_suffix == len_token)   // exact match
3319         {
3320           exact_match = TRUE;
3321
3322           add_dynamic_file_list_entry(dynamic_file_list,
3323                                       num_dynamic_file_list_entries,
3324                                       extra_file_hash,
3325                                       suffix_list,
3326                                       num_suffix_list_entries,
3327                                       token);
3328           add_property_mapping(property_mapping,
3329                                num_property_mapping_entries,
3330                                base_index, ext1_index, ext2_index, -1,
3331                                current_summarized_file_list_entry);
3332           continue;
3333         }
3334
3335         start_pos += len_ext2_suffix;
3336       }
3337
3338       if (exact_match)
3339         break;
3340
3341       // ---------- step 3: search for matching third suffix ----------
3342
3343       for (l = 0; l < num_ext3_suffixes && !ext3_suffix_found; l++)
3344       {
3345         char *ext3_suffix = ext3_suffixes[l];
3346         int len_ext3_suffix = strlen(ext3_suffix);
3347
3348         ext3_suffix_found = token_suffix_match(token, ext3_suffix, start_pos);
3349
3350         if (!ext3_suffix_found)
3351           continue;
3352
3353         ext3_index = l;
3354
3355         if (start_pos + len_ext3_suffix == len_token) // exact match
3356         {
3357           exact_match = TRUE;
3358
3359           add_dynamic_file_list_entry(dynamic_file_list,
3360                                       num_dynamic_file_list_entries,
3361                                       extra_file_hash,
3362                                       suffix_list,
3363                                       num_suffix_list_entries,
3364                                       token);
3365           add_property_mapping(property_mapping,
3366                                num_property_mapping_entries,
3367                                base_index, ext1_index, ext2_index, ext3_index,
3368                                current_summarized_file_list_entry);
3369           continue;
3370         }
3371       }
3372     }
3373   }
3374   END_HASH_ITERATION(extra_file_hash, itr)
3375
3376   if (artwork_info->num_dynamic_file_list_entries > 0)
3377   {
3378     artwork_info->dynamic_artwork_list =
3379       checked_calloc(artwork_info->num_dynamic_file_list_entries *
3380                      artwork_info->sizeof_artwork_list_entry);
3381   }
3382
3383   if (options.verbose && IS_PARENT_PROCESS())
3384   {
3385     SetupFileList *setup_file_list, *list;
3386     boolean dynamic_tokens_found = FALSE;
3387     boolean unknown_tokens_found = FALSE;
3388     boolean undefined_values_found = (hashtable_count(empty_file_hash) != 0);
3389
3390     // list may be NULL for empty artwork config files
3391     setup_file_list = loadSetupFileList(filename);
3392
3393     BEGIN_HASH_ITERATION(extra_file_hash, itr)
3394     {
3395       if (strEqual(HASH_ITERATION_VALUE(itr), known_token_value))
3396         dynamic_tokens_found = TRUE;
3397       else
3398         unknown_tokens_found = TRUE;
3399     }
3400     END_HASH_ITERATION(extra_file_hash, itr)
3401
3402     if (options.debug && dynamic_tokens_found)
3403     {
3404       Debug("config", "---");
3405       Debug("config", "dynamic token(s) found in config file:");
3406       Debug("config", "- config file: '%s'", filename);
3407
3408       for (list = setup_file_list; list != NULL; list = list->next)
3409       {
3410         char *value = getHashEntry(extra_file_hash, list->token);
3411
3412         if (value != NULL && strEqual(value, known_token_value))
3413           Debug("config", "- dynamic token: '%s'", list->token);
3414       }
3415
3416       Debug("config", "---");
3417     }
3418
3419     if (unknown_tokens_found)
3420     {
3421       Warn("---");
3422       Warn("unknown token(s) found in config file:");
3423       Warn("- config file: '%s'", filename);
3424
3425       for (list = setup_file_list; list != NULL; list = list->next)
3426       {
3427         char *value = getHashEntry(extra_file_hash, list->token);
3428
3429         if (value != NULL && !strEqual(value, known_token_value))
3430           Warn("- dynamic token: '%s'", list->token);
3431       }
3432
3433       Warn("---");
3434     }
3435
3436     if (undefined_values_found)
3437     {
3438       Warn("---");
3439       Warn("undefined values found in config file:");
3440       Warn("- config file: '%s'", filename);
3441
3442       for (list = setup_file_list; list != NULL; list = list->next)
3443       {
3444         char *value = getHashEntry(empty_file_hash, list->token);
3445
3446         if (value != NULL)
3447           Warn("- undefined value for token: '%s'", list->token);
3448       }
3449
3450       Warn("---");
3451     }
3452
3453     freeSetupFileList(setup_file_list);
3454   }
3455
3456   freeSetupFileHash(extra_file_hash);
3457   freeSetupFileHash(empty_file_hash);
3458 }
3459
3460 void LoadArtworkConfig(struct ArtworkListInfo *artwork_info)
3461 {
3462   struct FileInfo *file_list = artwork_info->file_list;
3463   int num_file_list_entries = artwork_info->num_file_list_entries;
3464   int num_suffix_list_entries = artwork_info->num_suffix_list_entries;
3465   char *filename_base = UNDEFINED_FILENAME, *filename_local;
3466   int i, j;
3467
3468   DrawInitText("Loading artwork config", 120, FC_GREEN);
3469   DrawInitText(ARTWORKINFO_FILENAME(artwork_info->type), 150, FC_YELLOW);
3470
3471   // always start with reliable default values
3472   for (i = 0; i < num_file_list_entries; i++)
3473   {
3474     setString(&file_list[i].filename, file_list[i].default_filename);
3475
3476     for (j = 0; j < num_suffix_list_entries; j++)
3477       setString(&file_list[i].parameter[j], file_list[i].default_parameter[j]);
3478
3479     file_list[i].redefined = FALSE;
3480     file_list[i].fallback_to_default = FALSE;
3481   }
3482
3483   // free previous dynamic artwork file array
3484   if (artwork_info->dynamic_file_list != NULL)
3485   {
3486     for (i = 0; i < artwork_info->num_dynamic_file_list_entries; i++)
3487     {
3488       free(artwork_info->dynamic_file_list[i].token);
3489       free(artwork_info->dynamic_file_list[i].filename);
3490       free(artwork_info->dynamic_file_list[i].parameter);
3491     }
3492
3493     free(artwork_info->dynamic_file_list);
3494     artwork_info->dynamic_file_list = NULL;
3495
3496     FreeCustomArtworkList(artwork_info, &artwork_info->dynamic_artwork_list,
3497                           &artwork_info->num_dynamic_file_list_entries);
3498   }
3499
3500   // free previous property mapping
3501   if (artwork_info->property_mapping != NULL)
3502   {
3503     free(artwork_info->property_mapping);
3504
3505     artwork_info->property_mapping = NULL;
3506     artwork_info->num_property_mapping_entries = 0;
3507   }
3508
3509   if (!GFX_OVERRIDE_ARTWORK(artwork_info->type))
3510   {
3511     // first look for special artwork configured in level series config
3512     filename_base = getCustomArtworkLevelConfigFilename(artwork_info->type);
3513
3514     if (fileExists(filename_base))
3515       LoadArtworkConfigFromFilename(artwork_info, filename_base);
3516   }
3517
3518   filename_local = getCustomArtworkConfigFilename(artwork_info->type);
3519
3520   if (filename_local != NULL && !strEqual(filename_base, filename_local))
3521     LoadArtworkConfigFromFilename(artwork_info, filename_local);
3522 }
3523
3524 static void deleteArtworkListEntry(struct ArtworkListInfo *artwork_info,
3525                                    struct ListNodeInfo **listnode)
3526 {
3527   if (*listnode)
3528   {
3529     char *filename = (*listnode)->source_filename;
3530
3531     if (--(*listnode)->num_references <= 0)
3532       deleteNodeFromList(&artwork_info->content_list, filename,
3533                          artwork_info->free_artwork);
3534
3535     *listnode = NULL;
3536   }
3537 }
3538
3539 static void replaceArtworkListEntry(struct ArtworkListInfo *artwork_info,
3540                                     struct ListNodeInfo **listnode,
3541                                     struct FileInfo *file_list_entry)
3542 {
3543   char *init_text[] =
3544   {
3545     "Loading graphics",
3546     "Loading sounds",
3547     "Loading music"
3548   };
3549
3550   ListNode *node;
3551   char *basename = file_list_entry->filename;
3552   char *filename = getCustomArtworkFilename(basename, artwork_info->type);
3553
3554   if (filename == NULL)
3555   {
3556     Warn("cannot find artwork file '%s'", basename);
3557
3558     basename = file_list_entry->default_filename;
3559
3560     // fail for cloned default artwork that has no default filename defined
3561     if (file_list_entry->default_is_cloned &&
3562         strEqual(basename, UNDEFINED_FILENAME))
3563     {
3564       int error_mode = ERR_WARN;
3565
3566       // we can get away without sounds and music, but not without graphics
3567       if (*listnode == NULL && artwork_info->type == ARTWORK_TYPE_GRAPHICS)
3568         error_mode = ERR_EXIT;
3569
3570       Error(error_mode, "token '%s' was cloned and has no default filename",
3571             file_list_entry->token);
3572
3573       return;
3574     }
3575
3576     // dynamic artwork has no default filename / skip empty default artwork
3577     if (basename == NULL || strEqual(basename, UNDEFINED_FILENAME))
3578       return;
3579
3580     file_list_entry->fallback_to_default = TRUE;
3581
3582     Warn("trying default artwork file '%s'", basename);
3583
3584     filename = getCustomArtworkFilename(basename, artwork_info->type);
3585
3586     if (filename == NULL)
3587     {
3588       int error_mode = ERR_WARN;
3589
3590       // we can get away without sounds and music, but not without graphics
3591       if (*listnode == NULL && artwork_info->type == ARTWORK_TYPE_GRAPHICS)
3592         error_mode = ERR_EXIT;
3593
3594       Error(error_mode, "cannot find default artwork file '%s'", basename);
3595
3596       return;
3597     }
3598   }
3599
3600   // check if the old and the new artwork file are the same
3601   if (*listnode && strEqual((*listnode)->source_filename, filename))
3602   {
3603     // The old and new artwork are the same (have the same filename and path).
3604     // This usually means that this artwork does not exist in this artwork set
3605     // and a fallback to the existing artwork is done.
3606
3607     return;
3608   }
3609
3610   // delete existing artwork file entry
3611   deleteArtworkListEntry(artwork_info, listnode);
3612
3613   // check if the new artwork file already exists in the list of artwork
3614   if ((node = getNodeFromKey(artwork_info->content_list, filename)) != NULL)
3615   {
3616       *listnode = (struct ListNodeInfo *)node->content;
3617       (*listnode)->num_references++;
3618
3619       return;
3620   }
3621
3622   DrawInitText(init_text[artwork_info->type], 120, FC_GREEN);
3623   DrawInitText(basename, 150, FC_YELLOW);
3624
3625   if ((*listnode = artwork_info->load_artwork(filename)) != NULL)
3626   {
3627     // add new artwork file entry to the list of artwork files
3628     (*listnode)->num_references = 1;
3629     addNodeToList(&artwork_info->content_list, (*listnode)->source_filename,
3630                   *listnode);
3631   }
3632   else
3633   {
3634     int error_mode = ERR_WARN;
3635
3636     // we can get away without sounds and music, but not without graphics
3637     if (artwork_info->type == ARTWORK_TYPE_GRAPHICS)
3638       error_mode = ERR_EXIT;
3639
3640     Error(error_mode, "cannot load artwork file '%s'", basename);
3641
3642     return;
3643   }
3644 }
3645
3646 static void LoadCustomArtwork(struct ArtworkListInfo *artwork_info,
3647                               struct ListNodeInfo **listnode,
3648                               struct FileInfo *file_list_entry)
3649 {
3650   if (strEqual(file_list_entry->filename, UNDEFINED_FILENAME))
3651   {
3652     deleteArtworkListEntry(artwork_info, listnode);
3653
3654     return;
3655   }
3656
3657   replaceArtworkListEntry(artwork_info, listnode, file_list_entry);
3658 }
3659
3660 void ReloadCustomArtworkList(struct ArtworkListInfo *artwork_info)
3661 {
3662   struct FileInfo *file_list = artwork_info->file_list;
3663   struct FileInfo *dynamic_file_list = artwork_info->dynamic_file_list;
3664   int num_file_list_entries = artwork_info->num_file_list_entries;
3665   int num_dynamic_file_list_entries =
3666     artwork_info->num_dynamic_file_list_entries;
3667   int i;
3668
3669   print_timestamp_init("ReloadCustomArtworkList");
3670
3671   for (i = 0; i < num_file_list_entries; i++)
3672     LoadCustomArtwork(artwork_info, &artwork_info->artwork_list[i],
3673                       &file_list[i]);
3674
3675   for (i = 0; i < num_dynamic_file_list_entries; i++)
3676     LoadCustomArtwork(artwork_info, &artwork_info->dynamic_artwork_list[i],
3677                       &dynamic_file_list[i]);
3678
3679   print_timestamp_done("ReloadCustomArtworkList");
3680
3681 #if 0
3682   dumpList(artwork_info->content_list);
3683 #endif
3684 }
3685
3686 static void FreeCustomArtworkList(struct ArtworkListInfo *artwork_info,
3687                                   struct ListNodeInfo ***list,
3688                                   int *num_list_entries)
3689 {
3690   int i;
3691
3692   if (*list == NULL)
3693     return;
3694
3695   for (i = 0; i < *num_list_entries; i++)
3696     deleteArtworkListEntry(artwork_info, &(*list)[i]);
3697   free(*list);
3698
3699   *list = NULL;
3700   *num_list_entries = 0;
3701 }
3702
3703 void FreeCustomArtworkLists(struct ArtworkListInfo *artwork_info)
3704 {
3705   if (artwork_info == NULL)
3706     return;
3707
3708   FreeCustomArtworkList(artwork_info, &artwork_info->artwork_list,
3709                         &artwork_info->num_file_list_entries);
3710
3711   FreeCustomArtworkList(artwork_info, &artwork_info->dynamic_artwork_list,
3712                         &artwork_info->num_dynamic_file_list_entries);
3713 }
3714
3715
3716 // ----------------------------------------------------------------------------
3717 // functions only needed for non-Unix (non-command-line) systems
3718 // (MS-DOS only; SDL/Windows creates files "stdout.txt" and "stderr.txt")
3719 // (now also added for Windows, to create files in user data directory)
3720 // ----------------------------------------------------------------------------
3721
3722 char *getLogFilename(char *basename)
3723 {
3724   return getPath2(getUserGameDataDir(), basename);
3725 }
3726
3727 void OpenLogFiles(void)
3728 {
3729   int i;
3730
3731   InitUserDataDirectory();
3732
3733   for (i = 0; i < NUM_LOGS; i++)
3734   {
3735     if ((program.log_file[i] = fopen(program.log_filename[i], MODE_WRITE))
3736         == NULL)
3737     {
3738       program.log_file[i] = program.log_file_default[i];   // reset to default
3739
3740       Warn("cannot open file '%s' for writing: %s",
3741            program.log_filename[i], strerror(errno));
3742     }
3743
3744     // output should be unbuffered so it is not truncated in a crash
3745     setbuf(program.log_file[i], NULL);
3746   }
3747 }
3748
3749 void CloseLogFiles(void)
3750 {
3751   int i;
3752
3753   for (i = 0; i < NUM_LOGS; i++)
3754     if (program.log_file[i] != program.log_file_default[i])
3755       fclose(program.log_file[i]);
3756 }
3757
3758 void DumpLogFile(int nr)
3759 {
3760   FILE *log_file = fopen(program.log_filename[nr], MODE_READ);
3761
3762   if (log_file == NULL)
3763     return;
3764
3765   while (!feof(log_file))
3766     fputc(fgetc(log_file), stdout);
3767
3768   fclose(log_file);
3769 }
3770
3771 void NotifyUserAboutErrorFile(void)
3772 {
3773 #if defined(PLATFORM_WIN32)
3774   char *title_text = getStringCat2(program.program_title, " Error Message");
3775   char *error_text = getStringCat2("The program was aborted due to an error; "
3776                                    "for details, see the following error file:"
3777                                    STRING_NEWLINE,
3778                                    program.log_filename[LOG_ERR_ID]);
3779
3780   MessageBox(NULL, error_text, title_text, MB_OK);
3781 #endif
3782 }
3783
3784
3785 // ----------------------------------------------------------------------------
3786 // the following is only for debugging purpose and normally not used
3787 // ----------------------------------------------------------------------------
3788
3789 #if DEBUG
3790
3791 #define DEBUG_PRINT_INIT_TIMESTAMPS             FALSE
3792 #define DEBUG_PRINT_INIT_TIMESTAMPS_DEPTH       10
3793
3794 #define DEBUG_NUM_TIMESTAMPS                    10
3795 #define DEBUG_TIME_IN_MICROSECONDS              0
3796
3797 #if DEBUG_TIME_IN_MICROSECONDS
3798 static double Counter_Microseconds(void)
3799 {
3800   static struct timeval base_time = { 0, 0 };
3801   struct timeval current_time;
3802   double counter;
3803
3804   gettimeofday(&current_time, NULL);
3805
3806   // reset base time in case of wrap-around
3807   if (current_time.tv_sec < base_time.tv_sec)
3808     base_time = current_time;
3809
3810   counter =
3811     ((double)(current_time.tv_sec  - base_time.tv_sec)) * 1000000 +
3812     ((double)(current_time.tv_usec - base_time.tv_usec));
3813
3814   return counter;               // return microseconds since last init
3815 }
3816 #endif
3817
3818 static char *debug_print_timestamp_get_padding(int padding_size)
3819 {
3820   static char *padding = NULL;
3821   int max_padding_size = 100;
3822
3823   if (padding == NULL)
3824   {
3825     padding = checked_calloc(max_padding_size + 1);
3826     memset(padding, ' ', max_padding_size);
3827   }
3828
3829   return &padding[MAX(0, max_padding_size - padding_size)];
3830 }
3831
3832 void debug_print_timestamp(int counter_nr, char *message)
3833 {
3834   int indent_size = 8;
3835   int padding_size = 40;
3836   float timestamp_interval;
3837
3838   if (counter_nr < 0)
3839     Error(ERR_EXIT, "debugging: invalid negative counter");
3840   else if (counter_nr >= DEBUG_NUM_TIMESTAMPS)
3841     Error(ERR_EXIT, "debugging: increase DEBUG_NUM_TIMESTAMPS in misc.c");
3842
3843 #if DEBUG_TIME_IN_MICROSECONDS
3844   static double counter[DEBUG_NUM_TIMESTAMPS][2];
3845   char *unit = "ms";
3846
3847   counter[counter_nr][0] = Counter_Microseconds();
3848 #else
3849   static int counter[DEBUG_NUM_TIMESTAMPS][2];
3850   char *unit = "s";
3851
3852   counter[counter_nr][0] = Counter();
3853 #endif
3854
3855   timestamp_interval = counter[counter_nr][0] - counter[counter_nr][1];
3856   counter[counter_nr][1] = counter[counter_nr][0];
3857
3858   if (message)
3859     Error(ERR_DEBUG, "%s%s%s %.3f %s",
3860            debug_print_timestamp_get_padding(counter_nr * indent_size),
3861            message,
3862            debug_print_timestamp_get_padding(padding_size - strlen(message)),
3863            timestamp_interval / 1000,
3864            unit);
3865 }
3866
3867 #if 0
3868 static void debug_print_parent_only(char *format, ...)
3869 {
3870   if (!IS_PARENT_PROCESS())
3871     return;
3872
3873   if (format)
3874   {
3875     va_list ap;
3876
3877     va_start(ap, format);
3878     vprintf(format, ap);
3879     va_end(ap);
3880
3881     printf("\n");
3882   }
3883 }
3884 #endif
3885
3886 #endif  // DEBUG
3887
3888 static void print_timestamp_ext(char *message, char *mode)
3889 {
3890 #if DEBUG_PRINT_INIT_TIMESTAMPS
3891   static char *debug_message = NULL;
3892   static char *last_message = NULL;
3893   static int counter_nr = 0;
3894   int max_depth = DEBUG_PRINT_INIT_TIMESTAMPS_DEPTH;
3895
3896   checked_free(debug_message);
3897   debug_message = getStringCat3(mode, " ", message);
3898
3899   if (strEqual(mode, "INIT"))
3900   {
3901     debug_print_timestamp(counter_nr, NULL);
3902
3903     if (counter_nr + 1 < max_depth)
3904       debug_print_timestamp(counter_nr, debug_message);
3905
3906     counter_nr++;
3907
3908     debug_print_timestamp(counter_nr, NULL);
3909   }
3910   else if (strEqual(mode, "DONE"))
3911   {
3912     counter_nr--;
3913
3914     if (counter_nr + 1 < max_depth ||
3915         (counter_nr == 0 && max_depth == 1))
3916     {
3917       last_message = message;
3918
3919       if (counter_nr == 0 && max_depth == 1)
3920       {
3921         checked_free(debug_message);
3922         debug_message = getStringCat3("TIME", " ", message);
3923       }
3924
3925       debug_print_timestamp(counter_nr, debug_message);
3926     }
3927   }
3928   else if (!strEqual(mode, "TIME") ||
3929            !strEqual(message, last_message))
3930   {
3931     if (counter_nr < max_depth)
3932       debug_print_timestamp(counter_nr, debug_message);
3933   }
3934 #endif
3935 }
3936
3937 void print_timestamp_init(char *message)
3938 {
3939   print_timestamp_ext(message, "INIT");
3940 }
3941
3942 void print_timestamp_time(char *message)
3943 {
3944   print_timestamp_ext(message, "TIME");
3945 }
3946
3947 void print_timestamp_done(char *message)
3948 {
3949   print_timestamp_ext(message, "DONE");
3950 }