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