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