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