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