rnd-20100401-1-src
[rocksndiamonds.git] / src / libgame / setup.c
1 /***********************************************************
2 * Artsoft Retro-Game Library                               *
3 *----------------------------------------------------------*
4 * (c) 1994-2006 Artsoft Entertainment                      *
5 *               Holger Schemel                             *
6 *               Detmolder Strasse 189                      *
7 *               33604 Bielefeld                            *
8 *               Germany                                    *
9 *               e-mail: info@artsoft.org                   *
10 *----------------------------------------------------------*
11 * setup.c                                                  *
12 ***********************************************************/
13
14 #include <sys/types.h>
15 #include <sys/stat.h>
16 #include <dirent.h>
17 #include <string.h>
18 #include <unistd.h>
19
20 #include "platform.h"
21
22 #if !defined(PLATFORM_WIN32)
23 #include <pwd.h>
24 #include <sys/param.h>
25 #endif
26
27 #include "setup.h"
28 #include "joystick.h"
29 #include "text.h"
30 #include "misc.h"
31 #include "hash.h"
32
33
34 #define NUM_LEVELCLASS_DESC     8
35
36 static char *levelclass_desc[NUM_LEVELCLASS_DESC] =
37 {
38   "Tutorial Levels",
39   "Classic Originals",
40   "Contributions",
41   "Private Levels",
42   "Boulderdash",
43   "Emerald Mine",
44   "Supaplex",
45   "DX Boulderdash"
46 };
47
48
49 #define LEVELCOLOR(n)   (IS_LEVELCLASS_TUTORIAL(n) ?            FC_BLUE :    \
50                          IS_LEVELCLASS_CLASSICS(n) ?            FC_RED :     \
51                          IS_LEVELCLASS_BD(n) ?                  FC_YELLOW :  \
52                          IS_LEVELCLASS_EM(n) ?                  FC_YELLOW :  \
53                          IS_LEVELCLASS_SP(n) ?                  FC_YELLOW :  \
54                          IS_LEVELCLASS_DX(n) ?                  FC_YELLOW :  \
55                          IS_LEVELCLASS_SB(n) ?                  FC_YELLOW :  \
56                          IS_LEVELCLASS_CONTRIB(n) ?             FC_GREEN :   \
57                          IS_LEVELCLASS_PRIVATE(n) ?             FC_RED :     \
58                          FC_BLUE)
59
60 #define LEVELSORTING(n) (IS_LEVELCLASS_TUTORIAL(n) ?            0 :     \
61                          IS_LEVELCLASS_CLASSICS(n) ?            1 :     \
62                          IS_LEVELCLASS_BD(n) ?                  2 :     \
63                          IS_LEVELCLASS_EM(n) ?                  3 :     \
64                          IS_LEVELCLASS_SP(n) ?                  4 :     \
65                          IS_LEVELCLASS_DX(n) ?                  5 :     \
66                          IS_LEVELCLASS_SB(n) ?                  6 :     \
67                          IS_LEVELCLASS_CONTRIB(n) ?             7 :     \
68                          IS_LEVELCLASS_PRIVATE(n) ?             8 :     \
69                          9)
70
71 #define ARTWORKCOLOR(n) (IS_ARTWORKCLASS_CLASSICS(n) ?          FC_RED :     \
72                          IS_ARTWORKCLASS_CONTRIB(n) ?           FC_GREEN :   \
73                          IS_ARTWORKCLASS_PRIVATE(n) ?           FC_RED :     \
74                          IS_ARTWORKCLASS_LEVEL(n) ?             FC_YELLOW :  \
75                          FC_BLUE)
76
77 #define ARTWORKSORTING(n) (IS_ARTWORKCLASS_CLASSICS(n) ?        0 :     \
78                            IS_ARTWORKCLASS_LEVEL(n) ?           1 :     \
79                            IS_ARTWORKCLASS_CONTRIB(n) ?         2 :     \
80                            IS_ARTWORKCLASS_PRIVATE(n) ?         3 :     \
81                            9)
82
83 #define TOKEN_VALUE_POSITION_SHORT              32
84 #define TOKEN_VALUE_POSITION_DEFAULT            40
85 #define TOKEN_COMMENT_POSITION_DEFAULT          60
86
87 #define MAX_COOKIE_LEN                          256
88
89
90 static void setTreeInfoToDefaults(TreeInfo *, int);
91 static TreeInfo *getTreeInfoCopy(TreeInfo *ti);
92 static int compareTreeInfoEntries(const void *, const void *);
93
94 static int token_value_position   = TOKEN_VALUE_POSITION_DEFAULT;
95 static int token_comment_position = TOKEN_COMMENT_POSITION_DEFAULT;
96
97 static SetupFileHash *artworkinfo_cache_old = NULL;
98 static SetupFileHash *artworkinfo_cache_new = NULL;
99 static boolean use_artworkinfo_cache = TRUE;
100
101
102 /* ------------------------------------------------------------------------- */
103 /* file functions                                                            */
104 /* ------------------------------------------------------------------------- */
105
106 static char *getLevelClassDescription(TreeInfo *ti)
107 {
108   int position = ti->sort_priority / 100;
109
110   if (position >= 0 && position < NUM_LEVELCLASS_DESC)
111     return levelclass_desc[position];
112   else
113     return "Unknown Level Class";
114 }
115
116 static char *getUserLevelDir(char *level_subdir)
117 {
118   static char *userlevel_dir = NULL;
119   char *data_dir = getUserGameDataDir();
120   char *userlevel_subdir = LEVELS_DIRECTORY;
121
122   checked_free(userlevel_dir);
123
124   if (level_subdir != NULL)
125     userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir);
126   else
127     userlevel_dir = getPath2(data_dir, userlevel_subdir);
128
129   return userlevel_dir;
130 }
131
132 static char *getScoreDir(char *level_subdir)
133 {
134   static char *score_dir = NULL;
135   char *data_dir = getCommonDataDir();
136   char *score_subdir = SCORES_DIRECTORY;
137
138   checked_free(score_dir);
139
140   if (level_subdir != NULL)
141     score_dir = getPath3(data_dir, score_subdir, level_subdir);
142   else
143     score_dir = getPath2(data_dir, score_subdir);
144
145   return score_dir;
146 }
147
148 static char *getLevelSetupDir(char *level_subdir)
149 {
150   static char *levelsetup_dir = NULL;
151   char *data_dir = getUserGameDataDir();
152   char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
153
154   checked_free(levelsetup_dir);
155
156   if (level_subdir != NULL)
157     levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir);
158   else
159     levelsetup_dir = getPath2(data_dir, levelsetup_subdir);
160
161   return levelsetup_dir;
162 }
163
164 static char *getCacheDir()
165 {
166   static char *cache_dir = NULL;
167
168   if (cache_dir == NULL)
169     cache_dir = getPath2(getUserGameDataDir(), CACHE_DIRECTORY);
170
171   return cache_dir;
172 }
173
174 static char *getLevelDirFromTreeInfo(TreeInfo *node)
175 {
176   static char *level_dir = NULL;
177
178   if (node == NULL)
179     return options.level_directory;
180
181   checked_free(level_dir);
182
183   level_dir = getPath2((node->in_user_dir ? getUserLevelDir(NULL) :
184                         options.level_directory), node->fullpath);
185
186   return level_dir;
187 }
188
189 char *getCurrentLevelDir()
190 {
191   return getLevelDirFromTreeInfo(leveldir_current);
192 }
193
194 static char *getTapeDir(char *level_subdir)
195 {
196   static char *tape_dir = NULL;
197   char *data_dir = getUserGameDataDir();
198   char *tape_subdir = TAPES_DIRECTORY;
199
200   checked_free(tape_dir);
201
202   if (level_subdir != NULL)
203     tape_dir = getPath3(data_dir, tape_subdir, level_subdir);
204   else
205     tape_dir = getPath2(data_dir, tape_subdir);
206
207   return tape_dir;
208 }
209
210 static char *getSolutionTapeDir()
211 {
212   static char *tape_dir = NULL;
213   char *data_dir = getCurrentLevelDir();
214   char *tape_subdir = TAPES_DIRECTORY;
215
216   checked_free(tape_dir);
217
218   tape_dir = getPath2(data_dir, tape_subdir);
219
220   return tape_dir;
221 }
222
223 static char *getDefaultGraphicsDir(char *graphics_subdir)
224 {
225   static char *graphics_dir = NULL;
226
227   if (graphics_subdir == NULL)
228     return options.graphics_directory;
229
230   checked_free(graphics_dir);
231
232   graphics_dir = getPath2(options.graphics_directory, graphics_subdir);
233
234   return graphics_dir;
235 }
236
237 static char *getDefaultSoundsDir(char *sounds_subdir)
238 {
239   static char *sounds_dir = NULL;
240
241   if (sounds_subdir == NULL)
242     return options.sounds_directory;
243
244   checked_free(sounds_dir);
245
246   sounds_dir = getPath2(options.sounds_directory, sounds_subdir);
247
248   return sounds_dir;
249 }
250
251 static char *getDefaultMusicDir(char *music_subdir)
252 {
253   static char *music_dir = NULL;
254
255   if (music_subdir == NULL)
256     return options.music_directory;
257
258   checked_free(music_dir);
259
260   music_dir = getPath2(options.music_directory, music_subdir);
261
262   return music_dir;
263 }
264
265 static char *getClassicArtworkSet(int type)
266 {
267   return (type == TREE_TYPE_GRAPHICS_DIR ? GFX_CLASSIC_SUBDIR :
268           type == TREE_TYPE_SOUNDS_DIR   ? SND_CLASSIC_SUBDIR :
269           type == TREE_TYPE_MUSIC_DIR    ? MUS_CLASSIC_SUBDIR : "");
270 }
271
272 static char *getClassicArtworkDir(int type)
273 {
274   return (type == TREE_TYPE_GRAPHICS_DIR ?
275           getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR) :
276           type == TREE_TYPE_SOUNDS_DIR ?
277           getDefaultSoundsDir(SND_CLASSIC_SUBDIR) :
278           type == TREE_TYPE_MUSIC_DIR ?
279           getDefaultMusicDir(MUS_CLASSIC_SUBDIR) : "");
280 }
281
282 static char *getUserGraphicsDir()
283 {
284   static char *usergraphics_dir = NULL;
285
286   if (usergraphics_dir == NULL)
287     usergraphics_dir = getPath2(getUserGameDataDir(), GRAPHICS_DIRECTORY);
288
289   return usergraphics_dir;
290 }
291
292 static char *getUserSoundsDir()
293 {
294   static char *usersounds_dir = NULL;
295
296   if (usersounds_dir == NULL)
297     usersounds_dir = getPath2(getUserGameDataDir(), SOUNDS_DIRECTORY);
298
299   return usersounds_dir;
300 }
301
302 static char *getUserMusicDir()
303 {
304   static char *usermusic_dir = NULL;
305
306   if (usermusic_dir == NULL)
307     usermusic_dir = getPath2(getUserGameDataDir(), MUSIC_DIRECTORY);
308
309   return usermusic_dir;
310 }
311
312 static char *getSetupArtworkDir(TreeInfo *ti)
313 {
314   static char *artwork_dir = NULL;
315
316   checked_free(artwork_dir);
317
318   artwork_dir = getPath2(ti->basepath, ti->fullpath);
319
320   return artwork_dir;
321 }
322
323 char *setLevelArtworkDir(TreeInfo *ti)
324 {
325   char **artwork_path_ptr, **artwork_set_ptr;
326   TreeInfo *level_artwork;
327
328   if (ti == NULL || leveldir_current == NULL)
329     return NULL;
330
331   artwork_path_ptr = LEVELDIR_ARTWORK_PATH_PTR(leveldir_current, ti->type);
332   artwork_set_ptr  = LEVELDIR_ARTWORK_SET_PTR( leveldir_current, ti->type);
333
334   checked_free(*artwork_path_ptr);
335
336   if ((level_artwork = getTreeInfoFromIdentifier(ti, *artwork_set_ptr)))
337   {
338     *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
339   }
340   else
341   {
342     /*
343       No (or non-existing) artwork configured in "levelinfo.conf". This would
344       normally result in using the artwork configured in the setup menu. But
345       if an artwork subdirectory exists (which might contain custom artwork
346       or an artwork configuration file), this level artwork must be treated
347       as relative to the default "classic" artwork, not to the artwork that
348       is currently configured in the setup menu.
349
350       Update: For "special" versions of R'n'D (like "R'n'D jue"), do not use
351       the "default" artwork (which would be "jue0" for "R'n'D jue"), but use
352       the real "classic" artwork from the original R'n'D (like "gfx_classic").
353     */
354
355     char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
356
357     checked_free(*artwork_set_ptr);
358
359     if (fileExists(dir))
360     {
361       *artwork_path_ptr = getStringCopy(getClassicArtworkDir(ti->type));
362       *artwork_set_ptr = getStringCopy(getClassicArtworkSet(ti->type));
363     }
364     else
365     {
366       *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
367       *artwork_set_ptr = NULL;
368     }
369
370     free(dir);
371   }
372
373   return *artwork_set_ptr;
374 }
375
376 inline static char *getLevelArtworkSet(int type)
377 {
378   if (leveldir_current == NULL)
379     return NULL;
380
381   return LEVELDIR_ARTWORK_SET(leveldir_current, type);
382 }
383
384 inline static char *getLevelArtworkDir(int type)
385 {
386   if (leveldir_current == NULL)
387     return UNDEFINED_FILENAME;
388
389   return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
390 }
391
392 char *getTapeFilename(int nr)
393 {
394   static char *filename = NULL;
395   char basename[MAX_FILENAME_LEN];
396
397   checked_free(filename);
398
399   sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
400   filename = getPath2(getTapeDir(leveldir_current->subdir), basename);
401
402   return filename;
403 }
404
405 char *getSolutionTapeFilename(int nr)
406 {
407   static char *filename = NULL;
408   char basename[MAX_FILENAME_LEN];
409
410   checked_free(filename);
411
412   sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
413   filename = getPath2(getSolutionTapeDir(), basename);
414
415   if (!fileExists(filename))
416   {
417     static char *filename_sln = NULL;
418
419     checked_free(filename_sln);
420
421     sprintf(basename, "%03d.sln", nr);
422     filename_sln = getPath2(getSolutionTapeDir(), basename);
423
424     if (fileExists(filename_sln))
425       return filename_sln;
426   }
427
428   return filename;
429 }
430
431 char *getScoreFilename(int nr)
432 {
433   static char *filename = NULL;
434   char basename[MAX_FILENAME_LEN];
435
436   checked_free(filename);
437
438   sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
439   filename = getPath2(getScoreDir(leveldir_current->subdir), basename);
440
441   return filename;
442 }
443
444 char *getSetupFilename()
445 {
446   static char *filename = NULL;
447
448   checked_free(filename);
449
450   filename = getPath2(getSetupDir(), SETUP_FILENAME);
451
452   return filename;
453 }
454
455 char *getEditorSetupFilename()
456 {
457   static char *filename = NULL;
458
459   checked_free(filename);
460   filename = getPath2(getCurrentLevelDir(), EDITORSETUP_FILENAME);
461
462   if (fileExists(filename))
463     return filename;
464
465   checked_free(filename);
466   filename = getPath2(getSetupDir(), EDITORSETUP_FILENAME);
467
468   return filename;
469 }
470
471 char *getHelpAnimFilename()
472 {
473   static char *filename = NULL;
474
475   checked_free(filename);
476
477   filename = getPath2(getCurrentLevelDir(), HELPANIM_FILENAME);
478
479   return filename;
480 }
481
482 char *getHelpTextFilename()
483 {
484   static char *filename = NULL;
485
486   checked_free(filename);
487
488   filename = getPath2(getCurrentLevelDir(), HELPTEXT_FILENAME);
489
490   return filename;
491 }
492
493 char *getLevelSetInfoFilename()
494 {
495   static char *filename = NULL;
496   char *basenames[] =
497   {
498     "README",
499     "README.TXT",
500     "README.txt",
501     "Readme",
502     "Readme.txt",
503     "readme",
504     "readme.txt",
505
506     NULL
507   };
508   int i;
509
510   for (i = 0; basenames[i] != NULL; i++)
511   {
512     checked_free(filename);
513     filename = getPath2(getCurrentLevelDir(), basenames[i]);
514
515     if (fileExists(filename))
516       return filename;
517   }
518
519   return NULL;
520 }
521
522 char *getLevelSetTitleMessageBasename(int nr, boolean initial)
523 {
524   static char basename[32];
525
526   sprintf(basename, "%s_%d.txt",
527           (initial ? "titlemessage_initial" : "titlemessage"), nr + 1);
528
529   return basename;
530 }
531
532 char *getLevelSetTitleMessageFilename(int nr, boolean initial)
533 {
534   static char *filename = NULL;
535   char *basename;
536   boolean skip_setup_artwork = FALSE;
537
538   checked_free(filename);
539
540   basename = getLevelSetTitleMessageBasename(nr, initial);
541
542   if (!gfx.override_level_graphics)
543   {
544     /* 1st try: look for special artwork in current level series directory */
545     filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
546     if (fileExists(filename))
547       return filename;
548
549     free(filename);
550
551     /* 2nd try: look for message file in current level set directory */
552     filename = getPath2(getCurrentLevelDir(), basename);
553     if (fileExists(filename))
554       return filename;
555
556     free(filename);
557
558     /* check if there is special artwork configured in level series config */
559     if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
560     {
561       /* 3rd try: look for special artwork configured in level series config */
562       filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
563       if (fileExists(filename))
564         return filename;
565
566       free(filename);
567
568       /* take missing artwork configured in level set config from default */
569       skip_setup_artwork = TRUE;
570     }
571   }
572
573   if (!skip_setup_artwork)
574   {
575     /* 4th try: look for special artwork in configured artwork directory */
576     filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
577     if (fileExists(filename))
578       return filename;
579
580     free(filename);
581   }
582
583   /* 5th try: look for default artwork in new default artwork directory */
584   filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
585   if (fileExists(filename))
586     return filename;
587
588   free(filename);
589
590   /* 6th try: look for default artwork in old default artwork directory */
591   filename = getPath2(options.graphics_directory, basename);
592   if (fileExists(filename))
593     return filename;
594
595   return NULL;          /* cannot find specified artwork file anywhere */
596 }
597
598 static char *getCorrectedArtworkBasename(char *basename)
599 {
600   char *basename_corrected = basename;
601
602 #if defined(PLATFORM_MSDOS)
603   if (program.filename_prefix != NULL)
604   {
605     int prefix_len = strlen(program.filename_prefix);
606
607     if (strncmp(basename, program.filename_prefix, prefix_len) == 0)
608       basename_corrected = &basename[prefix_len];
609
610     /* if corrected filename is still longer than standard MS-DOS filename
611        size (8 characters + 1 dot + 3 characters file extension), shorten
612        filename by writing file extension after 8th basename character */
613     if (strlen(basename_corrected) > 8 + 1 + 3)
614     {
615       static char *msdos_filename = NULL;
616
617       checked_free(msdos_filename);
618
619       msdos_filename = getStringCopy(basename_corrected);
620       strncpy(&msdos_filename[8], &basename[strlen(basename) - (1+3)], 1+3 +1);
621
622       basename_corrected = msdos_filename;
623     }
624   }
625 #endif
626
627   return basename_corrected;
628 }
629
630 char *getCustomImageFilename(char *basename)
631 {
632   static char *filename = NULL;
633   boolean skip_setup_artwork = FALSE;
634
635   checked_free(filename);
636
637   basename = getCorrectedArtworkBasename(basename);
638
639   if (!gfx.override_level_graphics)
640   {
641     /* 1st try: look for special artwork in current level series directory */
642     filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
643     if (fileExists(filename))
644       return filename;
645
646     free(filename);
647
648     /* check if there is special artwork configured in level series config */
649     if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
650     {
651       /* 2nd try: look for special artwork configured in level series config */
652       filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
653       if (fileExists(filename))
654         return filename;
655
656       free(filename);
657
658       /* take missing artwork configured in level set config from default */
659       skip_setup_artwork = TRUE;
660     }
661   }
662
663   if (!skip_setup_artwork)
664   {
665     /* 3rd try: look for special artwork in configured artwork directory */
666     filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
667     if (fileExists(filename))
668       return filename;
669
670     free(filename);
671   }
672
673   /* 4th try: look for default artwork in new default artwork directory */
674   filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
675   if (fileExists(filename))
676     return filename;
677
678   free(filename);
679
680   /* 5th try: look for default artwork in old default artwork directory */
681   filename = getPath2(options.graphics_directory, basename);
682   if (fileExists(filename))
683     return filename;
684
685 #if defined(CREATE_SPECIAL_EDITION)
686   free(filename);
687
688   if (options.debug)
689     Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)", basename);
690
691   /* 6th try: look for fallback artwork in old default artwork directory */
692   /* (needed to prevent errors when trying to access unused artwork files) */
693   filename = getPath2(options.graphics_directory, GFX_FALLBACK_FILENAME);
694   if (fileExists(filename))
695     return filename;
696 #endif
697
698   return NULL;          /* cannot find specified artwork file anywhere */
699 }
700
701 char *getCustomSoundFilename(char *basename)
702 {
703   static char *filename = NULL;
704   boolean skip_setup_artwork = FALSE;
705
706   checked_free(filename);
707
708   basename = getCorrectedArtworkBasename(basename);
709
710   if (!gfx.override_level_sounds)
711   {
712     /* 1st try: look for special artwork in current level series directory */
713     filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
714     if (fileExists(filename))
715       return filename;
716
717     free(filename);
718
719     /* check if there is special artwork configured in level series config */
720     if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
721     {
722       /* 2nd try: look for special artwork configured in level series config */
723       filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
724       if (fileExists(filename))
725         return filename;
726
727       free(filename);
728
729       /* take missing artwork configured in level set config from default */
730       skip_setup_artwork = TRUE;
731     }
732   }
733
734   if (!skip_setup_artwork)
735   {
736     /* 3rd try: look for special artwork in configured artwork directory */
737     filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
738     if (fileExists(filename))
739       return filename;
740
741     free(filename);
742   }
743
744   /* 4th try: look for default artwork in new default artwork directory */
745   filename = getPath2(getDefaultSoundsDir(SND_DEFAULT_SUBDIR), basename);
746   if (fileExists(filename))
747     return filename;
748
749   free(filename);
750
751   /* 5th try: look for default artwork in old default artwork directory */
752   filename = getPath2(options.sounds_directory, basename);
753   if (fileExists(filename))
754     return filename;
755
756 #if defined(CREATE_SPECIAL_EDITION)
757   free(filename);
758
759   if (options.debug)
760     Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)", basename);
761
762   /* 6th try: look for fallback artwork in old default artwork directory */
763   /* (needed to prevent errors when trying to access unused artwork files) */
764   filename = getPath2(options.sounds_directory, SND_FALLBACK_FILENAME);
765   if (fileExists(filename))
766     return filename;
767 #endif
768
769   return NULL;          /* cannot find specified artwork file anywhere */
770 }
771
772 char *getCustomMusicFilename(char *basename)
773 {
774   static char *filename = NULL;
775   boolean skip_setup_artwork = FALSE;
776
777   checked_free(filename);
778
779   basename = getCorrectedArtworkBasename(basename);
780
781   if (!gfx.override_level_music)
782   {
783     /* 1st try: look for special artwork in current level series directory */
784     filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
785     if (fileExists(filename))
786       return filename;
787
788     free(filename);
789
790     /* check if there is special artwork configured in level series config */
791     if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
792     {
793       /* 2nd try: look for special artwork configured in level series config */
794       filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
795       if (fileExists(filename))
796         return filename;
797
798       free(filename);
799
800       /* take missing artwork configured in level set config from default */
801       skip_setup_artwork = TRUE;
802     }
803   }
804
805   if (!skip_setup_artwork)
806   {
807     /* 3rd try: look for special artwork in configured artwork directory */
808     filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
809     if (fileExists(filename))
810       return filename;
811
812     free(filename);
813   }
814
815   /* 4th try: look for default artwork in new default artwork directory */
816   filename = getPath2(getDefaultMusicDir(MUS_DEFAULT_SUBDIR), basename);
817   if (fileExists(filename))
818     return filename;
819
820   free(filename);
821
822   /* 5th try: look for default artwork in old default artwork directory */
823   filename = getPath2(options.music_directory, basename);
824   if (fileExists(filename))
825     return filename;
826
827 #if defined(CREATE_SPECIAL_EDITION)
828   free(filename);
829
830   if (options.debug)
831     Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)", basename);
832
833   /* 6th try: look for fallback artwork in old default artwork directory */
834   /* (needed to prevent errors when trying to access unused artwork files) */
835   filename = getPath2(options.music_directory, MUS_FALLBACK_FILENAME);
836   if (fileExists(filename))
837     return filename;
838 #endif
839
840   return NULL;          /* cannot find specified artwork file anywhere */
841 }
842
843 char *getCustomArtworkFilename(char *basename, int type)
844 {
845   if (type == ARTWORK_TYPE_GRAPHICS)
846     return getCustomImageFilename(basename);
847   else if (type == ARTWORK_TYPE_SOUNDS)
848     return getCustomSoundFilename(basename);
849   else if (type == ARTWORK_TYPE_MUSIC)
850     return getCustomMusicFilename(basename);
851   else
852     return UNDEFINED_FILENAME;
853 }
854
855 char *getCustomArtworkConfigFilename(int type)
856 {
857   return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
858 }
859
860 char *getCustomArtworkLevelConfigFilename(int type)
861 {
862   static char *filename = NULL;
863
864   checked_free(filename);
865
866   filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
867
868   return filename;
869 }
870
871 char *getCustomMusicDirectory(void)
872 {
873   static char *directory = NULL;
874   boolean skip_setup_artwork = FALSE;
875
876   checked_free(directory);
877
878   if (!gfx.override_level_music)
879   {
880     /* 1st try: look for special artwork in current level series directory */
881     directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
882     if (fileExists(directory))
883       return directory;
884
885     free(directory);
886
887     /* check if there is special artwork configured in level series config */
888     if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
889     {
890       /* 2nd try: look for special artwork configured in level series config */
891       directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
892       if (fileExists(directory))
893         return directory;
894
895       free(directory);
896
897       /* take missing artwork configured in level set config from default */
898       skip_setup_artwork = TRUE;
899     }
900   }
901
902   if (!skip_setup_artwork)
903   {
904     /* 3rd try: look for special artwork in configured artwork directory */
905     directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
906     if (fileExists(directory))
907       return directory;
908
909     free(directory);
910   }
911
912   /* 4th try: look for default artwork in new default artwork directory */
913   directory = getStringCopy(getDefaultMusicDir(MUS_DEFAULT_SUBDIR));
914   if (fileExists(directory))
915     return directory;
916
917   free(directory);
918
919   /* 5th try: look for default artwork in old default artwork directory */
920   directory = getStringCopy(options.music_directory);
921   if (fileExists(directory))
922     return directory;
923
924   return NULL;          /* cannot find specified artwork file anywhere */
925 }
926
927 void InitTapeDirectory(char *level_subdir)
928 {
929   createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
930   createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
931   createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
932 }
933
934 void InitScoreDirectory(char *level_subdir)
935 {
936   createDirectory(getCommonDataDir(), "common data", PERMS_PUBLIC);
937   createDirectory(getScoreDir(NULL), "main score", PERMS_PUBLIC);
938   createDirectory(getScoreDir(level_subdir), "level score", PERMS_PUBLIC);
939 }
940
941 static void SaveUserLevelInfo();
942
943 void InitUserLevelDirectory(char *level_subdir)
944 {
945   if (!fileExists(getUserLevelDir(level_subdir)))
946   {
947     createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
948     createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
949     createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
950
951     SaveUserLevelInfo();
952   }
953 }
954
955 void InitLevelSetupDirectory(char *level_subdir)
956 {
957   createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
958   createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
959   createDirectory(getLevelSetupDir(level_subdir), "level setup", PERMS_PRIVATE);
960 }
961
962 void InitCacheDirectory()
963 {
964   createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
965   createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
966 }
967
968
969 /* ------------------------------------------------------------------------- */
970 /* some functions to handle lists of level and artwork directories           */
971 /* ------------------------------------------------------------------------- */
972
973 TreeInfo *newTreeInfo()
974 {
975   return checked_calloc(sizeof(TreeInfo));
976 }
977
978 TreeInfo *newTreeInfo_setDefaults(int type)
979 {
980   TreeInfo *ti = newTreeInfo();
981
982   setTreeInfoToDefaults(ti, type);
983
984   return ti;
985 }
986
987 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
988 {
989   node_new->next = *node_first;
990   *node_first = node_new;
991 }
992
993 int numTreeInfo(TreeInfo *node)
994 {
995   int num = 0;
996
997   while (node)
998   {
999     num++;
1000     node = node->next;
1001   }
1002
1003   return num;
1004 }
1005
1006 boolean validLevelSeries(TreeInfo *node)
1007 {
1008   return (node != NULL && !node->node_group && !node->parent_link);
1009 }
1010
1011 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
1012 {
1013   if (node == NULL)
1014     return NULL;
1015
1016   if (node->node_group)         /* enter level group (step down into tree) */
1017     return getFirstValidTreeInfoEntry(node->node_group);
1018   else if (node->parent_link)   /* skip start entry of level group */
1019   {
1020     if (node->next)             /* get first real level series entry */
1021       return getFirstValidTreeInfoEntry(node->next);
1022     else                        /* leave empty level group and go on */
1023       return getFirstValidTreeInfoEntry(node->node_parent->next);
1024   }
1025   else                          /* this seems to be a regular level series */
1026     return node;
1027 }
1028
1029 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1030 {
1031   if (node == NULL)
1032     return NULL;
1033
1034   if (node->node_parent == NULL)                /* top level group */
1035     return *node->node_top;
1036   else                                          /* sub level group */
1037     return node->node_parent->node_group;
1038 }
1039
1040 int numTreeInfoInGroup(TreeInfo *node)
1041 {
1042   return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1043 }
1044
1045 int posTreeInfo(TreeInfo *node)
1046 {
1047   TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1048   int pos = 0;
1049
1050   while (node_cmp)
1051   {
1052     if (node_cmp == node)
1053       return pos;
1054
1055     pos++;
1056     node_cmp = node_cmp->next;
1057   }
1058
1059   return 0;
1060 }
1061
1062 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1063 {
1064   TreeInfo *node_default = node;
1065   int pos_cmp = 0;
1066
1067   while (node)
1068   {
1069     if (pos_cmp == pos)
1070       return node;
1071
1072     pos_cmp++;
1073     node = node->next;
1074   }
1075
1076   return node_default;
1077 }
1078
1079 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1080 {
1081   if (identifier == NULL)
1082     return NULL;
1083
1084   while (node)
1085   {
1086     if (node->node_group)
1087     {
1088       TreeInfo *node_group;
1089
1090       node_group = getTreeInfoFromIdentifier(node->node_group, identifier);
1091
1092       if (node_group)
1093         return node_group;
1094     }
1095     else if (!node->parent_link)
1096     {
1097       if (strEqual(identifier, node->identifier))
1098         return node;
1099     }
1100
1101     node = node->next;
1102   }
1103
1104   return NULL;
1105 }
1106
1107 TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1108                         TreeInfo *node, boolean skip_sets_without_levels)
1109 {
1110   TreeInfo *node_new;
1111
1112   if (node == NULL)
1113     return NULL;
1114
1115   if (!node->parent_link && !node->level_group &&
1116       skip_sets_without_levels && node->levels == 0)
1117     return cloneTreeNode(node_top, node_parent, node->next,
1118                          skip_sets_without_levels);
1119
1120 #if 1
1121   node_new = getTreeInfoCopy(node);             /* copy complete node */
1122 #else
1123   node_new = newTreeInfo();
1124
1125   *node_new = *node;                            /* copy complete node */
1126 #endif
1127
1128   node_new->node_top = node_top;                /* correct top node link */
1129   node_new->node_parent = node_parent;          /* correct parent node link */
1130
1131   if (node->level_group)
1132     node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1133                                          skip_sets_without_levels);
1134
1135   node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1136                                  skip_sets_without_levels);
1137   
1138   return node_new;
1139 }
1140
1141 void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1142 {
1143   TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1144
1145   *ti_new = ti_cloned;
1146 }
1147
1148 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1149 {
1150   boolean settings_changed = FALSE;
1151
1152   while (node)
1153   {
1154     if (node->graphics_set_ecs && !setup.prefer_aga_graphics &&
1155         !strEqual(node->graphics_set, node->graphics_set_ecs))
1156     {
1157       setString(&node->graphics_set, node->graphics_set_ecs);
1158       settings_changed = TRUE;
1159     }
1160     else if (node->graphics_set_aga && setup.prefer_aga_graphics &&
1161              !strEqual(node->graphics_set, node->graphics_set_aga))
1162     {
1163       setString(&node->graphics_set, node->graphics_set_aga);
1164       settings_changed = TRUE;
1165     }
1166
1167     if (node->node_group != NULL)
1168       settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1169
1170     node = node->next;
1171   }
1172
1173   return settings_changed;
1174 }
1175
1176 void dumpTreeInfo(TreeInfo *node, int depth)
1177 {
1178   int i;
1179
1180   printf("Dumping TreeInfo:\n");
1181
1182   while (node)
1183   {
1184     for (i = 0; i < (depth + 1) * 3; i++)
1185       printf(" ");
1186
1187     printf("subdir == '%s' ['%s', '%s'] [%d])\n",
1188            node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1189
1190     if (node->node_group != NULL)
1191       dumpTreeInfo(node->node_group, depth + 1);
1192
1193     node = node->next;
1194   }
1195 }
1196
1197 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1198                                 int (*compare_function)(const void *,
1199                                                         const void *))
1200 {
1201   int num_nodes = numTreeInfo(*node_first);
1202   TreeInfo **sort_array;
1203   TreeInfo *node = *node_first;
1204   int i = 0;
1205
1206   if (num_nodes == 0)
1207     return;
1208
1209   /* allocate array for sorting structure pointers */
1210   sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1211
1212   /* writing structure pointers to sorting array */
1213   while (i < num_nodes && node)         /* double boundary check... */
1214   {
1215     sort_array[i] = node;
1216
1217     i++;
1218     node = node->next;
1219   }
1220
1221   /* sorting the structure pointers in the sorting array */
1222   qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1223         compare_function);
1224
1225   /* update the linkage of list elements with the sorted node array */
1226   for (i = 0; i < num_nodes - 1; i++)
1227     sort_array[i]->next = sort_array[i + 1];
1228   sort_array[num_nodes - 1]->next = NULL;
1229
1230   /* update the linkage of the main list anchor pointer */
1231   *node_first = sort_array[0];
1232
1233   free(sort_array);
1234
1235   /* now recursively sort the level group structures */
1236   node = *node_first;
1237   while (node)
1238   {
1239     if (node->node_group != NULL)
1240       sortTreeInfoBySortFunction(&node->node_group, compare_function);
1241
1242     node = node->next;
1243   }
1244 }
1245
1246 void sortTreeInfo(TreeInfo **node_first)
1247 {
1248   sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1249 }
1250
1251
1252 /* ========================================================================= */
1253 /* some stuff from "files.c"                                                 */
1254 /* ========================================================================= */
1255
1256 #if defined(PLATFORM_WIN32)
1257 #ifndef S_IRGRP
1258 #define S_IRGRP S_IRUSR
1259 #endif
1260 #ifndef S_IROTH
1261 #define S_IROTH S_IRUSR
1262 #endif
1263 #ifndef S_IWGRP
1264 #define S_IWGRP S_IWUSR
1265 #endif
1266 #ifndef S_IWOTH
1267 #define S_IWOTH S_IWUSR
1268 #endif
1269 #ifndef S_IXGRP
1270 #define S_IXGRP S_IXUSR
1271 #endif
1272 #ifndef S_IXOTH
1273 #define S_IXOTH S_IXUSR
1274 #endif
1275 #ifndef S_IRWXG
1276 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1277 #endif
1278 #ifndef S_ISGID
1279 #define S_ISGID 0
1280 #endif
1281 #endif  /* PLATFORM_WIN32 */
1282
1283 /* file permissions for newly written files */
1284 #define MODE_R_ALL              (S_IRUSR | S_IRGRP | S_IROTH)
1285 #define MODE_W_ALL              (S_IWUSR | S_IWGRP | S_IWOTH)
1286 #define MODE_X_ALL              (S_IXUSR | S_IXGRP | S_IXOTH)
1287
1288 #define MODE_W_PRIVATE          (S_IWUSR)
1289 #define MODE_W_PUBLIC           (S_IWUSR | S_IWGRP)
1290 #define MODE_W_PUBLIC_DIR       (S_IWUSR | S_IWGRP | S_ISGID)
1291
1292 #define DIR_PERMS_PRIVATE       (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1293 #define DIR_PERMS_PUBLIC        (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1294
1295 #define FILE_PERMS_PRIVATE      (MODE_R_ALL | MODE_W_PRIVATE)
1296 #define FILE_PERMS_PUBLIC       (MODE_R_ALL | MODE_W_PUBLIC)
1297
1298 char *getHomeDir()
1299 {
1300   static char *dir = NULL;
1301
1302 #if defined(PLATFORM_WIN32)
1303   if (dir == NULL)
1304   {
1305     dir = checked_malloc(MAX_PATH + 1);
1306
1307     if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1308       strcpy(dir, ".");
1309   }
1310 #elif defined(PLATFORM_UNIX)
1311   if (dir == NULL)
1312   {
1313     if ((dir = getenv("HOME")) == NULL)
1314     {
1315       struct passwd *pwd;
1316
1317       if ((pwd = getpwuid(getuid())) != NULL)
1318         dir = getStringCopy(pwd->pw_dir);
1319       else
1320         dir = ".";
1321     }
1322   }
1323 #else
1324   dir = ".";
1325 #endif
1326
1327   return dir;
1328 }
1329
1330 char *getCommonDataDir(void)
1331 {
1332   static char *common_data_dir = NULL;
1333
1334 #if defined(PLATFORM_WIN32)
1335   if (common_data_dir == NULL)
1336   {
1337     char *dir = checked_malloc(MAX_PATH + 1);
1338
1339     if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
1340         && !strEqual(dir, ""))          /* empty for Windows 95/98 */
1341       common_data_dir = getPath2(dir, program.userdata_subdir);
1342     else
1343       common_data_dir = options.rw_base_directory;
1344   }
1345 #else
1346   if (common_data_dir == NULL)
1347     common_data_dir = options.rw_base_directory;
1348 #endif
1349
1350   return common_data_dir;
1351 }
1352
1353 char *getPersonalDataDir(void)
1354 {
1355   static char *personal_data_dir = NULL;
1356
1357 #if defined(PLATFORM_MACOSX)
1358   if (personal_data_dir == NULL)
1359     personal_data_dir = getPath2(getHomeDir(), "Documents");
1360 #else
1361   if (personal_data_dir == NULL)
1362     personal_data_dir = getHomeDir();
1363 #endif
1364
1365   return personal_data_dir;
1366 }
1367
1368 char *getUserGameDataDir(void)
1369 {
1370   static char *user_game_data_dir = NULL;
1371
1372   if (user_game_data_dir == NULL)
1373     user_game_data_dir = getPath2(getPersonalDataDir(),
1374                                   program.userdata_subdir);
1375
1376   return user_game_data_dir;
1377 }
1378
1379 void updateUserGameDataDir()
1380 {
1381 #if defined(PLATFORM_MACOSX)
1382   char *userdata_dir_old = getPath2(getHomeDir(), program.userdata_subdir_unix);
1383   char *userdata_dir_new = getUserGameDataDir();        /* do not free() this */
1384
1385   /* convert old Unix style game data directory to Mac OS X style, if needed */
1386   if (fileExists(userdata_dir_old) && !fileExists(userdata_dir_new))
1387   {
1388     if (rename(userdata_dir_old, userdata_dir_new) != 0)
1389     {
1390       Error(ERR_WARN, "cannot move game data directory '%s' to '%s'",
1391             userdata_dir_old, userdata_dir_new);
1392
1393       /* continue using Unix style data directory -- this should not happen */
1394       program.userdata_path = getPath2(getPersonalDataDir(),
1395                                        program.userdata_subdir_unix);
1396     }
1397   }
1398
1399   free(userdata_dir_old);
1400 #endif
1401 }
1402
1403 char *getSetupDir()
1404 {
1405   return getUserGameDataDir();
1406 }
1407
1408 static mode_t posix_umask(mode_t mask)
1409 {
1410 #if defined(PLATFORM_UNIX)
1411   return umask(mask);
1412 #else
1413   return 0;
1414 #endif
1415 }
1416
1417 static int posix_mkdir(const char *pathname, mode_t mode)
1418 {
1419 #if defined(PLATFORM_WIN32)
1420   return mkdir(pathname);
1421 #else
1422   return mkdir(pathname, mode);
1423 #endif
1424 }
1425
1426 static boolean posix_process_running_setgid()
1427 {
1428 #if defined(PLATFORM_UNIX)
1429   return (getgid() != getegid());
1430 #else
1431   return FALSE;
1432 #endif
1433 }
1434
1435 void createDirectory(char *dir, char *text, int permission_class)
1436 {
1437   /* leave "other" permissions in umask untouched, but ensure group parts
1438      of USERDATA_DIR_MODE are not masked */
1439   mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1440                      DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1441   mode_t last_umask = posix_umask(0);
1442   mode_t group_umask = ~(dir_mode & S_IRWXG);
1443   int running_setgid = posix_process_running_setgid();
1444
1445   /* if we're setgid, protect files against "other" */
1446   /* else keep umask(0) to make the dir world-writable */
1447
1448   if (running_setgid)
1449     posix_umask(last_umask & group_umask);
1450   else
1451     dir_mode |= MODE_W_ALL;
1452
1453   if (!fileExists(dir))
1454     if (posix_mkdir(dir, dir_mode) != 0)
1455       Error(ERR_WARN, "cannot create %s directory '%s'", text, dir);
1456
1457   if (permission_class == PERMS_PUBLIC && !running_setgid)
1458     chmod(dir, dir_mode);
1459
1460   posix_umask(last_umask);              /* restore previous umask */
1461 }
1462
1463 void InitUserDataDirectory()
1464 {
1465   createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1466 }
1467
1468 void SetFilePermissions(char *filename, int permission_class)
1469 {
1470   int running_setgid = posix_process_running_setgid();
1471   int perms = (permission_class == PERMS_PRIVATE ?
1472                FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
1473
1474   if (permission_class == PERMS_PUBLIC && !running_setgid)
1475     perms |= MODE_W_ALL;
1476
1477   chmod(filename, perms);
1478 }
1479
1480 char *getCookie(char *file_type)
1481 {
1482   static char cookie[MAX_COOKIE_LEN + 1];
1483
1484   if (strlen(program.cookie_prefix) + 1 +
1485       strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1486     return "[COOKIE ERROR]";    /* should never happen */
1487
1488   sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1489           program.cookie_prefix, file_type,
1490           program.version_major, program.version_minor);
1491
1492   return cookie;
1493 }
1494
1495 int getFileVersionFromCookieString(const char *cookie)
1496 {
1497   const char *ptr_cookie1, *ptr_cookie2;
1498   const char *pattern1 = "_FILE_VERSION_";
1499   const char *pattern2 = "?.?";
1500   const int len_cookie = strlen(cookie);
1501   const int len_pattern1 = strlen(pattern1);
1502   const int len_pattern2 = strlen(pattern2);
1503   const int len_pattern = len_pattern1 + len_pattern2;
1504   int version_major, version_minor;
1505
1506   if (len_cookie <= len_pattern)
1507     return -1;
1508
1509   ptr_cookie1 = &cookie[len_cookie - len_pattern];
1510   ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1511
1512   if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1513     return -1;
1514
1515   if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1516       ptr_cookie2[1] != '.' ||
1517       ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1518     return -1;
1519
1520   version_major = ptr_cookie2[0] - '0';
1521   version_minor = ptr_cookie2[2] - '0';
1522
1523   return VERSION_IDENT(version_major, version_minor, 0, 0);
1524 }
1525
1526 boolean checkCookieString(const char *cookie, const char *template)
1527 {
1528   const char *pattern = "_FILE_VERSION_?.?";
1529   const int len_cookie = strlen(cookie);
1530   const int len_template = strlen(template);
1531   const int len_pattern = strlen(pattern);
1532
1533   if (len_cookie != len_template)
1534     return FALSE;
1535
1536   if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1537     return FALSE;
1538
1539   return TRUE;
1540 }
1541
1542 /* ------------------------------------------------------------------------- */
1543 /* setup file list and hash handling functions                               */
1544 /* ------------------------------------------------------------------------- */
1545
1546 char *getFormattedSetupEntry(char *token, char *value)
1547 {
1548   int i;
1549   static char entry[MAX_LINE_LEN];
1550
1551   /* if value is an empty string, just return token without value */
1552   if (*value == '\0')
1553     return token;
1554
1555   /* start with the token and some spaces to format output line */
1556   sprintf(entry, "%s:", token);
1557   for (i = strlen(entry); i < token_value_position; i++)
1558     strcat(entry, " ");
1559
1560   /* continue with the token's value */
1561   strcat(entry, value);
1562
1563   return entry;
1564 }
1565
1566 SetupFileList *newSetupFileList(char *token, char *value)
1567 {
1568   SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1569
1570   new->token = getStringCopy(token);
1571   new->value = getStringCopy(value);
1572
1573   new->next = NULL;
1574
1575   return new;
1576 }
1577
1578 void freeSetupFileList(SetupFileList *list)
1579 {
1580   if (list == NULL)
1581     return;
1582
1583   checked_free(list->token);
1584   checked_free(list->value);
1585
1586   if (list->next)
1587     freeSetupFileList(list->next);
1588
1589   free(list);
1590 }
1591
1592 char *getListEntry(SetupFileList *list, char *token)
1593 {
1594   if (list == NULL)
1595     return NULL;
1596
1597   if (strEqual(list->token, token))
1598     return list->value;
1599   else
1600     return getListEntry(list->next, token);
1601 }
1602
1603 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1604 {
1605   if (list == NULL)
1606     return NULL;
1607
1608   if (strEqual(list->token, token))
1609   {
1610     checked_free(list->value);
1611
1612     list->value = getStringCopy(value);
1613
1614     return list;
1615   }
1616   else if (list->next == NULL)
1617     return (list->next = newSetupFileList(token, value));
1618   else
1619     return setListEntry(list->next, token, value);
1620 }
1621
1622 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1623 {
1624   if (list == NULL)
1625     return NULL;
1626
1627   if (list->next == NULL)
1628     return (list->next = newSetupFileList(token, value));
1629   else
1630     return addListEntry(list->next, token, value);
1631 }
1632
1633 #ifdef DEBUG
1634 static void printSetupFileList(SetupFileList *list)
1635 {
1636   if (!list)
1637     return;
1638
1639   printf("token: '%s'\n", list->token);
1640   printf("value: '%s'\n", list->value);
1641
1642   printSetupFileList(list->next);
1643 }
1644 #endif
1645
1646 #ifdef DEBUG
1647 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1648 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1649 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1650 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1651 #else
1652 #define insert_hash_entry hashtable_insert
1653 #define search_hash_entry hashtable_search
1654 #define change_hash_entry hashtable_change
1655 #define remove_hash_entry hashtable_remove
1656 #endif
1657
1658 unsigned int get_hash_from_key(void *key)
1659 {
1660   /*
1661     djb2
1662
1663     This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1664     'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1665     uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1666     it works better than many other constants, prime or not) has never been
1667     adequately explained.
1668
1669     If you just want to have a good hash function, and cannot wait, djb2
1670     is one of the best string hash functions i know. It has excellent
1671     distribution and speed on many different sets of keys and table sizes.
1672     You are not likely to do better with one of the "well known" functions
1673     such as PJW, K&R, etc.
1674
1675     Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1676   */
1677
1678   char *str = (char *)key;
1679   unsigned int hash = 5381;
1680   int c;
1681
1682   while ((c = *str++))
1683     hash = ((hash << 5) + hash) + c;    /* hash * 33 + c */
1684
1685   return hash;
1686 }
1687
1688 static int keys_are_equal(void *key1, void *key2)
1689 {
1690   return (strEqual((char *)key1, (char *)key2));
1691 }
1692
1693 SetupFileHash *newSetupFileHash()
1694 {
1695   SetupFileHash *new_hash =
1696     create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1697
1698   if (new_hash == NULL)
1699     Error(ERR_EXIT, "create_hashtable() failed -- out of memory");
1700
1701   return new_hash;
1702 }
1703
1704 void freeSetupFileHash(SetupFileHash *hash)
1705 {
1706   if (hash == NULL)
1707     return;
1708
1709   hashtable_destroy(hash, 1);   /* 1 == also free values stored in hash */
1710 }
1711
1712 char *getHashEntry(SetupFileHash *hash, char *token)
1713 {
1714   if (hash == NULL)
1715     return NULL;
1716
1717   return search_hash_entry(hash, token);
1718 }
1719
1720 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1721 {
1722   char *value_copy;
1723
1724   if (hash == NULL)
1725     return;
1726
1727   value_copy = getStringCopy(value);
1728
1729   /* change value; if it does not exist, insert it as new */
1730   if (!change_hash_entry(hash, token, value_copy))
1731     if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1732       Error(ERR_EXIT, "cannot insert into hash -- aborting");
1733 }
1734
1735 char *removeHashEntry(SetupFileHash *hash, char *token)
1736 {
1737   if (hash == NULL)
1738     return NULL;
1739
1740   return remove_hash_entry(hash, token);
1741 }
1742
1743 #if 0
1744 static void printSetupFileHash(SetupFileHash *hash)
1745 {
1746   BEGIN_HASH_ITERATION(hash, itr)
1747   {
1748     printf("token: '%s'\n", HASH_ITERATION_TOKEN(itr));
1749     printf("value: '%s'\n", HASH_ITERATION_VALUE(itr));
1750   }
1751   END_HASH_ITERATION(hash, itr)
1752 }
1753 #endif
1754
1755 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE            1
1756 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING            0
1757 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH             0
1758
1759 static boolean token_value_separator_found = FALSE;
1760 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1761 static boolean token_value_separator_warning = FALSE;
1762 #endif
1763 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1764 static boolean token_already_exists_warning = FALSE;
1765 #endif
1766
1767 static boolean getTokenValueFromSetupLineExt(char *line,
1768                                              char **token_ptr, char **value_ptr,
1769                                              char *filename, char *line_raw,
1770                                              int line_nr,
1771                                              boolean separator_required)
1772 {
1773   static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
1774   char *token, *value, *line_ptr;
1775
1776   /* when externally invoked via ReadTokenValueFromLine(), copy line buffers */
1777   if (line_raw == NULL)
1778   {
1779     strncpy(line_copy, line, MAX_LINE_LEN);
1780     line_copy[MAX_LINE_LEN] = '\0';
1781     line = line_copy;
1782
1783     strcpy(line_raw_copy, line_copy);
1784     line_raw = line_raw_copy;
1785   }
1786
1787   /* cut trailing comment from input line */
1788   for (line_ptr = line; *line_ptr; line_ptr++)
1789   {
1790     if (*line_ptr == '#')
1791     {
1792       *line_ptr = '\0';
1793       break;
1794     }
1795   }
1796
1797   /* cut trailing whitespaces from input line */
1798   for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1799     if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1800       *line_ptr = '\0';
1801
1802   /* ignore empty lines */
1803   if (*line == '\0')
1804     return FALSE;
1805
1806   /* cut leading whitespaces from token */
1807   for (token = line; *token; token++)
1808     if (*token != ' ' && *token != '\t')
1809       break;
1810
1811   /* start with empty value as reliable default */
1812   value = "";
1813
1814   token_value_separator_found = FALSE;
1815
1816   /* find end of token to determine start of value */
1817   for (line_ptr = token; *line_ptr; line_ptr++)
1818   {
1819 #if 1
1820     /* first look for an explicit token/value separator, like ':' or '=' */
1821     if (*line_ptr == ':' || *line_ptr == '=')
1822 #else
1823     if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
1824 #endif
1825     {
1826       *line_ptr = '\0';                 /* terminate token string */
1827       value = line_ptr + 1;             /* set beginning of value */
1828
1829       token_value_separator_found = TRUE;
1830
1831       break;
1832     }
1833   }
1834
1835 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
1836   /* fallback: if no token/value separator found, also allow whitespaces */
1837   if (!token_value_separator_found && !separator_required)
1838   {
1839     for (line_ptr = token; *line_ptr; line_ptr++)
1840     {
1841       if (*line_ptr == ' ' || *line_ptr == '\t')
1842       {
1843         *line_ptr = '\0';               /* terminate token string */
1844         value = line_ptr + 1;           /* set beginning of value */
1845
1846         token_value_separator_found = TRUE;
1847
1848         break;
1849       }
1850     }
1851
1852 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1853     if (token_value_separator_found)
1854     {
1855       if (!token_value_separator_warning)
1856       {
1857         Error(ERR_INFO_LINE, "-");
1858
1859         if (filename != NULL)
1860         {
1861           Error(ERR_WARN, "missing token/value separator(s) in config file:");
1862           Error(ERR_INFO, "- config file: '%s'", filename);
1863         }
1864         else
1865         {
1866           Error(ERR_WARN, "missing token/value separator(s):");
1867         }
1868
1869         token_value_separator_warning = TRUE;
1870       }
1871
1872       if (filename != NULL)
1873         Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
1874       else
1875         Error(ERR_INFO, "- line: '%s'", line_raw);
1876     }
1877 #endif
1878   }
1879 #endif
1880
1881   /* cut trailing whitespaces from token */
1882   for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
1883     if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1884       *line_ptr = '\0';
1885
1886   /* cut leading whitespaces from value */
1887   for (; *value; value++)
1888     if (*value != ' ' && *value != '\t')
1889       break;
1890
1891 #if 0
1892   if (*value == '\0')
1893     value = "true";     /* treat tokens without value as "true" */
1894 #endif
1895
1896   *token_ptr = token;
1897   *value_ptr = value;
1898
1899   return TRUE;
1900 }
1901
1902 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
1903 {
1904   /* while the internal (old) interface does not require a token/value
1905      separator (for downwards compatibility with existing files which
1906      don't use them), it is mandatory for the external (new) interface */
1907
1908   return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
1909 }
1910
1911 #if 1
1912 static boolean loadSetupFileData(void *setup_file_data, char *filename,
1913                                  boolean top_recursion_level, boolean is_hash)
1914 {
1915   static SetupFileHash *include_filename_hash = NULL;
1916   char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
1917   char *token, *value, *line_ptr;
1918   void *insert_ptr = NULL;
1919   boolean read_continued_line = FALSE;
1920   FILE *file;
1921   int line_nr = 0, token_count = 0, include_count = 0;
1922
1923 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1924   token_value_separator_warning = FALSE;
1925 #endif
1926
1927 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1928   token_already_exists_warning = FALSE;
1929 #endif
1930
1931   if (!(file = fopen(filename, MODE_READ)))
1932   {
1933     Error(ERR_WARN, "cannot open configuration file '%s'", filename);
1934
1935     return FALSE;
1936   }
1937
1938   /* use "insert pointer" to store list end for constant insertion complexity */
1939   if (!is_hash)
1940     insert_ptr = setup_file_data;
1941
1942   /* on top invocation, create hash to mark included files (to prevent loops) */
1943   if (top_recursion_level)
1944     include_filename_hash = newSetupFileHash();
1945
1946   /* mark this file as already included (to prevent including it again) */
1947   setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
1948
1949   while (!feof(file))
1950   {
1951     /* read next line of input file */
1952     if (!fgets(line, MAX_LINE_LEN, file))
1953       break;
1954
1955     /* check if line was completely read and is terminated by line break */
1956     if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
1957       line_nr++;
1958
1959     /* cut trailing line break (this can be newline and/or carriage return) */
1960     for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1961       if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
1962         *line_ptr = '\0';
1963
1964     /* copy raw input line for later use (mainly debugging output) */
1965     strcpy(line_raw, line);
1966
1967     if (read_continued_line)
1968     {
1969 #if 0
1970       /* !!! ??? WHY ??? !!! */
1971       /* cut leading whitespaces from input line */
1972       for (line_ptr = line; *line_ptr; line_ptr++)
1973         if (*line_ptr != ' ' && *line_ptr != '\t')
1974           break;
1975 #endif
1976
1977       /* append new line to existing line, if there is enough space */
1978       if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
1979         strcat(previous_line, line_ptr);
1980
1981       strcpy(line, previous_line);      /* copy storage buffer to line */
1982
1983       read_continued_line = FALSE;
1984     }
1985
1986     /* if the last character is '\', continue at next line */
1987     if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
1988     {
1989       line[strlen(line) - 1] = '\0';    /* cut off trailing backslash */
1990       strcpy(previous_line, line);      /* copy line to storage buffer */
1991
1992       read_continued_line = TRUE;
1993
1994       continue;
1995     }
1996
1997     if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
1998                                        line_raw, line_nr, FALSE))
1999       continue;
2000
2001     if (*token)
2002     {
2003       if (strEqual(token, "include"))
2004       {
2005         if (getHashEntry(include_filename_hash, value) == NULL)
2006         {
2007           char *basepath = getBasePath(filename);
2008           char *basename = getBaseName(value);
2009           char *filename_include = getPath2(basepath, basename);
2010
2011 #if 0
2012           Error(ERR_INFO, "[including file '%s']", filename_include);
2013 #endif
2014
2015           loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2016
2017           free(basepath);
2018           free(basename);
2019           free(filename_include);
2020
2021           include_count++;
2022         }
2023         else
2024         {
2025           Error(ERR_WARN, "ignoring already processed file '%s'", value);
2026         }
2027       }
2028       else
2029       {
2030         if (is_hash)
2031         {
2032 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2033           char *old_value =
2034             getHashEntry((SetupFileHash *)setup_file_data, token);
2035
2036           if (old_value != NULL)
2037           {
2038             if (!token_already_exists_warning)
2039             {
2040               Error(ERR_INFO_LINE, "-");
2041               Error(ERR_WARN, "duplicate token(s) found in config file:");
2042               Error(ERR_INFO, "- config file: '%s'", filename);
2043
2044               token_already_exists_warning = TRUE;
2045             }
2046
2047             Error(ERR_INFO, "- token: '%s' (in line %d)", token, line_nr);
2048             Error(ERR_INFO, "  old value: '%s'", old_value);
2049             Error(ERR_INFO, "  new value: '%s'", value);
2050           }
2051 #endif
2052
2053           setHashEntry((SetupFileHash *)setup_file_data, token, value);
2054         }
2055         else
2056         {
2057           insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2058         }
2059
2060         token_count++;
2061       }
2062     }
2063   }
2064
2065   fclose(file);
2066
2067 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2068   if (token_value_separator_warning)
2069     Error(ERR_INFO_LINE, "-");
2070 #endif
2071
2072 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2073   if (token_already_exists_warning)
2074     Error(ERR_INFO_LINE, "-");
2075 #endif
2076
2077   if (token_count == 0 && include_count == 0)
2078     Error(ERR_WARN, "configuration file '%s' is empty", filename);
2079
2080   if (top_recursion_level)
2081     freeSetupFileHash(include_filename_hash);
2082
2083   return TRUE;
2084 }
2085
2086 #else
2087
2088 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2089                                  boolean top_recursion_level, boolean is_hash)
2090 {
2091   static SetupFileHash *include_filename_hash = NULL;
2092   char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2093   char *token, *value, *line_ptr;
2094   void *insert_ptr = NULL;
2095   boolean read_continued_line = FALSE;
2096   FILE *file;
2097   int line_nr = 0;
2098   int token_count = 0;
2099
2100 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2101   token_value_separator_warning = FALSE;
2102 #endif
2103
2104   if (!(file = fopen(filename, MODE_READ)))
2105   {
2106     Error(ERR_WARN, "cannot open configuration file '%s'", filename);
2107
2108     return FALSE;
2109   }
2110
2111   /* use "insert pointer" to store list end for constant insertion complexity */
2112   if (!is_hash)
2113     insert_ptr = setup_file_data;
2114
2115   /* on top invocation, create hash to mark included files (to prevent loops) */
2116   if (top_recursion_level)
2117     include_filename_hash = newSetupFileHash();
2118
2119   /* mark this file as already included (to prevent including it again) */
2120   setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2121
2122   while (!feof(file))
2123   {
2124     /* read next line of input file */
2125     if (!fgets(line, MAX_LINE_LEN, file))
2126       break;
2127
2128     /* check if line was completely read and is terminated by line break */
2129     if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2130       line_nr++;
2131
2132     /* cut trailing line break (this can be newline and/or carriage return) */
2133     for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2134       if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2135         *line_ptr = '\0';
2136
2137     /* copy raw input line for later use (mainly debugging output) */
2138     strcpy(line_raw, line);
2139
2140     if (read_continued_line)
2141     {
2142       /* cut leading whitespaces from input line */
2143       for (line_ptr = line; *line_ptr; line_ptr++)
2144         if (*line_ptr != ' ' && *line_ptr != '\t')
2145           break;
2146
2147       /* append new line to existing line, if there is enough space */
2148       if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2149         strcat(previous_line, line_ptr);
2150
2151       strcpy(line, previous_line);      /* copy storage buffer to line */
2152
2153       read_continued_line = FALSE;
2154     }
2155
2156     /* if the last character is '\', continue at next line */
2157     if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2158     {
2159       line[strlen(line) - 1] = '\0';    /* cut off trailing backslash */
2160       strcpy(previous_line, line);      /* copy line to storage buffer */
2161
2162       read_continued_line = TRUE;
2163
2164       continue;
2165     }
2166
2167     /* cut trailing comment from input line */
2168     for (line_ptr = line; *line_ptr; line_ptr++)
2169     {
2170       if (*line_ptr == '#')
2171       {
2172         *line_ptr = '\0';
2173         break;
2174       }
2175     }
2176
2177     /* cut trailing whitespaces from input line */
2178     for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2179       if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2180         *line_ptr = '\0';
2181
2182     /* ignore empty lines */
2183     if (*line == '\0')
2184       continue;
2185
2186     /* cut leading whitespaces from token */
2187     for (token = line; *token; token++)
2188       if (*token != ' ' && *token != '\t')
2189         break;
2190
2191     /* start with empty value as reliable default */
2192     value = "";
2193
2194     token_value_separator_found = FALSE;
2195
2196     /* find end of token to determine start of value */
2197     for (line_ptr = token; *line_ptr; line_ptr++)
2198     {
2199 #if 1
2200       /* first look for an explicit token/value separator, like ':' or '=' */
2201       if (*line_ptr == ':' || *line_ptr == '=')
2202 #else
2203       if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
2204 #endif
2205       {
2206         *line_ptr = '\0';               /* terminate token string */
2207         value = line_ptr + 1;           /* set beginning of value */
2208
2209         token_value_separator_found = TRUE;
2210
2211         break;
2212       }
2213     }
2214
2215 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
2216     /* fallback: if no token/value separator found, also allow whitespaces */
2217     if (!token_value_separator_found)
2218     {
2219       for (line_ptr = token; *line_ptr; line_ptr++)
2220       {
2221         if (*line_ptr == ' ' || *line_ptr == '\t')
2222         {
2223           *line_ptr = '\0';             /* terminate token string */
2224           value = line_ptr + 1;         /* set beginning of value */
2225
2226           token_value_separator_found = TRUE;
2227
2228           break;
2229         }
2230       }
2231
2232 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2233       if (token_value_separator_found)
2234       {
2235         if (!token_value_separator_warning)
2236         {
2237           Error(ERR_INFO_LINE, "-");
2238           Error(ERR_WARN, "missing token/value separator(s) in config file:");
2239           Error(ERR_INFO, "- config file: '%s'", filename);
2240
2241           token_value_separator_warning = TRUE;
2242         }
2243
2244         Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
2245       }
2246 #endif
2247     }
2248 #endif
2249
2250     /* cut trailing whitespaces from token */
2251     for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2252       if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2253         *line_ptr = '\0';
2254
2255     /* cut leading whitespaces from value */
2256     for (; *value; value++)
2257       if (*value != ' ' && *value != '\t')
2258         break;
2259
2260 #if 0
2261     if (*value == '\0')
2262       value = "true";   /* treat tokens without value as "true" */
2263 #endif
2264
2265     if (*token)
2266     {
2267       if (strEqual(token, "include"))
2268       {
2269         if (getHashEntry(include_filename_hash, value) == NULL)
2270         {
2271           char *basepath = getBasePath(filename);
2272           char *basename = getBaseName(value);
2273           char *filename_include = getPath2(basepath, basename);
2274
2275 #if 0
2276           Error(ERR_INFO, "[including file '%s']", filename_include);
2277 #endif
2278
2279           loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2280
2281           free(basepath);
2282           free(basename);
2283           free(filename_include);
2284         }
2285         else
2286         {
2287           Error(ERR_WARN, "ignoring already processed file '%s'", value);
2288         }
2289       }
2290       else
2291       {
2292         if (is_hash)
2293           setHashEntry((SetupFileHash *)setup_file_data, token, value);
2294         else
2295           insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2296
2297         token_count++;
2298       }
2299     }
2300   }
2301
2302   fclose(file);
2303
2304 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2305   if (token_value_separator_warning)
2306     Error(ERR_INFO_LINE, "-");
2307 #endif
2308
2309   if (token_count == 0)
2310     Error(ERR_WARN, "configuration file '%s' is empty", filename);
2311
2312   if (top_recursion_level)
2313     freeSetupFileHash(include_filename_hash);
2314
2315   return TRUE;
2316 }
2317 #endif
2318
2319 void saveSetupFileHash(SetupFileHash *hash, char *filename)
2320 {
2321   FILE *file;
2322
2323   if (!(file = fopen(filename, MODE_WRITE)))
2324   {
2325     Error(ERR_WARN, "cannot write configuration file '%s'", filename);
2326
2327     return;
2328   }
2329
2330   BEGIN_HASH_ITERATION(hash, itr)
2331   {
2332     fprintf(file, "%s\n", getFormattedSetupEntry(HASH_ITERATION_TOKEN(itr),
2333                                                  HASH_ITERATION_VALUE(itr)));
2334   }
2335   END_HASH_ITERATION(hash, itr)
2336
2337   fclose(file);
2338 }
2339
2340 SetupFileList *loadSetupFileList(char *filename)
2341 {
2342   SetupFileList *setup_file_list = newSetupFileList("", "");
2343   SetupFileList *first_valid_list_entry;
2344
2345   if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2346   {
2347     freeSetupFileList(setup_file_list);
2348
2349     return NULL;
2350   }
2351
2352   first_valid_list_entry = setup_file_list->next;
2353
2354   /* free empty list header */
2355   setup_file_list->next = NULL;
2356   freeSetupFileList(setup_file_list);
2357
2358   return first_valid_list_entry;
2359 }
2360
2361 SetupFileHash *loadSetupFileHash(char *filename)
2362 {
2363   SetupFileHash *setup_file_hash = newSetupFileHash();
2364
2365   if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2366   {
2367     freeSetupFileHash(setup_file_hash);
2368
2369     return NULL;
2370   }
2371
2372   return setup_file_hash;
2373 }
2374
2375 void checkSetupFileHashIdentifier(SetupFileHash *setup_file_hash,
2376                                   char *filename, char *identifier)
2377 {
2378   char *value = getHashEntry(setup_file_hash, TOKEN_STR_FILE_IDENTIFIER);
2379
2380   if (value == NULL)
2381     Error(ERR_WARN, "config file '%s' has no file identifier", filename);
2382   else if (!checkCookieString(value, identifier))
2383     Error(ERR_WARN, "config file '%s' has wrong file identifier", filename);
2384 }
2385
2386
2387 /* ========================================================================= */
2388 /* setup file stuff                                                          */
2389 /* ========================================================================= */
2390
2391 #define TOKEN_STR_LAST_LEVEL_SERIES             "last_level_series"
2392 #define TOKEN_STR_LAST_PLAYED_LEVEL             "last_played_level"
2393 #define TOKEN_STR_HANDICAP_LEVEL                "handicap_level"
2394
2395 /* level directory info */
2396 #define LEVELINFO_TOKEN_IDENTIFIER              0
2397 #define LEVELINFO_TOKEN_NAME                    1
2398 #define LEVELINFO_TOKEN_NAME_SORTING            2
2399 #define LEVELINFO_TOKEN_AUTHOR                  3
2400 #define LEVELINFO_TOKEN_YEAR                    4
2401 #define LEVELINFO_TOKEN_IMPORTED_FROM           5
2402 #define LEVELINFO_TOKEN_IMPORTED_BY             6
2403 #define LEVELINFO_TOKEN_TESTED_BY               7
2404 #define LEVELINFO_TOKEN_LEVELS                  8
2405 #define LEVELINFO_TOKEN_FIRST_LEVEL             9
2406 #define LEVELINFO_TOKEN_SORT_PRIORITY           10
2407 #define LEVELINFO_TOKEN_LATEST_ENGINE           11
2408 #define LEVELINFO_TOKEN_LEVEL_GROUP             12
2409 #define LEVELINFO_TOKEN_READONLY                13
2410 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS        14
2411 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA        15
2412 #define LEVELINFO_TOKEN_GRAPHICS_SET            16
2413 #define LEVELINFO_TOKEN_SOUNDS_SET              17
2414 #define LEVELINFO_TOKEN_MUSIC_SET               18
2415 #define LEVELINFO_TOKEN_FILENAME                19
2416 #define LEVELINFO_TOKEN_FILETYPE                20
2417 #define LEVELINFO_TOKEN_SPECIAL_FLAGS           21
2418 #define LEVELINFO_TOKEN_HANDICAP                22
2419 #define LEVELINFO_TOKEN_SKIP_LEVELS             23
2420
2421 #define NUM_LEVELINFO_TOKENS                    24
2422
2423 static LevelDirTree ldi;
2424
2425 static struct TokenInfo levelinfo_tokens[] =
2426 {
2427   /* level directory info */
2428   { TYPE_STRING,        &ldi.identifier,        "identifier"            },
2429   { TYPE_STRING,        &ldi.name,              "name"                  },
2430   { TYPE_STRING,        &ldi.name_sorting,      "name_sorting"          },
2431   { TYPE_STRING,        &ldi.author,            "author"                },
2432   { TYPE_STRING,        &ldi.year,              "year"                  },
2433   { TYPE_STRING,        &ldi.imported_from,     "imported_from"         },
2434   { TYPE_STRING,        &ldi.imported_by,       "imported_by"           },
2435   { TYPE_STRING,        &ldi.tested_by,         "tested_by"             },
2436   { TYPE_INTEGER,       &ldi.levels,            "levels"                },
2437   { TYPE_INTEGER,       &ldi.first_level,       "first_level"           },
2438   { TYPE_INTEGER,       &ldi.sort_priority,     "sort_priority"         },
2439   { TYPE_BOOLEAN,       &ldi.latest_engine,     "latest_engine"         },
2440   { TYPE_BOOLEAN,       &ldi.level_group,       "level_group"           },
2441   { TYPE_BOOLEAN,       &ldi.readonly,          "readonly"              },
2442   { TYPE_STRING,        &ldi.graphics_set_ecs,  "graphics_set.ecs"      },
2443   { TYPE_STRING,        &ldi.graphics_set_aga,  "graphics_set.aga"      },
2444   { TYPE_STRING,        &ldi.graphics_set,      "graphics_set"          },
2445   { TYPE_STRING,        &ldi.sounds_set,        "sounds_set"            },
2446   { TYPE_STRING,        &ldi.music_set,         "music_set"             },
2447   { TYPE_STRING,        &ldi.level_filename,    "filename"              },
2448   { TYPE_STRING,        &ldi.level_filetype,    "filetype"              },
2449   { TYPE_STRING,        &ldi.special_flags,     "special_flags"         },
2450   { TYPE_BOOLEAN,       &ldi.handicap,          "handicap"              },
2451   { TYPE_BOOLEAN,       &ldi.skip_levels,       "skip_levels"           }
2452 };
2453
2454 static struct TokenInfo artworkinfo_tokens[] =
2455 {
2456   /* artwork directory info */
2457   { TYPE_STRING,        &ldi.identifier,        "identifier"            },
2458   { TYPE_STRING,        &ldi.subdir,            "subdir"                },
2459   { TYPE_STRING,        &ldi.name,              "name"                  },
2460   { TYPE_STRING,        &ldi.name_sorting,      "name_sorting"          },
2461   { TYPE_STRING,        &ldi.author,            "author"                },
2462   { TYPE_INTEGER,       &ldi.sort_priority,     "sort_priority"         },
2463   { TYPE_STRING,        &ldi.basepath,          "basepath"              },
2464   { TYPE_STRING,        &ldi.fullpath,          "fullpath"              },
2465   { TYPE_BOOLEAN,       &ldi.in_user_dir,       "in_user_dir"           },
2466   { TYPE_INTEGER,       &ldi.color,             "color"                 },
2467   { TYPE_STRING,        &ldi.class_desc,        "class_desc"            },
2468
2469   { -1,                 NULL,                   NULL                    },
2470 };
2471
2472 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2473 {
2474   ti->type = type;
2475
2476   ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR    ? &leveldir_first :
2477                   ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2478                   ti->type == TREE_TYPE_SOUNDS_DIR   ? &artwork.snd_first :
2479                   ti->type == TREE_TYPE_MUSIC_DIR    ? &artwork.mus_first :
2480                   NULL);
2481
2482   ti->node_parent = NULL;
2483   ti->node_group = NULL;
2484   ti->next = NULL;
2485
2486   ti->cl_first = -1;
2487   ti->cl_cursor = -1;
2488
2489   ti->subdir = NULL;
2490   ti->fullpath = NULL;
2491   ti->basepath = NULL;
2492   ti->identifier = NULL;
2493   ti->name = getStringCopy(ANONYMOUS_NAME);
2494   ti->name_sorting = NULL;
2495   ti->author = getStringCopy(ANONYMOUS_NAME);
2496   ti->year = NULL;
2497
2498   ti->sort_priority = LEVELCLASS_UNDEFINED;     /* default: least priority */
2499   ti->latest_engine = FALSE;                    /* default: get from level */
2500   ti->parent_link = FALSE;
2501   ti->in_user_dir = FALSE;
2502   ti->user_defined = FALSE;
2503   ti->color = 0;
2504   ti->class_desc = NULL;
2505
2506   ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2507
2508   if (ti->type == TREE_TYPE_LEVEL_DIR)
2509   {
2510     ti->imported_from = NULL;
2511     ti->imported_by = NULL;
2512     ti->tested_by = NULL;
2513
2514     ti->graphics_set_ecs = NULL;
2515     ti->graphics_set_aga = NULL;
2516     ti->graphics_set = NULL;
2517     ti->sounds_set = NULL;
2518     ti->music_set = NULL;
2519     ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2520     ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2521     ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2522
2523     ti->level_filename = NULL;
2524     ti->level_filetype = NULL;
2525
2526     ti->special_flags = NULL;
2527
2528     ti->levels = 0;
2529     ti->first_level = 0;
2530     ti->last_level = 0;
2531     ti->level_group = FALSE;
2532     ti->handicap_level = 0;
2533     ti->readonly = TRUE;
2534     ti->handicap = TRUE;
2535     ti->skip_levels = FALSE;
2536   }
2537 }
2538
2539 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2540 {
2541   if (parent == NULL)
2542   {
2543     Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
2544
2545     setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2546
2547     return;
2548   }
2549
2550   /* copy all values from the parent structure */
2551
2552   ti->type = parent->type;
2553
2554   ti->node_top = parent->node_top;
2555   ti->node_parent = parent;
2556   ti->node_group = NULL;
2557   ti->next = NULL;
2558
2559   ti->cl_first = -1;
2560   ti->cl_cursor = -1;
2561
2562   ti->subdir = NULL;
2563   ti->fullpath = NULL;
2564   ti->basepath = NULL;
2565   ti->identifier = NULL;
2566   ti->name = getStringCopy(ANONYMOUS_NAME);
2567   ti->name_sorting = NULL;
2568   ti->author = getStringCopy(parent->author);
2569   ti->year = getStringCopy(parent->year);
2570
2571   ti->sort_priority = parent->sort_priority;
2572   ti->latest_engine = parent->latest_engine;
2573   ti->parent_link = FALSE;
2574   ti->in_user_dir = parent->in_user_dir;
2575   ti->user_defined = parent->user_defined;
2576   ti->color = parent->color;
2577   ti->class_desc = getStringCopy(parent->class_desc);
2578
2579   ti->infotext = getStringCopy(parent->infotext);
2580
2581   if (ti->type == TREE_TYPE_LEVEL_DIR)
2582   {
2583     ti->imported_from = getStringCopy(parent->imported_from);
2584     ti->imported_by = getStringCopy(parent->imported_by);
2585     ti->tested_by = getStringCopy(parent->tested_by);
2586
2587     ti->graphics_set_ecs = NULL;
2588     ti->graphics_set_aga = NULL;
2589     ti->graphics_set = NULL;
2590     ti->sounds_set = NULL;
2591     ti->music_set = NULL;
2592     ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2593     ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2594     ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2595
2596     ti->level_filename = NULL;
2597     ti->level_filetype = NULL;
2598
2599     ti->special_flags = getStringCopy(parent->special_flags);
2600
2601     ti->levels = 0;
2602     ti->first_level = 0;
2603     ti->last_level = 0;
2604     ti->level_group = FALSE;
2605     ti->handicap_level = 0;
2606 #if 1
2607     ti->readonly = parent->readonly;
2608 #else
2609     ti->readonly = TRUE;
2610 #endif
2611     ti->handicap = TRUE;
2612     ti->skip_levels = FALSE;
2613   }
2614 }
2615
2616 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2617 {
2618   TreeInfo *ti_copy = newTreeInfo();
2619
2620   /* copy all values from the original structure */
2621
2622   ti_copy->type                 = ti->type;
2623
2624   ti_copy->node_top             = ti->node_top;
2625   ti_copy->node_parent          = ti->node_parent;
2626   ti_copy->node_group           = ti->node_group;
2627   ti_copy->next                 = ti->next;
2628
2629   ti_copy->cl_first             = ti->cl_first;
2630   ti_copy->cl_cursor            = ti->cl_cursor;
2631
2632   ti_copy->subdir               = getStringCopy(ti->subdir);
2633   ti_copy->fullpath             = getStringCopy(ti->fullpath);
2634   ti_copy->basepath             = getStringCopy(ti->basepath);
2635   ti_copy->identifier           = getStringCopy(ti->identifier);
2636   ti_copy->name                 = getStringCopy(ti->name);
2637   ti_copy->name_sorting         = getStringCopy(ti->name_sorting);
2638   ti_copy->author               = getStringCopy(ti->author);
2639   ti_copy->year                 = getStringCopy(ti->year);
2640   ti_copy->imported_from        = getStringCopy(ti->imported_from);
2641   ti_copy->imported_by          = getStringCopy(ti->imported_by);
2642   ti_copy->tested_by            = getStringCopy(ti->tested_by);
2643
2644   ti_copy->graphics_set_ecs     = getStringCopy(ti->graphics_set_ecs);
2645   ti_copy->graphics_set_aga     = getStringCopy(ti->graphics_set_aga);
2646   ti_copy->graphics_set         = getStringCopy(ti->graphics_set);
2647   ti_copy->sounds_set           = getStringCopy(ti->sounds_set);
2648   ti_copy->music_set            = getStringCopy(ti->music_set);
2649   ti_copy->graphics_path        = getStringCopy(ti->graphics_path);
2650   ti_copy->sounds_path          = getStringCopy(ti->sounds_path);
2651   ti_copy->music_path           = getStringCopy(ti->music_path);
2652
2653   ti_copy->level_filename       = getStringCopy(ti->level_filename);
2654   ti_copy->level_filetype       = getStringCopy(ti->level_filetype);
2655
2656   ti_copy->special_flags        = getStringCopy(ti->special_flags);
2657
2658   ti_copy->levels               = ti->levels;
2659   ti_copy->first_level          = ti->first_level;
2660   ti_copy->last_level           = ti->last_level;
2661   ti_copy->sort_priority        = ti->sort_priority;
2662
2663   ti_copy->latest_engine        = ti->latest_engine;
2664
2665   ti_copy->level_group          = ti->level_group;
2666   ti_copy->parent_link          = ti->parent_link;
2667   ti_copy->in_user_dir          = ti->in_user_dir;
2668   ti_copy->user_defined         = ti->user_defined;
2669   ti_copy->readonly             = ti->readonly;
2670   ti_copy->handicap             = ti->handicap;
2671   ti_copy->skip_levels          = ti->skip_levels;
2672
2673   ti_copy->color                = ti->color;
2674   ti_copy->class_desc           = getStringCopy(ti->class_desc);
2675   ti_copy->handicap_level       = ti->handicap_level;
2676
2677   ti_copy->infotext             = getStringCopy(ti->infotext);
2678
2679   return ti_copy;
2680 }
2681
2682 static void freeTreeInfo(TreeInfo *ti)
2683 {
2684   if (ti == NULL)
2685     return;
2686
2687   checked_free(ti->subdir);
2688   checked_free(ti->fullpath);
2689   checked_free(ti->basepath);
2690   checked_free(ti->identifier);
2691
2692   checked_free(ti->name);
2693   checked_free(ti->name_sorting);
2694   checked_free(ti->author);
2695   checked_free(ti->year);
2696
2697   checked_free(ti->class_desc);
2698
2699   checked_free(ti->infotext);
2700
2701   if (ti->type == TREE_TYPE_LEVEL_DIR)
2702   {
2703     checked_free(ti->imported_from);
2704     checked_free(ti->imported_by);
2705     checked_free(ti->tested_by);
2706
2707     checked_free(ti->graphics_set_ecs);
2708     checked_free(ti->graphics_set_aga);
2709     checked_free(ti->graphics_set);
2710     checked_free(ti->sounds_set);
2711     checked_free(ti->music_set);
2712
2713     checked_free(ti->graphics_path);
2714     checked_free(ti->sounds_path);
2715     checked_free(ti->music_path);
2716
2717     checked_free(ti->level_filename);
2718     checked_free(ti->level_filetype);
2719
2720     checked_free(ti->special_flags);
2721   }
2722
2723   checked_free(ti);
2724 }
2725
2726 void setSetupInfo(struct TokenInfo *token_info,
2727                   int token_nr, char *token_value)
2728 {
2729   int token_type = token_info[token_nr].type;
2730   void *setup_value = token_info[token_nr].value;
2731
2732   if (token_value == NULL)
2733     return;
2734
2735   /* set setup field to corresponding token value */
2736   switch (token_type)
2737   {
2738     case TYPE_BOOLEAN:
2739     case TYPE_SWITCH:
2740       *(boolean *)setup_value = get_boolean_from_string(token_value);
2741       break;
2742
2743     case TYPE_SWITCH3:
2744       *(int *)setup_value = get_switch3_from_string(token_value);
2745       break;
2746
2747     case TYPE_KEY:
2748       *(Key *)setup_value = getKeyFromKeyName(token_value);
2749       break;
2750
2751     case TYPE_KEY_X11:
2752       *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2753       break;
2754
2755     case TYPE_INTEGER:
2756       *(int *)setup_value = get_integer_from_string(token_value);
2757       break;
2758
2759     case TYPE_STRING:
2760       checked_free(*(char **)setup_value);
2761       *(char **)setup_value = getStringCopy(token_value);
2762       break;
2763
2764     default:
2765       break;
2766   }
2767 }
2768
2769 static int compareTreeInfoEntries(const void *object1, const void *object2)
2770 {
2771   const TreeInfo *entry1 = *((TreeInfo **)object1);
2772   const TreeInfo *entry2 = *((TreeInfo **)object2);
2773   int class_sorting1, class_sorting2;
2774   int compare_result;
2775
2776   if (entry1->type == TREE_TYPE_LEVEL_DIR)
2777   {
2778     class_sorting1 = LEVELSORTING(entry1);
2779     class_sorting2 = LEVELSORTING(entry2);
2780   }
2781   else
2782   {
2783     class_sorting1 = ARTWORKSORTING(entry1);
2784     class_sorting2 = ARTWORKSORTING(entry2);
2785   }
2786
2787   if (entry1->parent_link || entry2->parent_link)
2788     compare_result = (entry1->parent_link ? -1 : +1);
2789   else if (entry1->sort_priority == entry2->sort_priority)
2790   {
2791     char *name1 = getStringToLower(entry1->name_sorting);
2792     char *name2 = getStringToLower(entry2->name_sorting);
2793
2794     compare_result = strcmp(name1, name2);
2795
2796     free(name1);
2797     free(name2);
2798   }
2799   else if (class_sorting1 == class_sorting2)
2800     compare_result = entry1->sort_priority - entry2->sort_priority;
2801   else
2802     compare_result = class_sorting1 - class_sorting2;
2803
2804   return compare_result;
2805 }
2806
2807 static void createParentTreeInfoNode(TreeInfo *node_parent)
2808 {
2809   TreeInfo *ti_new;
2810
2811   if (node_parent == NULL)
2812     return;
2813
2814   ti_new = newTreeInfo();
2815   setTreeInfoToDefaults(ti_new, node_parent->type);
2816
2817   ti_new->node_parent = node_parent;
2818   ti_new->parent_link = TRUE;
2819
2820   setString(&ti_new->identifier, node_parent->identifier);
2821   setString(&ti_new->name, ".. (parent directory)");
2822   setString(&ti_new->name_sorting, ti_new->name);
2823
2824   setString(&ti_new->subdir, "..");
2825   setString(&ti_new->fullpath, node_parent->fullpath);
2826
2827   ti_new->sort_priority = node_parent->sort_priority;
2828   ti_new->latest_engine = node_parent->latest_engine;
2829
2830   setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2831
2832   pushTreeInfo(&node_parent->node_group, ti_new);
2833 }
2834
2835
2836 /* -------------------------------------------------------------------------- */
2837 /* functions for handling level and custom artwork info cache                 */
2838 /* -------------------------------------------------------------------------- */
2839
2840 static void LoadArtworkInfoCache()
2841 {
2842   InitCacheDirectory();
2843
2844   if (artworkinfo_cache_old == NULL)
2845   {
2846     char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2847
2848     /* try to load artwork info hash from already existing cache file */
2849     artworkinfo_cache_old = loadSetupFileHash(filename);
2850
2851     /* if no artwork info cache file was found, start with empty hash */
2852     if (artworkinfo_cache_old == NULL)
2853       artworkinfo_cache_old = newSetupFileHash();
2854
2855     free(filename);
2856   }
2857
2858   if (artworkinfo_cache_new == NULL)
2859     artworkinfo_cache_new = newSetupFileHash();
2860 }
2861
2862 static void SaveArtworkInfoCache()
2863 {
2864   char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2865
2866   InitCacheDirectory();
2867
2868   saveSetupFileHash(artworkinfo_cache_new, filename);
2869
2870   free(filename);
2871 }
2872
2873 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
2874 {
2875   static char *prefix = NULL;
2876
2877   checked_free(prefix);
2878
2879   prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
2880
2881   return prefix;
2882 }
2883
2884 /* (identical to above function, but separate string buffer needed -- nasty) */
2885 static char *getCacheToken(char *prefix, char *suffix)
2886 {
2887   static char *token = NULL;
2888
2889   checked_free(token);
2890
2891   token = getStringCat2WithSeparator(prefix, suffix, ".");
2892
2893   return token;
2894 }
2895
2896 static char *getFileTimestampString(char *filename)
2897 {
2898 #if 1
2899   return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
2900 #else
2901   struct stat file_status;
2902
2903   if (stat(filename, &file_status) != 0)        /* cannot stat file */
2904     return getStringCopy(i_to_a(0));
2905
2906   return getStringCopy(i_to_a(file_status.st_mtime));
2907 #endif
2908 }
2909
2910 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
2911 {
2912   struct stat file_status;
2913
2914   if (timestamp_string == NULL)
2915     return TRUE;
2916
2917   if (stat(filename, &file_status) != 0)        /* cannot stat file */
2918     return TRUE;
2919
2920   return (file_status.st_mtime != atoi(timestamp_string));
2921 }
2922
2923 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
2924 {
2925   char *identifier = level_node->subdir;
2926   char *type_string = ARTWORK_DIRECTORY(type);
2927   char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2928   char *token_main = getCacheToken(token_prefix, "CACHED");
2929   char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2930   boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
2931   TreeInfo *artwork_info = NULL;
2932
2933   if (!use_artworkinfo_cache)
2934     return NULL;
2935
2936   if (cached)
2937   {
2938     int i;
2939
2940     artwork_info = newTreeInfo();
2941     setTreeInfoToDefaults(artwork_info, type);
2942
2943     /* set all structure fields according to the token/value pairs */
2944     ldi = *artwork_info;
2945     for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2946     {
2947       char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2948       char *value = getHashEntry(artworkinfo_cache_old, token);
2949
2950       setSetupInfo(artworkinfo_tokens, i, value);
2951
2952       /* check if cache entry for this item is invalid or incomplete */
2953       if (value == NULL)
2954       {
2955 #if 1
2956         Error(ERR_WARN, "cache entry '%s' invalid", token);
2957 #endif
2958
2959         cached = FALSE;
2960       }
2961     }
2962
2963     *artwork_info = ldi;
2964   }
2965
2966   if (cached)
2967   {
2968     char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2969                                         LEVELINFO_FILENAME);
2970     char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2971                                           ARTWORKINFO_FILENAME(type));
2972
2973     /* check if corresponding "levelinfo.conf" file has changed */
2974     token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2975     cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2976
2977     if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
2978       cached = FALSE;
2979
2980     /* check if corresponding "<artworkinfo>.conf" file has changed */
2981     token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2982     cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2983
2984     if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
2985       cached = FALSE;
2986
2987 #if 0
2988     if (!cached)
2989       printf("::: '%s': INVALIDATED FROM CACHE BY TIMESTAMP\n", identifier);
2990 #endif
2991
2992     checked_free(filename_levelinfo);
2993     checked_free(filename_artworkinfo);
2994   }
2995
2996   if (!cached && artwork_info != NULL)
2997   {
2998     freeTreeInfo(artwork_info);
2999
3000     return NULL;
3001   }
3002
3003   return artwork_info;
3004 }
3005
3006 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
3007                                      LevelDirTree *level_node, int type)
3008 {
3009   char *identifier = level_node->subdir;
3010   char *type_string = ARTWORK_DIRECTORY(type);
3011   char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3012   char *token_main = getCacheToken(token_prefix, "CACHED");
3013   boolean set_cache_timestamps = TRUE;
3014   int i;
3015
3016   setHashEntry(artworkinfo_cache_new, token_main, "true");
3017
3018   if (set_cache_timestamps)
3019   {
3020     char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3021                                         LEVELINFO_FILENAME);
3022     char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3023                                           ARTWORKINFO_FILENAME(type));
3024     char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
3025     char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
3026
3027     token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3028     setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
3029
3030     token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3031     setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
3032
3033     checked_free(filename_levelinfo);
3034     checked_free(filename_artworkinfo);
3035     checked_free(timestamp_levelinfo);
3036     checked_free(timestamp_artworkinfo);
3037   }
3038
3039   ldi = *artwork_info;
3040   for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3041   {
3042     char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3043     char *value = getSetupValue(artworkinfo_tokens[i].type,
3044                                 artworkinfo_tokens[i].value);
3045     if (value != NULL)
3046       setHashEntry(artworkinfo_cache_new, token, value);
3047   }
3048 }
3049
3050
3051 /* -------------------------------------------------------------------------- */
3052 /* functions for loading level info and custom artwork info                   */
3053 /* -------------------------------------------------------------------------- */
3054
3055 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
3056 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3057
3058 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3059                                           TreeInfo *node_parent,
3060                                           char *level_directory,
3061                                           char *directory_name)
3062 {
3063 #if 0
3064   static unsigned long progress_delay = 0;
3065   unsigned long progress_delay_value = 100;     /* (in milliseconds) */
3066 #endif
3067   char *directory_path = getPath2(level_directory, directory_name);
3068   char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3069   SetupFileHash *setup_file_hash;
3070   LevelDirTree *leveldir_new = NULL;
3071   int i;
3072
3073   /* unless debugging, silently ignore directories without "levelinfo.conf" */
3074   if (!options.debug && !fileExists(filename))
3075   {
3076     free(directory_path);
3077     free(filename);
3078
3079     return FALSE;
3080   }
3081
3082   setup_file_hash = loadSetupFileHash(filename);
3083
3084   if (setup_file_hash == NULL)
3085   {
3086     Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
3087
3088     free(directory_path);
3089     free(filename);
3090
3091     return FALSE;
3092   }
3093
3094   leveldir_new = newTreeInfo();
3095
3096   if (node_parent)
3097     setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3098   else
3099     setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3100
3101   leveldir_new->subdir = getStringCopy(directory_name);
3102
3103   checkSetupFileHashIdentifier(setup_file_hash, filename,
3104                                getCookie("LEVELINFO"));
3105
3106   /* set all structure fields according to the token/value pairs */
3107   ldi = *leveldir_new;
3108   for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3109     setSetupInfo(levelinfo_tokens, i,
3110                  getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3111   *leveldir_new = ldi;
3112
3113   if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3114     setString(&leveldir_new->name, leveldir_new->subdir);
3115
3116   if (leveldir_new->identifier == NULL)
3117     leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3118
3119   if (leveldir_new->name_sorting == NULL)
3120     leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3121
3122   if (node_parent == NULL)              /* top level group */
3123   {
3124     leveldir_new->basepath = getStringCopy(level_directory);
3125     leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3126   }
3127   else                                  /* sub level group */
3128   {
3129     leveldir_new->basepath = getStringCopy(node_parent->basepath);
3130     leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3131   }
3132
3133 #if 0
3134   if (leveldir_new->levels < 1)
3135     leveldir_new->levels = 1;
3136 #endif
3137
3138   leveldir_new->last_level =
3139     leveldir_new->first_level + leveldir_new->levels - 1;
3140
3141   leveldir_new->in_user_dir =
3142     (!strEqual(leveldir_new->basepath, options.level_directory));
3143
3144 #if 0
3145   printf("::: '%s' -> %d\n",
3146          leveldir_new->identifier,
3147          leveldir_new->in_user_dir);
3148 #endif
3149
3150   /* adjust some settings if user's private level directory was detected */
3151   if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3152       leveldir_new->in_user_dir &&
3153       (strEqual(leveldir_new->subdir, getLoginName()) ||
3154        strEqual(leveldir_new->name,   getLoginName()) ||
3155        strEqual(leveldir_new->author, getRealName())))
3156   {
3157     leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3158     leveldir_new->readonly = FALSE;
3159   }
3160
3161   leveldir_new->user_defined =
3162     (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3163
3164   leveldir_new->color = LEVELCOLOR(leveldir_new);
3165
3166   setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3167
3168   leveldir_new->handicap_level =        /* set handicap to default value */
3169     (leveldir_new->user_defined || !leveldir_new->handicap ?
3170      leveldir_new->last_level : leveldir_new->first_level);
3171
3172 #if 1
3173 #if 1
3174   DrawInitTextExt(leveldir_new->name, 150, FC_YELLOW,
3175                   leveldir_new->level_group);
3176 #else
3177   if (leveldir_new->level_group ||
3178       DelayReached(&progress_delay, progress_delay_value))
3179     DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3180 #endif
3181 #else
3182   DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3183 #endif
3184
3185 #if 0
3186   /* !!! don't skip sets without levels (else artwork base sets are missing) */
3187 #if 1
3188   if (leveldir_new->levels < 1 && !leveldir_new->level_group)
3189   {
3190     /* skip level sets without levels (which are probably artwork base sets) */
3191
3192     freeSetupFileHash(setup_file_hash);
3193     free(directory_path);
3194     free(filename);
3195
3196     return FALSE;
3197   }
3198 #endif
3199 #endif
3200
3201   pushTreeInfo(node_first, leveldir_new);
3202
3203   freeSetupFileHash(setup_file_hash);
3204
3205   if (leveldir_new->level_group)
3206   {
3207     /* create node to link back to current level directory */
3208     createParentTreeInfoNode(leveldir_new);
3209
3210     /* recursively step into sub-directory and look for more level series */
3211     LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3212                               leveldir_new, directory_path);
3213   }
3214
3215   free(directory_path);
3216   free(filename);
3217
3218   return TRUE;
3219 }
3220
3221 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3222                                       TreeInfo *node_parent,
3223                                       char *level_directory)
3224 {
3225   DIR *dir;
3226   struct dirent *dir_entry;
3227   boolean valid_entry_found = FALSE;
3228
3229   if ((dir = opendir(level_directory)) == NULL)
3230   {
3231     Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3232     return;
3233   }
3234
3235   while ((dir_entry = readdir(dir)) != NULL)    /* loop until last dir entry */
3236   {
3237     struct stat file_status;
3238     char *directory_name = dir_entry->d_name;
3239     char *directory_path = getPath2(level_directory, directory_name);
3240
3241     /* skip entries for current and parent directory */
3242     if (strEqual(directory_name, ".") ||
3243         strEqual(directory_name, ".."))
3244     {
3245       free(directory_path);
3246       continue;
3247     }
3248
3249     /* find out if directory entry is itself a directory */
3250     if (stat(directory_path, &file_status) != 0 ||      /* cannot stat file */
3251         (file_status.st_mode & S_IFMT) != S_IFDIR)      /* not a directory */
3252     {
3253       free(directory_path);
3254       continue;
3255     }
3256
3257     free(directory_path);
3258
3259     if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3260         strEqual(directory_name, SOUNDS_DIRECTORY) ||
3261         strEqual(directory_name, MUSIC_DIRECTORY))
3262       continue;
3263
3264     valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3265                                                     level_directory,
3266                                                     directory_name);
3267   }
3268
3269   closedir(dir);
3270
3271   /* special case: top level directory may directly contain "levelinfo.conf" */
3272   if (node_parent == NULL && !valid_entry_found)
3273   {
3274     /* check if this directory directly contains a file "levelinfo.conf" */
3275     valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3276                                                     level_directory, ".");
3277   }
3278
3279   if (!valid_entry_found)
3280     Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
3281           level_directory);
3282 }
3283
3284 boolean AdjustGraphicsForEMC()
3285 {
3286   boolean settings_changed = FALSE;
3287
3288   settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3289   settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3290
3291   return settings_changed;
3292 }
3293
3294 void LoadLevelInfo()
3295 {
3296   InitUserLevelDirectory(getLoginName());
3297
3298   DrawInitText("Loading level series", 120, FC_GREEN);
3299
3300   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3301   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3302
3303   /* after loading all level set information, clone the level directory tree
3304      and remove all level sets without levels (these may still contain artwork
3305      to be offered in the setup menu as "custom artwork", and are therefore
3306      checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3307   leveldir_first_all = leveldir_first;
3308   cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3309
3310   AdjustGraphicsForEMC();
3311
3312   /* before sorting, the first entries will be from the user directory */
3313   leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3314
3315   if (leveldir_first == NULL)
3316     Error(ERR_EXIT, "cannot find any valid level series in any directory");
3317
3318   sortTreeInfo(&leveldir_first);
3319
3320 #if 0
3321   dumpTreeInfo(leveldir_first, 0);
3322 #endif
3323 }
3324
3325 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3326                                               TreeInfo *node_parent,
3327                                               char *base_directory,
3328                                               char *directory_name, int type)
3329 {
3330   char *directory_path = getPath2(base_directory, directory_name);
3331   char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3332   SetupFileHash *setup_file_hash = NULL;
3333   TreeInfo *artwork_new = NULL;
3334   int i;
3335
3336   if (fileExists(filename))
3337     setup_file_hash = loadSetupFileHash(filename);
3338
3339   if (setup_file_hash == NULL)  /* no config file -- look for artwork files */
3340   {
3341     DIR *dir;
3342     struct dirent *dir_entry;
3343     boolean valid_file_found = FALSE;
3344
3345     if ((dir = opendir(directory_path)) != NULL)
3346     {
3347       while ((dir_entry = readdir(dir)) != NULL)
3348       {
3349         char *entry_name = dir_entry->d_name;
3350
3351         if (FileIsArtworkType(entry_name, type))
3352         {
3353           valid_file_found = TRUE;
3354           break;
3355         }
3356       }
3357
3358       closedir(dir);
3359     }
3360
3361     if (!valid_file_found)
3362     {
3363       if (!strEqual(directory_name, "."))
3364         Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
3365
3366       free(directory_path);
3367       free(filename);
3368
3369       return FALSE;
3370     }
3371   }
3372
3373   artwork_new = newTreeInfo();
3374
3375   if (node_parent)
3376     setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3377   else
3378     setTreeInfoToDefaults(artwork_new, type);
3379
3380   artwork_new->subdir = getStringCopy(directory_name);
3381
3382   if (setup_file_hash)  /* (before defining ".color" and ".class_desc") */
3383   {
3384 #if 0
3385     checkSetupFileHashIdentifier(setup_file_hash, filename, getCookie("..."));
3386 #endif
3387
3388     /* set all structure fields according to the token/value pairs */
3389     ldi = *artwork_new;
3390     for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3391       setSetupInfo(levelinfo_tokens, i,
3392                    getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3393     *artwork_new = ldi;
3394
3395     if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3396       setString(&artwork_new->name, artwork_new->subdir);
3397
3398     if (artwork_new->identifier == NULL)
3399       artwork_new->identifier = getStringCopy(artwork_new->subdir);
3400
3401     if (artwork_new->name_sorting == NULL)
3402       artwork_new->name_sorting = getStringCopy(artwork_new->name);
3403   }
3404
3405   if (node_parent == NULL)              /* top level group */
3406   {
3407     artwork_new->basepath = getStringCopy(base_directory);
3408     artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3409   }
3410   else                                  /* sub level group */
3411   {
3412     artwork_new->basepath = getStringCopy(node_parent->basepath);
3413     artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3414   }
3415
3416   artwork_new->in_user_dir =
3417     (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3418
3419   /* (may use ".sort_priority" from "setup_file_hash" above) */
3420   artwork_new->color = ARTWORKCOLOR(artwork_new);
3421
3422   setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3423
3424   if (setup_file_hash == NULL)  /* (after determining ".user_defined") */
3425   {
3426     if (strEqual(artwork_new->subdir, "."))
3427     {
3428       if (artwork_new->user_defined)
3429       {
3430         setString(&artwork_new->identifier, "private");
3431         artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3432       }
3433       else
3434       {
3435         setString(&artwork_new->identifier, "classic");
3436         artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3437       }
3438
3439       /* set to new values after changing ".sort_priority" */
3440       artwork_new->color = ARTWORKCOLOR(artwork_new);
3441
3442       setString(&artwork_new->class_desc,
3443                 getLevelClassDescription(artwork_new));
3444     }
3445     else
3446     {
3447       setString(&artwork_new->identifier, artwork_new->subdir);
3448     }
3449
3450     setString(&artwork_new->name, artwork_new->identifier);
3451     setString(&artwork_new->name_sorting, artwork_new->name);
3452   }
3453
3454 #if 0
3455   DrawInitText(artwork_new->name, 150, FC_YELLOW);
3456 #endif
3457
3458   pushTreeInfo(node_first, artwork_new);
3459
3460   freeSetupFileHash(setup_file_hash);
3461
3462   free(directory_path);
3463   free(filename);
3464
3465   return TRUE;
3466 }
3467
3468 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3469                                           TreeInfo *node_parent,
3470                                           char *base_directory, int type)
3471 {
3472   DIR *dir;
3473   struct dirent *dir_entry;
3474   boolean valid_entry_found = FALSE;
3475
3476   if ((dir = opendir(base_directory)) == NULL)
3477   {
3478     /* display error if directory is main "options.graphics_directory" etc. */
3479     if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3480       Error(ERR_WARN, "cannot read directory '%s'", base_directory);
3481
3482     return;
3483   }
3484
3485   while ((dir_entry = readdir(dir)) != NULL)    /* loop until last dir entry */
3486   {
3487     struct stat file_status;
3488     char *directory_name = dir_entry->d_name;
3489     char *directory_path = getPath2(base_directory, directory_name);
3490
3491     /* skip directory entries for current and parent directory */
3492     if (strEqual(directory_name, ".") ||
3493         strEqual(directory_name, ".."))
3494     {
3495       free(directory_path);
3496       continue;
3497     }
3498
3499     /* skip directory entries which are not a directory or are not accessible */
3500     if (stat(directory_path, &file_status) != 0 ||      /* cannot stat file */
3501         (file_status.st_mode & S_IFMT) != S_IFDIR)      /* not a directory */
3502     {
3503       free(directory_path);
3504       continue;
3505     }
3506
3507     free(directory_path);
3508
3509     /* check if this directory contains artwork with or without config file */
3510     valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3511                                                         base_directory,
3512                                                         directory_name, type);
3513   }
3514
3515   closedir(dir);
3516
3517   /* check if this directory directly contains artwork itself */
3518   valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3519                                                       base_directory, ".",
3520                                                       type);
3521   if (!valid_entry_found)
3522     Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
3523           base_directory);
3524 }
3525
3526 static TreeInfo *getDummyArtworkInfo(int type)
3527 {
3528   /* this is only needed when there is completely no artwork available */
3529   TreeInfo *artwork_new = newTreeInfo();
3530
3531   setTreeInfoToDefaults(artwork_new, type);
3532
3533   setString(&artwork_new->subdir,   UNDEFINED_FILENAME);
3534   setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3535   setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3536
3537   setString(&artwork_new->identifier,   UNDEFINED_FILENAME);
3538   setString(&artwork_new->name,         UNDEFINED_FILENAME);
3539   setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3540
3541   return artwork_new;
3542 }
3543
3544 void LoadArtworkInfo()
3545 {
3546   LoadArtworkInfoCache();
3547
3548   DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3549
3550   LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3551                                 options.graphics_directory,
3552                                 TREE_TYPE_GRAPHICS_DIR);
3553   LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3554                                 getUserGraphicsDir(),
3555                                 TREE_TYPE_GRAPHICS_DIR);
3556
3557   LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3558                                 options.sounds_directory,
3559                                 TREE_TYPE_SOUNDS_DIR);
3560   LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3561                                 getUserSoundsDir(),
3562                                 TREE_TYPE_SOUNDS_DIR);
3563
3564   LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3565                                 options.music_directory,
3566                                 TREE_TYPE_MUSIC_DIR);
3567   LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3568                                 getUserMusicDir(),
3569                                 TREE_TYPE_MUSIC_DIR);
3570
3571   if (artwork.gfx_first == NULL)
3572     artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3573   if (artwork.snd_first == NULL)
3574     artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3575   if (artwork.mus_first == NULL)
3576     artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3577
3578   /* before sorting, the first entries will be from the user directory */
3579   artwork.gfx_current =
3580     getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3581   if (artwork.gfx_current == NULL)
3582     artwork.gfx_current =
3583       getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3584   if (artwork.gfx_current == NULL)
3585     artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3586
3587   artwork.snd_current =
3588     getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3589   if (artwork.snd_current == NULL)
3590     artwork.snd_current =
3591       getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3592   if (artwork.snd_current == NULL)
3593     artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3594
3595   artwork.mus_current =
3596     getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3597   if (artwork.mus_current == NULL)
3598     artwork.mus_current =
3599       getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3600   if (artwork.mus_current == NULL)
3601     artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3602
3603   artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3604   artwork.snd_current_identifier = artwork.snd_current->identifier;
3605   artwork.mus_current_identifier = artwork.mus_current->identifier;
3606
3607 #if 0
3608   printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
3609   printf("sounds set == %s\n\n", artwork.snd_current_identifier);
3610   printf("music set == %s\n\n", artwork.mus_current_identifier);
3611 #endif
3612
3613   sortTreeInfo(&artwork.gfx_first);
3614   sortTreeInfo(&artwork.snd_first);
3615   sortTreeInfo(&artwork.mus_first);
3616
3617 #if 0
3618   dumpTreeInfo(artwork.gfx_first, 0);
3619   dumpTreeInfo(artwork.snd_first, 0);
3620   dumpTreeInfo(artwork.mus_first, 0);
3621 #endif
3622 }
3623
3624 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
3625                                   LevelDirTree *level_node)
3626 {
3627 #if 0
3628   static unsigned long progress_delay = 0;
3629   unsigned long progress_delay_value = 100;     /* (in milliseconds) */
3630 #endif
3631   int type = (*artwork_node)->type;
3632
3633   /* recursively check all level directories for artwork sub-directories */
3634
3635   while (level_node)
3636   {
3637     /* check all tree entries for artwork, but skip parent link entries */
3638     if (!level_node->parent_link)
3639     {
3640       TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3641       boolean cached = (artwork_new != NULL);
3642
3643       if (cached)
3644       {
3645         pushTreeInfo(artwork_node, artwork_new);
3646       }
3647       else
3648       {
3649         TreeInfo *topnode_last = *artwork_node;
3650         char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3651                               ARTWORK_DIRECTORY(type));
3652
3653         LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3654
3655         if (topnode_last != *artwork_node)      /* check for newly added node */
3656         {
3657           artwork_new = *artwork_node;
3658
3659           setString(&artwork_new->identifier,   level_node->subdir);
3660           setString(&artwork_new->name,         level_node->name);
3661           setString(&artwork_new->name_sorting, level_node->name_sorting);
3662
3663           artwork_new->sort_priority = level_node->sort_priority;
3664           artwork_new->color = LEVELCOLOR(artwork_new);
3665         }
3666
3667         free(path);
3668       }
3669
3670       /* insert artwork info (from old cache or filesystem) into new cache */
3671       if (artwork_new != NULL)
3672         setArtworkInfoCacheEntry(artwork_new, level_node, type);
3673     }
3674
3675 #if 1
3676     DrawInitTextExt(level_node->name, 150, FC_YELLOW,
3677                     level_node->level_group);
3678 #else
3679     if (level_node->level_group ||
3680         DelayReached(&progress_delay, progress_delay_value))
3681       DrawInitText(level_node->name, 150, FC_YELLOW);
3682 #endif
3683
3684     if (level_node->node_group != NULL)
3685       LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
3686
3687     level_node = level_node->next;
3688   }
3689 }
3690
3691 void LoadLevelArtworkInfo()
3692 {
3693   DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
3694
3695   LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
3696   LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
3697   LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
3698
3699   SaveArtworkInfoCache();
3700
3701   /* needed for reloading level artwork not known at ealier stage */
3702
3703   if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
3704   {
3705     artwork.gfx_current =
3706       getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3707     if (artwork.gfx_current == NULL)
3708       artwork.gfx_current =
3709         getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3710     if (artwork.gfx_current == NULL)
3711       artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3712   }
3713
3714   if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
3715   {
3716     artwork.snd_current =
3717       getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3718     if (artwork.snd_current == NULL)
3719       artwork.snd_current =
3720         getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3721     if (artwork.snd_current == NULL)
3722       artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3723   }
3724
3725   if (!strEqual(artwork.mus_current_identifier, setup.music_set))
3726   {
3727     artwork.mus_current =
3728       getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3729     if (artwork.mus_current == NULL)
3730       artwork.mus_current =
3731         getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3732     if (artwork.mus_current == NULL)
3733       artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3734   }
3735
3736   sortTreeInfo(&artwork.gfx_first);
3737   sortTreeInfo(&artwork.snd_first);
3738   sortTreeInfo(&artwork.mus_first);
3739
3740 #if 0
3741   dumpTreeInfo(artwork.gfx_first, 0);
3742   dumpTreeInfo(artwork.snd_first, 0);
3743   dumpTreeInfo(artwork.mus_first, 0);
3744 #endif
3745 }
3746
3747 static void SaveUserLevelInfo()
3748 {
3749   LevelDirTree *level_info;
3750   char *filename;
3751   FILE *file;
3752   int i;
3753
3754   filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
3755
3756   if (!(file = fopen(filename, MODE_WRITE)))
3757   {
3758     Error(ERR_WARN, "cannot write level info file '%s'", filename);
3759     free(filename);
3760     return;
3761   }
3762
3763   level_info = newTreeInfo();
3764
3765   /* always start with reliable default values */
3766   setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
3767
3768   setString(&level_info->name, getLoginName());
3769   setString(&level_info->author, getRealName());
3770   level_info->levels = 100;
3771   level_info->first_level = 1;
3772
3773   token_value_position = TOKEN_VALUE_POSITION_SHORT;
3774
3775   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3776                                                  getCookie("LEVELINFO")));
3777
3778   ldi = *level_info;
3779   for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3780   {
3781     if (i == LEVELINFO_TOKEN_NAME ||
3782         i == LEVELINFO_TOKEN_AUTHOR ||
3783         i == LEVELINFO_TOKEN_LEVELS ||
3784         i == LEVELINFO_TOKEN_FIRST_LEVEL)
3785       fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
3786
3787     /* just to make things nicer :) */
3788     if (i == LEVELINFO_TOKEN_AUTHOR)
3789       fprintf(file, "\n");      
3790   }
3791
3792   token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
3793
3794   fclose(file);
3795
3796   SetFilePermissions(filename, PERMS_PRIVATE);
3797
3798   freeTreeInfo(level_info);
3799   free(filename);
3800 }
3801
3802 char *getSetupValue(int type, void *value)
3803 {
3804   static char value_string[MAX_LINE_LEN];
3805
3806   if (value == NULL)
3807     return NULL;
3808
3809   switch (type)
3810   {
3811     case TYPE_BOOLEAN:
3812       strcpy(value_string, (*(boolean *)value ? "true" : "false"));
3813       break;
3814
3815     case TYPE_SWITCH:
3816       strcpy(value_string, (*(boolean *)value ? "on" : "off"));
3817       break;
3818
3819     case TYPE_SWITCH3:
3820       strcpy(value_string, (*(int *)value == AUTO  ? "auto" :
3821                             *(int *)value == FALSE ? "off" : "on"));
3822       break;
3823
3824     case TYPE_YES_NO:
3825       strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
3826       break;
3827
3828     case TYPE_YES_NO_AUTO:
3829       strcpy(value_string, (*(int *)value == AUTO  ? "auto" :
3830                             *(int *)value == FALSE ? "no" : "yes"));
3831       break;
3832
3833     case TYPE_ECS_AGA:
3834       strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
3835       break;
3836
3837     case TYPE_KEY:
3838       strcpy(value_string, getKeyNameFromKey(*(Key *)value));
3839       break;
3840
3841     case TYPE_KEY_X11:
3842       strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
3843       break;
3844
3845     case TYPE_INTEGER:
3846       sprintf(value_string, "%d", *(int *)value);
3847       break;
3848
3849     case TYPE_STRING:
3850       if (*(char **)value == NULL)
3851         return NULL;
3852
3853       strcpy(value_string, *(char **)value);
3854       break;
3855
3856     default:
3857       value_string[0] = '\0';
3858       break;
3859   }
3860
3861   if (type & TYPE_GHOSTED)
3862     strcpy(value_string, "n/a");
3863
3864   return value_string;
3865 }
3866
3867 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
3868 {
3869   int i;
3870   char *line;
3871   static char token_string[MAX_LINE_LEN];
3872   int token_type = token_info[token_nr].type;
3873   void *setup_value = token_info[token_nr].value;
3874   char *token_text = token_info[token_nr].text;
3875   char *value_string = getSetupValue(token_type, setup_value);
3876
3877   /* build complete token string */
3878   sprintf(token_string, "%s%s", prefix, token_text);
3879
3880   /* build setup entry line */
3881   line = getFormattedSetupEntry(token_string, value_string);
3882
3883   if (token_type == TYPE_KEY_X11)
3884   {
3885     Key key = *(Key *)setup_value;
3886     char *keyname = getKeyNameFromKey(key);
3887
3888     /* add comment, if useful */
3889     if (!strEqual(keyname, "(undefined)") &&
3890         !strEqual(keyname, "(unknown)"))
3891     {
3892       /* add at least one whitespace */
3893       strcat(line, " ");
3894       for (i = strlen(line); i < token_comment_position; i++)
3895         strcat(line, " ");
3896
3897       strcat(line, "# ");
3898       strcat(line, keyname);
3899     }
3900   }
3901
3902   return line;
3903 }
3904
3905 void LoadLevelSetup_LastSeries()
3906 {
3907   /* ----------------------------------------------------------------------- */
3908   /* ~/.<program>/levelsetup.conf                                            */
3909   /* ----------------------------------------------------------------------- */
3910
3911   char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3912   SetupFileHash *level_setup_hash = NULL;
3913
3914   /* always start with reliable default values */
3915   leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3916
3917 #if defined(CREATE_SPECIAL_EDITION_RND_JUE)
3918   leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3919                                                "jue_start");
3920   if (leveldir_current == NULL)
3921     leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3922 #endif
3923
3924   if ((level_setup_hash = loadSetupFileHash(filename)))
3925   {
3926     char *last_level_series =
3927       getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
3928
3929     leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3930                                                  last_level_series);
3931     if (leveldir_current == NULL)
3932       leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3933
3934     checkSetupFileHashIdentifier(level_setup_hash, filename,
3935                                  getCookie("LEVELSETUP"));
3936
3937     freeSetupFileHash(level_setup_hash);
3938   }
3939   else
3940     Error(ERR_WARN, "using default setup values");
3941
3942   free(filename);
3943 }
3944
3945 void SaveLevelSetup_LastSeries()
3946 {
3947   /* ----------------------------------------------------------------------- */
3948   /* ~/.<program>/levelsetup.conf                                            */
3949   /* ----------------------------------------------------------------------- */
3950
3951   char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3952   char *level_subdir = leveldir_current->subdir;
3953   FILE *file;
3954
3955   InitUserDataDirectory();
3956
3957   if (!(file = fopen(filename, MODE_WRITE)))
3958   {
3959     Error(ERR_WARN, "cannot write setup file '%s'", filename);
3960     free(filename);
3961     return;
3962   }
3963
3964   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3965                                                  getCookie("LEVELSETUP")));
3966   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
3967                                                level_subdir));
3968
3969   fclose(file);
3970
3971   SetFilePermissions(filename, PERMS_PRIVATE);
3972
3973   free(filename);
3974 }
3975
3976 static void checkSeriesInfo()
3977 {
3978   static char *level_directory = NULL;
3979   DIR *dir;
3980   struct dirent *dir_entry;
3981
3982   /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
3983
3984   level_directory = getPath2((leveldir_current->in_user_dir ?
3985                               getUserLevelDir(NULL) :
3986                               options.level_directory),
3987                              leveldir_current->fullpath);
3988
3989   if ((dir = opendir(level_directory)) == NULL)
3990   {
3991     Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3992     return;
3993   }
3994
3995   while ((dir_entry = readdir(dir)) != NULL)    /* last directory entry */
3996   {
3997     if (strlen(dir_entry->d_name) > 4 &&
3998         dir_entry->d_name[3] == '.' &&
3999         strEqual(&dir_entry->d_name[4], LEVELFILE_EXTENSION))
4000     {
4001       char levelnum_str[4];
4002       int levelnum_value;
4003
4004       strncpy(levelnum_str, dir_entry->d_name, 3);
4005       levelnum_str[3] = '\0';
4006
4007       levelnum_value = atoi(levelnum_str);
4008
4009 #if 0
4010       if (levelnum_value < leveldir_current->first_level)
4011       {
4012         Error(ERR_WARN, "additional level %d found", levelnum_value);
4013         leveldir_current->first_level = levelnum_value;
4014       }
4015       else if (levelnum_value > leveldir_current->last_level)
4016       {
4017         Error(ERR_WARN, "additional level %d found", levelnum_value);
4018         leveldir_current->last_level = levelnum_value;
4019       }
4020 #endif
4021     }
4022   }
4023
4024   closedir(dir);
4025 }
4026
4027 void LoadLevelSetup_SeriesInfo()
4028 {
4029   char *filename;
4030   SetupFileHash *level_setup_hash = NULL;
4031   char *level_subdir = leveldir_current->subdir;
4032
4033   /* always start with reliable default values */
4034   level_nr = leveldir_current->first_level;
4035
4036   checkSeriesInfo(leveldir_current);
4037
4038   /* ----------------------------------------------------------------------- */
4039   /* ~/.<program>/levelsetup/<level series>/levelsetup.conf                  */
4040   /* ----------------------------------------------------------------------- */
4041
4042   level_subdir = leveldir_current->subdir;
4043
4044   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4045
4046   if ((level_setup_hash = loadSetupFileHash(filename)))
4047   {
4048     char *token_value;
4049
4050     token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
4051
4052     if (token_value)
4053     {
4054       level_nr = atoi(token_value);
4055
4056       if (level_nr < leveldir_current->first_level)
4057         level_nr = leveldir_current->first_level;
4058       if (level_nr > leveldir_current->last_level)
4059         level_nr = leveldir_current->last_level;
4060     }
4061
4062     token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
4063
4064     if (token_value)
4065     {
4066       int level_nr = atoi(token_value);
4067
4068       if (level_nr < leveldir_current->first_level)
4069         level_nr = leveldir_current->first_level;
4070       if (level_nr > leveldir_current->last_level + 1)
4071         level_nr = leveldir_current->last_level;
4072
4073       if (leveldir_current->user_defined || !leveldir_current->handicap)
4074         level_nr = leveldir_current->last_level;
4075
4076       leveldir_current->handicap_level = level_nr;
4077     }
4078
4079     checkSetupFileHashIdentifier(level_setup_hash, filename,
4080                                  getCookie("LEVELSETUP"));
4081
4082     freeSetupFileHash(level_setup_hash);
4083   }
4084   else
4085     Error(ERR_WARN, "using default setup values");
4086
4087   free(filename);
4088 }
4089
4090 void SaveLevelSetup_SeriesInfo()
4091 {
4092   char *filename;
4093   char *level_subdir = leveldir_current->subdir;
4094   char *level_nr_str = int2str(level_nr, 0);
4095   char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
4096   FILE *file;
4097
4098   /* ----------------------------------------------------------------------- */
4099   /* ~/.<program>/levelsetup/<level series>/levelsetup.conf                  */
4100   /* ----------------------------------------------------------------------- */
4101
4102   InitLevelSetupDirectory(level_subdir);
4103
4104   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4105
4106   if (!(file = fopen(filename, MODE_WRITE)))
4107   {
4108     Error(ERR_WARN, "cannot write setup file '%s'", filename);
4109     free(filename);
4110     return;
4111   }
4112
4113   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
4114                                                  getCookie("LEVELSETUP")));
4115   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
4116                                                level_nr_str));
4117   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
4118                                                handicap_level_str));
4119
4120   fclose(file);
4121
4122   SetFilePermissions(filename, PERMS_PRIVATE);
4123
4124   free(filename);
4125 }