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