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