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