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