rnd-20100325-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 static boolean posix_process_running_setgid()
1414 {
1415 #if defined(PLATFORM_UNIX)
1416   return (getgid() != getegid());
1417 #else
1418   return FALSE;
1419 #endif
1420 }
1421
1422 void createDirectory(char *dir, char *text, int permission_class)
1423 {
1424   /* leave "other" permissions in umask untouched, but ensure group parts
1425      of USERDATA_DIR_MODE are not masked */
1426   mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1427                      DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1428   mode_t last_umask = posix_umask(0);
1429   mode_t group_umask = ~(dir_mode & S_IRWXG);
1430   int running_setgid = posix_process_running_setgid();
1431
1432   /* if we're setgid, protect files against "other" */
1433   /* else keep umask(0) to make the dir world-writable */
1434
1435   if (running_setgid)
1436     posix_umask(last_umask & group_umask);
1437   else
1438     dir_mode |= MODE_W_ALL;
1439
1440   if (!fileExists(dir))
1441     if (posix_mkdir(dir, dir_mode) != 0)
1442       Error(ERR_WARN, "cannot create %s directory '%s'", text, dir);
1443
1444   if (permission_class == PERMS_PUBLIC && !running_setgid)
1445     chmod(dir, dir_mode);
1446
1447   posix_umask(last_umask);              /* restore previous umask */
1448 }
1449
1450 void InitUserDataDirectory()
1451 {
1452   createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1453 }
1454
1455 void SetFilePermissions(char *filename, int permission_class)
1456 {
1457   int running_setgid = posix_process_running_setgid();
1458   int perms = (permission_class == PERMS_PRIVATE ?
1459                FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
1460
1461   if (permission_class == PERMS_PUBLIC && !running_setgid)
1462     perms |= MODE_W_ALL;
1463
1464   chmod(filename, perms);
1465 }
1466
1467 char *getCookie(char *file_type)
1468 {
1469   static char cookie[MAX_COOKIE_LEN + 1];
1470
1471   if (strlen(program.cookie_prefix) + 1 +
1472       strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1473     return "[COOKIE ERROR]";    /* should never happen */
1474
1475   sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1476           program.cookie_prefix, file_type,
1477           program.version_major, program.version_minor);
1478
1479   return cookie;
1480 }
1481
1482 int getFileVersionFromCookieString(const char *cookie)
1483 {
1484   const char *ptr_cookie1, *ptr_cookie2;
1485   const char *pattern1 = "_FILE_VERSION_";
1486   const char *pattern2 = "?.?";
1487   const int len_cookie = strlen(cookie);
1488   const int len_pattern1 = strlen(pattern1);
1489   const int len_pattern2 = strlen(pattern2);
1490   const int len_pattern = len_pattern1 + len_pattern2;
1491   int version_major, version_minor;
1492
1493   if (len_cookie <= len_pattern)
1494     return -1;
1495
1496   ptr_cookie1 = &cookie[len_cookie - len_pattern];
1497   ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1498
1499   if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1500     return -1;
1501
1502   if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1503       ptr_cookie2[1] != '.' ||
1504       ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1505     return -1;
1506
1507   version_major = ptr_cookie2[0] - '0';
1508   version_minor = ptr_cookie2[2] - '0';
1509
1510   return VERSION_IDENT(version_major, version_minor, 0, 0);
1511 }
1512
1513 boolean checkCookieString(const char *cookie, const char *template)
1514 {
1515   const char *pattern = "_FILE_VERSION_?.?";
1516   const int len_cookie = strlen(cookie);
1517   const int len_template = strlen(template);
1518   const int len_pattern = strlen(pattern);
1519
1520   if (len_cookie != len_template)
1521     return FALSE;
1522
1523   if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1524     return FALSE;
1525
1526   return TRUE;
1527 }
1528
1529 /* ------------------------------------------------------------------------- */
1530 /* setup file list and hash handling functions                               */
1531 /* ------------------------------------------------------------------------- */
1532
1533 char *getFormattedSetupEntry(char *token, char *value)
1534 {
1535   int i;
1536   static char entry[MAX_LINE_LEN];
1537
1538   /* if value is an empty string, just return token without value */
1539   if (*value == '\0')
1540     return token;
1541
1542   /* start with the token and some spaces to format output line */
1543   sprintf(entry, "%s:", token);
1544   for (i = strlen(entry); i < token_value_position; i++)
1545     strcat(entry, " ");
1546
1547   /* continue with the token's value */
1548   strcat(entry, value);
1549
1550   return entry;
1551 }
1552
1553 SetupFileList *newSetupFileList(char *token, char *value)
1554 {
1555   SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1556
1557   new->token = getStringCopy(token);
1558   new->value = getStringCopy(value);
1559
1560   new->next = NULL;
1561
1562   return new;
1563 }
1564
1565 void freeSetupFileList(SetupFileList *list)
1566 {
1567   if (list == NULL)
1568     return;
1569
1570   checked_free(list->token);
1571   checked_free(list->value);
1572
1573   if (list->next)
1574     freeSetupFileList(list->next);
1575
1576   free(list);
1577 }
1578
1579 char *getListEntry(SetupFileList *list, char *token)
1580 {
1581   if (list == NULL)
1582     return NULL;
1583
1584   if (strEqual(list->token, token))
1585     return list->value;
1586   else
1587     return getListEntry(list->next, token);
1588 }
1589
1590 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1591 {
1592   if (list == NULL)
1593     return NULL;
1594
1595   if (strEqual(list->token, token))
1596   {
1597     checked_free(list->value);
1598
1599     list->value = getStringCopy(value);
1600
1601     return list;
1602   }
1603   else if (list->next == NULL)
1604     return (list->next = newSetupFileList(token, value));
1605   else
1606     return setListEntry(list->next, token, value);
1607 }
1608
1609 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1610 {
1611   if (list == NULL)
1612     return NULL;
1613
1614   if (list->next == NULL)
1615     return (list->next = newSetupFileList(token, value));
1616   else
1617     return addListEntry(list->next, token, value);
1618 }
1619
1620 #ifdef DEBUG
1621 static void printSetupFileList(SetupFileList *list)
1622 {
1623   if (!list)
1624     return;
1625
1626   printf("token: '%s'\n", list->token);
1627   printf("value: '%s'\n", list->value);
1628
1629   printSetupFileList(list->next);
1630 }
1631 #endif
1632
1633 #ifdef DEBUG
1634 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1635 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1636 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1637 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1638 #else
1639 #define insert_hash_entry hashtable_insert
1640 #define search_hash_entry hashtable_search
1641 #define change_hash_entry hashtable_change
1642 #define remove_hash_entry hashtable_remove
1643 #endif
1644
1645 unsigned int get_hash_from_key(void *key)
1646 {
1647   /*
1648     djb2
1649
1650     This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1651     'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1652     uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1653     it works better than many other constants, prime or not) has never been
1654     adequately explained.
1655
1656     If you just want to have a good hash function, and cannot wait, djb2
1657     is one of the best string hash functions i know. It has excellent
1658     distribution and speed on many different sets of keys and table sizes.
1659     You are not likely to do better with one of the "well known" functions
1660     such as PJW, K&R, etc.
1661
1662     Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1663   */
1664
1665   char *str = (char *)key;
1666   unsigned int hash = 5381;
1667   int c;
1668
1669   while ((c = *str++))
1670     hash = ((hash << 5) + hash) + c;    /* hash * 33 + c */
1671
1672   return hash;
1673 }
1674
1675 static int keys_are_equal(void *key1, void *key2)
1676 {
1677   return (strEqual((char *)key1, (char *)key2));
1678 }
1679
1680 SetupFileHash *newSetupFileHash()
1681 {
1682   SetupFileHash *new_hash =
1683     create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1684
1685   if (new_hash == NULL)
1686     Error(ERR_EXIT, "create_hashtable() failed -- out of memory");
1687
1688   return new_hash;
1689 }
1690
1691 void freeSetupFileHash(SetupFileHash *hash)
1692 {
1693   if (hash == NULL)
1694     return;
1695
1696   hashtable_destroy(hash, 1);   /* 1 == also free values stored in hash */
1697 }
1698
1699 char *getHashEntry(SetupFileHash *hash, char *token)
1700 {
1701   if (hash == NULL)
1702     return NULL;
1703
1704   return search_hash_entry(hash, token);
1705 }
1706
1707 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1708 {
1709   char *value_copy;
1710
1711   if (hash == NULL)
1712     return;
1713
1714   value_copy = getStringCopy(value);
1715
1716   /* change value; if it does not exist, insert it as new */
1717   if (!change_hash_entry(hash, token, value_copy))
1718     if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1719       Error(ERR_EXIT, "cannot insert into hash -- aborting");
1720 }
1721
1722 char *removeHashEntry(SetupFileHash *hash, char *token)
1723 {
1724   if (hash == NULL)
1725     return NULL;
1726
1727   return remove_hash_entry(hash, token);
1728 }
1729
1730 #if 0
1731 static void printSetupFileHash(SetupFileHash *hash)
1732 {
1733   BEGIN_HASH_ITERATION(hash, itr)
1734   {
1735     printf("token: '%s'\n", HASH_ITERATION_TOKEN(itr));
1736     printf("value: '%s'\n", HASH_ITERATION_VALUE(itr));
1737   }
1738   END_HASH_ITERATION(hash, itr)
1739 }
1740 #endif
1741
1742 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE            1
1743 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING            0
1744 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH             0
1745
1746 static boolean token_value_separator_found = FALSE;
1747 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1748 static boolean token_value_separator_warning = FALSE;
1749 #endif
1750 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1751 static boolean token_already_exists_warning = FALSE;
1752 #endif
1753
1754 static boolean getTokenValueFromSetupLineExt(char *line,
1755                                              char **token_ptr, char **value_ptr,
1756                                              char *filename, char *line_raw,
1757                                              int line_nr,
1758                                              boolean separator_required)
1759 {
1760   static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
1761   char *token, *value, *line_ptr;
1762
1763   /* when externally invoked via ReadTokenValueFromLine(), copy line buffers */
1764   if (line_raw == NULL)
1765   {
1766     strncpy(line_copy, line, MAX_LINE_LEN);
1767     line_copy[MAX_LINE_LEN] = '\0';
1768     line = line_copy;
1769
1770     strcpy(line_raw_copy, line_copy);
1771     line_raw = line_raw_copy;
1772   }
1773
1774   /* cut trailing comment from input line */
1775   for (line_ptr = line; *line_ptr; line_ptr++)
1776   {
1777     if (*line_ptr == '#')
1778     {
1779       *line_ptr = '\0';
1780       break;
1781     }
1782   }
1783
1784   /* cut trailing whitespaces from input line */
1785   for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1786     if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1787       *line_ptr = '\0';
1788
1789   /* ignore empty lines */
1790   if (*line == '\0')
1791     return FALSE;
1792
1793   /* cut leading whitespaces from token */
1794   for (token = line; *token; token++)
1795     if (*token != ' ' && *token != '\t')
1796       break;
1797
1798   /* start with empty value as reliable default */
1799   value = "";
1800
1801   token_value_separator_found = FALSE;
1802
1803   /* find end of token to determine start of value */
1804   for (line_ptr = token; *line_ptr; line_ptr++)
1805   {
1806 #if 1
1807     /* first look for an explicit token/value separator, like ':' or '=' */
1808     if (*line_ptr == ':' || *line_ptr == '=')
1809 #else
1810     if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
1811 #endif
1812     {
1813       *line_ptr = '\0';                 /* terminate token string */
1814       value = line_ptr + 1;             /* set beginning of value */
1815
1816       token_value_separator_found = TRUE;
1817
1818       break;
1819     }
1820   }
1821
1822 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
1823   /* fallback: if no token/value separator found, also allow whitespaces */
1824   if (!token_value_separator_found && !separator_required)
1825   {
1826     for (line_ptr = token; *line_ptr; line_ptr++)
1827     {
1828       if (*line_ptr == ' ' || *line_ptr == '\t')
1829       {
1830         *line_ptr = '\0';               /* terminate token string */
1831         value = line_ptr + 1;           /* set beginning of value */
1832
1833         token_value_separator_found = TRUE;
1834
1835         break;
1836       }
1837     }
1838
1839 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1840     if (token_value_separator_found)
1841     {
1842       if (!token_value_separator_warning)
1843       {
1844         Error(ERR_INFO_LINE, "-");
1845
1846         if (filename != NULL)
1847         {
1848           Error(ERR_WARN, "missing token/value separator(s) in config file:");
1849           Error(ERR_INFO, "- config file: '%s'", filename);
1850         }
1851         else
1852         {
1853           Error(ERR_WARN, "missing token/value separator(s):");
1854         }
1855
1856         token_value_separator_warning = TRUE;
1857       }
1858
1859       if (filename != NULL)
1860         Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
1861       else
1862         Error(ERR_INFO, "- line: '%s'", line_raw);
1863     }
1864 #endif
1865   }
1866 #endif
1867
1868   /* cut trailing whitespaces from token */
1869   for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
1870     if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1871       *line_ptr = '\0';
1872
1873   /* cut leading whitespaces from value */
1874   for (; *value; value++)
1875     if (*value != ' ' && *value != '\t')
1876       break;
1877
1878 #if 0
1879   if (*value == '\0')
1880     value = "true";     /* treat tokens without value as "true" */
1881 #endif
1882
1883   *token_ptr = token;
1884   *value_ptr = value;
1885
1886   return TRUE;
1887 }
1888
1889 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
1890 {
1891   /* while the internal (old) interface does not require a token/value
1892      separator (for downwards compatibility with existing files which
1893      don't use them), it is mandatory for the external (new) interface */
1894
1895   return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
1896 }
1897
1898 #if 1
1899 static boolean loadSetupFileData(void *setup_file_data, char *filename,
1900                                  boolean top_recursion_level, boolean is_hash)
1901 {
1902   static SetupFileHash *include_filename_hash = NULL;
1903   char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
1904   char *token, *value, *line_ptr;
1905   void *insert_ptr = NULL;
1906   boolean read_continued_line = FALSE;
1907   FILE *file;
1908   int line_nr = 0, token_count = 0, include_count = 0;
1909
1910 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1911   token_value_separator_warning = FALSE;
1912 #endif
1913
1914 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1915   token_already_exists_warning = FALSE;
1916 #endif
1917
1918   if (!(file = fopen(filename, MODE_READ)))
1919   {
1920     Error(ERR_WARN, "cannot open configuration file '%s'", filename);
1921
1922     return FALSE;
1923   }
1924
1925   /* use "insert pointer" to store list end for constant insertion complexity */
1926   if (!is_hash)
1927     insert_ptr = setup_file_data;
1928
1929   /* on top invocation, create hash to mark included files (to prevent loops) */
1930   if (top_recursion_level)
1931     include_filename_hash = newSetupFileHash();
1932
1933   /* mark this file as already included (to prevent including it again) */
1934   setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
1935
1936   while (!feof(file))
1937   {
1938     /* read next line of input file */
1939     if (!fgets(line, MAX_LINE_LEN, file))
1940       break;
1941
1942     /* check if line was completely read and is terminated by line break */
1943     if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
1944       line_nr++;
1945
1946     /* cut trailing line break (this can be newline and/or carriage return) */
1947     for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1948       if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
1949         *line_ptr = '\0';
1950
1951     /* copy raw input line for later use (mainly debugging output) */
1952     strcpy(line_raw, line);
1953
1954     if (read_continued_line)
1955     {
1956 #if 0
1957       /* !!! ??? WHY ??? !!! */
1958       /* cut leading whitespaces from input line */
1959       for (line_ptr = line; *line_ptr; line_ptr++)
1960         if (*line_ptr != ' ' && *line_ptr != '\t')
1961           break;
1962 #endif
1963
1964       /* append new line to existing line, if there is enough space */
1965       if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
1966         strcat(previous_line, line_ptr);
1967
1968       strcpy(line, previous_line);      /* copy storage buffer to line */
1969
1970       read_continued_line = FALSE;
1971     }
1972
1973     /* if the last character is '\', continue at next line */
1974     if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
1975     {
1976       line[strlen(line) - 1] = '\0';    /* cut off trailing backslash */
1977       strcpy(previous_line, line);      /* copy line to storage buffer */
1978
1979       read_continued_line = TRUE;
1980
1981       continue;
1982     }
1983
1984     if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
1985                                        line_raw, line_nr, FALSE))
1986       continue;
1987
1988     if (*token)
1989     {
1990       if (strEqual(token, "include"))
1991       {
1992         if (getHashEntry(include_filename_hash, value) == NULL)
1993         {
1994           char *basepath = getBasePath(filename);
1995           char *basename = getBaseName(value);
1996           char *filename_include = getPath2(basepath, basename);
1997
1998 #if 0
1999           Error(ERR_INFO, "[including file '%s']", filename_include);
2000 #endif
2001
2002           loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2003
2004           free(basepath);
2005           free(basename);
2006           free(filename_include);
2007
2008           include_count++;
2009         }
2010         else
2011         {
2012           Error(ERR_WARN, "ignoring already processed file '%s'", value);
2013         }
2014       }
2015       else
2016       {
2017         if (is_hash)
2018         {
2019 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2020           char *old_value =
2021             getHashEntry((SetupFileHash *)setup_file_data, token);
2022
2023           if (old_value != NULL)
2024           {
2025             if (!token_already_exists_warning)
2026             {
2027               Error(ERR_INFO_LINE, "-");
2028               Error(ERR_WARN, "duplicate token(s) found in config file:");
2029               Error(ERR_INFO, "- config file: '%s'", filename);
2030
2031               token_already_exists_warning = TRUE;
2032             }
2033
2034             Error(ERR_INFO, "- token: '%s' (in line %d)", token, line_nr);
2035             Error(ERR_INFO, "  old value: '%s'", old_value);
2036             Error(ERR_INFO, "  new value: '%s'", value);
2037           }
2038 #endif
2039
2040           setHashEntry((SetupFileHash *)setup_file_data, token, value);
2041         }
2042         else
2043         {
2044           insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2045         }
2046
2047         token_count++;
2048       }
2049     }
2050   }
2051
2052   fclose(file);
2053
2054 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2055   if (token_value_separator_warning)
2056     Error(ERR_INFO_LINE, "-");
2057 #endif
2058
2059 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2060   if (token_already_exists_warning)
2061     Error(ERR_INFO_LINE, "-");
2062 #endif
2063
2064   if (token_count == 0 && include_count == 0)
2065     Error(ERR_WARN, "configuration file '%s' is empty", filename);
2066
2067   if (top_recursion_level)
2068     freeSetupFileHash(include_filename_hash);
2069
2070   return TRUE;
2071 }
2072
2073 #else
2074
2075 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2076                                  boolean top_recursion_level, boolean is_hash)
2077 {
2078   static SetupFileHash *include_filename_hash = NULL;
2079   char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2080   char *token, *value, *line_ptr;
2081   void *insert_ptr = NULL;
2082   boolean read_continued_line = FALSE;
2083   FILE *file;
2084   int line_nr = 0;
2085   int token_count = 0;
2086
2087 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2088   token_value_separator_warning = FALSE;
2089 #endif
2090
2091   if (!(file = fopen(filename, MODE_READ)))
2092   {
2093     Error(ERR_WARN, "cannot open configuration file '%s'", filename);
2094
2095     return FALSE;
2096   }
2097
2098   /* use "insert pointer" to store list end for constant insertion complexity */
2099   if (!is_hash)
2100     insert_ptr = setup_file_data;
2101
2102   /* on top invocation, create hash to mark included files (to prevent loops) */
2103   if (top_recursion_level)
2104     include_filename_hash = newSetupFileHash();
2105
2106   /* mark this file as already included (to prevent including it again) */
2107   setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2108
2109   while (!feof(file))
2110   {
2111     /* read next line of input file */
2112     if (!fgets(line, MAX_LINE_LEN, file))
2113       break;
2114
2115     /* check if line was completely read and is terminated by line break */
2116     if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2117       line_nr++;
2118
2119     /* cut trailing line break (this can be newline and/or carriage return) */
2120     for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2121       if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2122         *line_ptr = '\0';
2123
2124     /* copy raw input line for later use (mainly debugging output) */
2125     strcpy(line_raw, line);
2126
2127     if (read_continued_line)
2128     {
2129       /* cut leading whitespaces from input line */
2130       for (line_ptr = line; *line_ptr; line_ptr++)
2131         if (*line_ptr != ' ' && *line_ptr != '\t')
2132           break;
2133
2134       /* append new line to existing line, if there is enough space */
2135       if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2136         strcat(previous_line, line_ptr);
2137
2138       strcpy(line, previous_line);      /* copy storage buffer to line */
2139
2140       read_continued_line = FALSE;
2141     }
2142
2143     /* if the last character is '\', continue at next line */
2144     if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2145     {
2146       line[strlen(line) - 1] = '\0';    /* cut off trailing backslash */
2147       strcpy(previous_line, line);      /* copy line to storage buffer */
2148
2149       read_continued_line = TRUE;
2150
2151       continue;
2152     }
2153
2154     /* cut trailing comment from input line */
2155     for (line_ptr = line; *line_ptr; line_ptr++)
2156     {
2157       if (*line_ptr == '#')
2158       {
2159         *line_ptr = '\0';
2160         break;
2161       }
2162     }
2163
2164     /* cut trailing whitespaces from input line */
2165     for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2166       if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2167         *line_ptr = '\0';
2168
2169     /* ignore empty lines */
2170     if (*line == '\0')
2171       continue;
2172
2173     /* cut leading whitespaces from token */
2174     for (token = line; *token; token++)
2175       if (*token != ' ' && *token != '\t')
2176         break;
2177
2178     /* start with empty value as reliable default */
2179     value = "";
2180
2181     token_value_separator_found = FALSE;
2182
2183     /* find end of token to determine start of value */
2184     for (line_ptr = token; *line_ptr; line_ptr++)
2185     {
2186 #if 1
2187       /* first look for an explicit token/value separator, like ':' or '=' */
2188       if (*line_ptr == ':' || *line_ptr == '=')
2189 #else
2190       if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
2191 #endif
2192       {
2193         *line_ptr = '\0';               /* terminate token string */
2194         value = line_ptr + 1;           /* set beginning of value */
2195
2196         token_value_separator_found = TRUE;
2197
2198         break;
2199       }
2200     }
2201
2202 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
2203     /* fallback: if no token/value separator found, also allow whitespaces */
2204     if (!token_value_separator_found)
2205     {
2206       for (line_ptr = token; *line_ptr; line_ptr++)
2207       {
2208         if (*line_ptr == ' ' || *line_ptr == '\t')
2209         {
2210           *line_ptr = '\0';             /* terminate token string */
2211           value = line_ptr + 1;         /* set beginning of value */
2212
2213           token_value_separator_found = TRUE;
2214
2215           break;
2216         }
2217       }
2218
2219 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2220       if (token_value_separator_found)
2221       {
2222         if (!token_value_separator_warning)
2223         {
2224           Error(ERR_INFO_LINE, "-");
2225           Error(ERR_WARN, "missing token/value separator(s) in config file:");
2226           Error(ERR_INFO, "- config file: '%s'", filename);
2227
2228           token_value_separator_warning = TRUE;
2229         }
2230
2231         Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
2232       }
2233 #endif
2234     }
2235 #endif
2236
2237     /* cut trailing whitespaces from token */
2238     for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2239       if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2240         *line_ptr = '\0';
2241
2242     /* cut leading whitespaces from value */
2243     for (; *value; value++)
2244       if (*value != ' ' && *value != '\t')
2245         break;
2246
2247 #if 0
2248     if (*value == '\0')
2249       value = "true";   /* treat tokens without value as "true" */
2250 #endif
2251
2252     if (*token)
2253     {
2254       if (strEqual(token, "include"))
2255       {
2256         if (getHashEntry(include_filename_hash, value) == NULL)
2257         {
2258           char *basepath = getBasePath(filename);
2259           char *basename = getBaseName(value);
2260           char *filename_include = getPath2(basepath, basename);
2261
2262 #if 0
2263           Error(ERR_INFO, "[including file '%s']", filename_include);
2264 #endif
2265
2266           loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2267
2268           free(basepath);
2269           free(basename);
2270           free(filename_include);
2271         }
2272         else
2273         {
2274           Error(ERR_WARN, "ignoring already processed file '%s'", value);
2275         }
2276       }
2277       else
2278       {
2279         if (is_hash)
2280           setHashEntry((SetupFileHash *)setup_file_data, token, value);
2281         else
2282           insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2283
2284         token_count++;
2285       }
2286     }
2287   }
2288
2289   fclose(file);
2290
2291 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2292   if (token_value_separator_warning)
2293     Error(ERR_INFO_LINE, "-");
2294 #endif
2295
2296   if (token_count == 0)
2297     Error(ERR_WARN, "configuration file '%s' is empty", filename);
2298
2299   if (top_recursion_level)
2300     freeSetupFileHash(include_filename_hash);
2301
2302   return TRUE;
2303 }
2304 #endif
2305
2306 void saveSetupFileHash(SetupFileHash *hash, char *filename)
2307 {
2308   FILE *file;
2309
2310   if (!(file = fopen(filename, MODE_WRITE)))
2311   {
2312     Error(ERR_WARN, "cannot write configuration file '%s'", filename);
2313
2314     return;
2315   }
2316
2317   BEGIN_HASH_ITERATION(hash, itr)
2318   {
2319     fprintf(file, "%s\n", getFormattedSetupEntry(HASH_ITERATION_TOKEN(itr),
2320                                                  HASH_ITERATION_VALUE(itr)));
2321   }
2322   END_HASH_ITERATION(hash, itr)
2323
2324   fclose(file);
2325 }
2326
2327 SetupFileList *loadSetupFileList(char *filename)
2328 {
2329   SetupFileList *setup_file_list = newSetupFileList("", "");
2330   SetupFileList *first_valid_list_entry;
2331
2332   if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2333   {
2334     freeSetupFileList(setup_file_list);
2335
2336     return NULL;
2337   }
2338
2339   first_valid_list_entry = setup_file_list->next;
2340
2341   /* free empty list header */
2342   setup_file_list->next = NULL;
2343   freeSetupFileList(setup_file_list);
2344
2345   return first_valid_list_entry;
2346 }
2347
2348 SetupFileHash *loadSetupFileHash(char *filename)
2349 {
2350   SetupFileHash *setup_file_hash = newSetupFileHash();
2351
2352   if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2353   {
2354     freeSetupFileHash(setup_file_hash);
2355
2356     return NULL;
2357   }
2358
2359   return setup_file_hash;
2360 }
2361
2362 void checkSetupFileHashIdentifier(SetupFileHash *setup_file_hash,
2363                                   char *filename, char *identifier)
2364 {
2365   char *value = getHashEntry(setup_file_hash, TOKEN_STR_FILE_IDENTIFIER);
2366
2367   if (value == NULL)
2368     Error(ERR_WARN, "config file '%s' has no file identifier", filename);
2369   else if (!checkCookieString(value, identifier))
2370     Error(ERR_WARN, "config file '%s' has wrong file identifier", filename);
2371 }
2372
2373
2374 /* ========================================================================= */
2375 /* setup file stuff                                                          */
2376 /* ========================================================================= */
2377
2378 #define TOKEN_STR_LAST_LEVEL_SERIES             "last_level_series"
2379 #define TOKEN_STR_LAST_PLAYED_LEVEL             "last_played_level"
2380 #define TOKEN_STR_HANDICAP_LEVEL                "handicap_level"
2381
2382 /* level directory info */
2383 #define LEVELINFO_TOKEN_IDENTIFIER              0
2384 #define LEVELINFO_TOKEN_NAME                    1
2385 #define LEVELINFO_TOKEN_NAME_SORTING            2
2386 #define LEVELINFO_TOKEN_AUTHOR                  3
2387 #define LEVELINFO_TOKEN_YEAR                    4
2388 #define LEVELINFO_TOKEN_IMPORTED_FROM           5
2389 #define LEVELINFO_TOKEN_IMPORTED_BY             6
2390 #define LEVELINFO_TOKEN_TESTED_BY               7
2391 #define LEVELINFO_TOKEN_LEVELS                  8
2392 #define LEVELINFO_TOKEN_FIRST_LEVEL             9
2393 #define LEVELINFO_TOKEN_SORT_PRIORITY           10
2394 #define LEVELINFO_TOKEN_LATEST_ENGINE           11
2395 #define LEVELINFO_TOKEN_LEVEL_GROUP             12
2396 #define LEVELINFO_TOKEN_READONLY                13
2397 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS        14
2398 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA        15
2399 #define LEVELINFO_TOKEN_GRAPHICS_SET            16
2400 #define LEVELINFO_TOKEN_SOUNDS_SET              17
2401 #define LEVELINFO_TOKEN_MUSIC_SET               18
2402 #define LEVELINFO_TOKEN_FILENAME                19
2403 #define LEVELINFO_TOKEN_FILETYPE                20
2404 #define LEVELINFO_TOKEN_SPECIAL_FLAGS           21
2405 #define LEVELINFO_TOKEN_HANDICAP                22
2406 #define LEVELINFO_TOKEN_SKIP_LEVELS             23
2407
2408 #define NUM_LEVELINFO_TOKENS                    24
2409
2410 static LevelDirTree ldi;
2411
2412 static struct TokenInfo levelinfo_tokens[] =
2413 {
2414   /* level directory info */
2415   { TYPE_STRING,        &ldi.identifier,        "identifier"            },
2416   { TYPE_STRING,        &ldi.name,              "name"                  },
2417   { TYPE_STRING,        &ldi.name_sorting,      "name_sorting"          },
2418   { TYPE_STRING,        &ldi.author,            "author"                },
2419   { TYPE_STRING,        &ldi.year,              "year"                  },
2420   { TYPE_STRING,        &ldi.imported_from,     "imported_from"         },
2421   { TYPE_STRING,        &ldi.imported_by,       "imported_by"           },
2422   { TYPE_STRING,        &ldi.tested_by,         "tested_by"             },
2423   { TYPE_INTEGER,       &ldi.levels,            "levels"                },
2424   { TYPE_INTEGER,       &ldi.first_level,       "first_level"           },
2425   { TYPE_INTEGER,       &ldi.sort_priority,     "sort_priority"         },
2426   { TYPE_BOOLEAN,       &ldi.latest_engine,     "latest_engine"         },
2427   { TYPE_BOOLEAN,       &ldi.level_group,       "level_group"           },
2428   { TYPE_BOOLEAN,       &ldi.readonly,          "readonly"              },
2429   { TYPE_STRING,        &ldi.graphics_set_ecs,  "graphics_set.ecs"      },
2430   { TYPE_STRING,        &ldi.graphics_set_aga,  "graphics_set.aga"      },
2431   { TYPE_STRING,        &ldi.graphics_set,      "graphics_set"          },
2432   { TYPE_STRING,        &ldi.sounds_set,        "sounds_set"            },
2433   { TYPE_STRING,        &ldi.music_set,         "music_set"             },
2434   { TYPE_STRING,        &ldi.level_filename,    "filename"              },
2435   { TYPE_STRING,        &ldi.level_filetype,    "filetype"              },
2436   { TYPE_STRING,        &ldi.special_flags,     "special_flags"         },
2437   { TYPE_BOOLEAN,       &ldi.handicap,          "handicap"              },
2438   { TYPE_BOOLEAN,       &ldi.skip_levels,       "skip_levels"           }
2439 };
2440
2441 static struct TokenInfo artworkinfo_tokens[] =
2442 {
2443   /* artwork directory info */
2444   { TYPE_STRING,        &ldi.identifier,        "identifier"            },
2445   { TYPE_STRING,        &ldi.subdir,            "subdir"                },
2446   { TYPE_STRING,        &ldi.name,              "name"                  },
2447   { TYPE_STRING,        &ldi.name_sorting,      "name_sorting"          },
2448   { TYPE_STRING,        &ldi.author,            "author"                },
2449   { TYPE_INTEGER,       &ldi.sort_priority,     "sort_priority"         },
2450   { TYPE_STRING,        &ldi.basepath,          "basepath"              },
2451   { TYPE_STRING,        &ldi.fullpath,          "fullpath"              },
2452   { TYPE_BOOLEAN,       &ldi.in_user_dir,       "in_user_dir"           },
2453   { TYPE_INTEGER,       &ldi.color,             "color"                 },
2454   { TYPE_STRING,        &ldi.class_desc,        "class_desc"            },
2455
2456   { -1,                 NULL,                   NULL                    },
2457 };
2458
2459 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2460 {
2461   ti->type = type;
2462
2463   ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR    ? &leveldir_first :
2464                   ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2465                   ti->type == TREE_TYPE_SOUNDS_DIR   ? &artwork.snd_first :
2466                   ti->type == TREE_TYPE_MUSIC_DIR    ? &artwork.mus_first :
2467                   NULL);
2468
2469   ti->node_parent = NULL;
2470   ti->node_group = NULL;
2471   ti->next = NULL;
2472
2473   ti->cl_first = -1;
2474   ti->cl_cursor = -1;
2475
2476   ti->subdir = NULL;
2477   ti->fullpath = NULL;
2478   ti->basepath = NULL;
2479   ti->identifier = NULL;
2480   ti->name = getStringCopy(ANONYMOUS_NAME);
2481   ti->name_sorting = NULL;
2482   ti->author = getStringCopy(ANONYMOUS_NAME);
2483   ti->year = NULL;
2484
2485   ti->sort_priority = LEVELCLASS_UNDEFINED;     /* default: least priority */
2486   ti->latest_engine = FALSE;                    /* default: get from level */
2487   ti->parent_link = FALSE;
2488   ti->in_user_dir = FALSE;
2489   ti->user_defined = FALSE;
2490   ti->color = 0;
2491   ti->class_desc = NULL;
2492
2493   ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2494
2495   if (ti->type == TREE_TYPE_LEVEL_DIR)
2496   {
2497     ti->imported_from = NULL;
2498     ti->imported_by = NULL;
2499     ti->tested_by = NULL;
2500
2501     ti->graphics_set_ecs = NULL;
2502     ti->graphics_set_aga = NULL;
2503     ti->graphics_set = NULL;
2504     ti->sounds_set = NULL;
2505     ti->music_set = NULL;
2506     ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2507     ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2508     ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2509
2510     ti->level_filename = NULL;
2511     ti->level_filetype = NULL;
2512
2513     ti->special_flags = NULL;
2514
2515     ti->levels = 0;
2516     ti->first_level = 0;
2517     ti->last_level = 0;
2518     ti->level_group = FALSE;
2519     ti->handicap_level = 0;
2520     ti->readonly = TRUE;
2521     ti->handicap = TRUE;
2522     ti->skip_levels = FALSE;
2523   }
2524 }
2525
2526 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2527 {
2528   if (parent == NULL)
2529   {
2530     Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
2531
2532     setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2533
2534     return;
2535   }
2536
2537   /* copy all values from the parent structure */
2538
2539   ti->type = parent->type;
2540
2541   ti->node_top = parent->node_top;
2542   ti->node_parent = parent;
2543   ti->node_group = NULL;
2544   ti->next = NULL;
2545
2546   ti->cl_first = -1;
2547   ti->cl_cursor = -1;
2548
2549   ti->subdir = NULL;
2550   ti->fullpath = NULL;
2551   ti->basepath = NULL;
2552   ti->identifier = NULL;
2553   ti->name = getStringCopy(ANONYMOUS_NAME);
2554   ti->name_sorting = NULL;
2555   ti->author = getStringCopy(parent->author);
2556   ti->year = getStringCopy(parent->year);
2557
2558   ti->sort_priority = parent->sort_priority;
2559   ti->latest_engine = parent->latest_engine;
2560   ti->parent_link = FALSE;
2561   ti->in_user_dir = parent->in_user_dir;
2562   ti->user_defined = parent->user_defined;
2563   ti->color = parent->color;
2564   ti->class_desc = getStringCopy(parent->class_desc);
2565
2566   ti->infotext = getStringCopy(parent->infotext);
2567
2568   if (ti->type == TREE_TYPE_LEVEL_DIR)
2569   {
2570     ti->imported_from = getStringCopy(parent->imported_from);
2571     ti->imported_by = getStringCopy(parent->imported_by);
2572     ti->tested_by = getStringCopy(parent->tested_by);
2573
2574     ti->graphics_set_ecs = NULL;
2575     ti->graphics_set_aga = NULL;
2576     ti->graphics_set = NULL;
2577     ti->sounds_set = NULL;
2578     ti->music_set = NULL;
2579     ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2580     ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2581     ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2582
2583     ti->level_filename = NULL;
2584     ti->level_filetype = NULL;
2585
2586     ti->special_flags = getStringCopy(parent->special_flags);
2587
2588     ti->levels = 0;
2589     ti->first_level = 0;
2590     ti->last_level = 0;
2591     ti->level_group = FALSE;
2592     ti->handicap_level = 0;
2593     ti->readonly = TRUE;
2594     ti->handicap = TRUE;
2595     ti->skip_levels = FALSE;
2596   }
2597 }
2598
2599 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2600 {
2601   TreeInfo *ti_copy = newTreeInfo();
2602
2603   /* copy all values from the original structure */
2604
2605   ti_copy->type                 = ti->type;
2606
2607   ti_copy->node_top             = ti->node_top;
2608   ti_copy->node_parent          = ti->node_parent;
2609   ti_copy->node_group           = ti->node_group;
2610   ti_copy->next                 = ti->next;
2611
2612   ti_copy->cl_first             = ti->cl_first;
2613   ti_copy->cl_cursor            = ti->cl_cursor;
2614
2615   ti_copy->subdir               = getStringCopy(ti->subdir);
2616   ti_copy->fullpath             = getStringCopy(ti->fullpath);
2617   ti_copy->basepath             = getStringCopy(ti->basepath);
2618   ti_copy->identifier           = getStringCopy(ti->identifier);
2619   ti_copy->name                 = getStringCopy(ti->name);
2620   ti_copy->name_sorting         = getStringCopy(ti->name_sorting);
2621   ti_copy->author               = getStringCopy(ti->author);
2622   ti_copy->year                 = getStringCopy(ti->year);
2623   ti_copy->imported_from        = getStringCopy(ti->imported_from);
2624   ti_copy->imported_by          = getStringCopy(ti->imported_by);
2625   ti_copy->tested_by            = getStringCopy(ti->tested_by);
2626
2627   ti_copy->graphics_set_ecs     = getStringCopy(ti->graphics_set_ecs);
2628   ti_copy->graphics_set_aga     = getStringCopy(ti->graphics_set_aga);
2629   ti_copy->graphics_set         = getStringCopy(ti->graphics_set);
2630   ti_copy->sounds_set           = getStringCopy(ti->sounds_set);
2631   ti_copy->music_set            = getStringCopy(ti->music_set);
2632   ti_copy->graphics_path        = getStringCopy(ti->graphics_path);
2633   ti_copy->sounds_path          = getStringCopy(ti->sounds_path);
2634   ti_copy->music_path           = getStringCopy(ti->music_path);
2635
2636   ti_copy->level_filename       = getStringCopy(ti->level_filename);
2637   ti_copy->level_filetype       = getStringCopy(ti->level_filetype);
2638
2639   ti_copy->special_flags        = getStringCopy(ti->special_flags);
2640
2641   ti_copy->levels               = ti->levels;
2642   ti_copy->first_level          = ti->first_level;
2643   ti_copy->last_level           = ti->last_level;
2644   ti_copy->sort_priority        = ti->sort_priority;
2645
2646   ti_copy->latest_engine        = ti->latest_engine;
2647
2648   ti_copy->level_group          = ti->level_group;
2649   ti_copy->parent_link          = ti->parent_link;
2650   ti_copy->in_user_dir          = ti->in_user_dir;
2651   ti_copy->user_defined         = ti->user_defined;
2652   ti_copy->readonly             = ti->readonly;
2653   ti_copy->handicap             = ti->handicap;
2654   ti_copy->skip_levels          = ti->skip_levels;
2655
2656   ti_copy->color                = ti->color;
2657   ti_copy->class_desc           = getStringCopy(ti->class_desc);
2658   ti_copy->handicap_level       = ti->handicap_level;
2659
2660   ti_copy->infotext             = getStringCopy(ti->infotext);
2661
2662   return ti_copy;
2663 }
2664
2665 static void freeTreeInfo(TreeInfo *ti)
2666 {
2667   if (ti == NULL)
2668     return;
2669
2670   checked_free(ti->subdir);
2671   checked_free(ti->fullpath);
2672   checked_free(ti->basepath);
2673   checked_free(ti->identifier);
2674
2675   checked_free(ti->name);
2676   checked_free(ti->name_sorting);
2677   checked_free(ti->author);
2678   checked_free(ti->year);
2679
2680   checked_free(ti->class_desc);
2681
2682   checked_free(ti->infotext);
2683
2684   if (ti->type == TREE_TYPE_LEVEL_DIR)
2685   {
2686     checked_free(ti->imported_from);
2687     checked_free(ti->imported_by);
2688     checked_free(ti->tested_by);
2689
2690     checked_free(ti->graphics_set_ecs);
2691     checked_free(ti->graphics_set_aga);
2692     checked_free(ti->graphics_set);
2693     checked_free(ti->sounds_set);
2694     checked_free(ti->music_set);
2695
2696     checked_free(ti->graphics_path);
2697     checked_free(ti->sounds_path);
2698     checked_free(ti->music_path);
2699
2700     checked_free(ti->level_filename);
2701     checked_free(ti->level_filetype);
2702
2703     checked_free(ti->special_flags);
2704   }
2705
2706   checked_free(ti);
2707 }
2708
2709 void setSetupInfo(struct TokenInfo *token_info,
2710                   int token_nr, char *token_value)
2711 {
2712   int token_type = token_info[token_nr].type;
2713   void *setup_value = token_info[token_nr].value;
2714
2715   if (token_value == NULL)
2716     return;
2717
2718   /* set setup field to corresponding token value */
2719   switch (token_type)
2720   {
2721     case TYPE_BOOLEAN:
2722     case TYPE_SWITCH:
2723       *(boolean *)setup_value = get_boolean_from_string(token_value);
2724       break;
2725
2726     case TYPE_SWITCH3:
2727       *(int *)setup_value = get_switch3_from_string(token_value);
2728       break;
2729
2730     case TYPE_KEY:
2731       *(Key *)setup_value = getKeyFromKeyName(token_value);
2732       break;
2733
2734     case TYPE_KEY_X11:
2735       *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2736       break;
2737
2738     case TYPE_INTEGER:
2739       *(int *)setup_value = get_integer_from_string(token_value);
2740       break;
2741
2742     case TYPE_STRING:
2743       checked_free(*(char **)setup_value);
2744       *(char **)setup_value = getStringCopy(token_value);
2745       break;
2746
2747     default:
2748       break;
2749   }
2750 }
2751
2752 static int compareTreeInfoEntries(const void *object1, const void *object2)
2753 {
2754   const TreeInfo *entry1 = *((TreeInfo **)object1);
2755   const TreeInfo *entry2 = *((TreeInfo **)object2);
2756   int class_sorting1, class_sorting2;
2757   int compare_result;
2758
2759   if (entry1->type == TREE_TYPE_LEVEL_DIR)
2760   {
2761     class_sorting1 = LEVELSORTING(entry1);
2762     class_sorting2 = LEVELSORTING(entry2);
2763   }
2764   else
2765   {
2766     class_sorting1 = ARTWORKSORTING(entry1);
2767     class_sorting2 = ARTWORKSORTING(entry2);
2768   }
2769
2770   if (entry1->parent_link || entry2->parent_link)
2771     compare_result = (entry1->parent_link ? -1 : +1);
2772   else if (entry1->sort_priority == entry2->sort_priority)
2773   {
2774     char *name1 = getStringToLower(entry1->name_sorting);
2775     char *name2 = getStringToLower(entry2->name_sorting);
2776
2777     compare_result = strcmp(name1, name2);
2778
2779     free(name1);
2780     free(name2);
2781   }
2782   else if (class_sorting1 == class_sorting2)
2783     compare_result = entry1->sort_priority - entry2->sort_priority;
2784   else
2785     compare_result = class_sorting1 - class_sorting2;
2786
2787   return compare_result;
2788 }
2789
2790 static void createParentTreeInfoNode(TreeInfo *node_parent)
2791 {
2792   TreeInfo *ti_new;
2793
2794   if (node_parent == NULL)
2795     return;
2796
2797   ti_new = newTreeInfo();
2798   setTreeInfoToDefaults(ti_new, node_parent->type);
2799
2800   ti_new->node_parent = node_parent;
2801   ti_new->parent_link = TRUE;
2802
2803   setString(&ti_new->identifier, node_parent->identifier);
2804   setString(&ti_new->name, ".. (parent directory)");
2805   setString(&ti_new->name_sorting, ti_new->name);
2806
2807   setString(&ti_new->subdir, "..");
2808   setString(&ti_new->fullpath, node_parent->fullpath);
2809
2810   ti_new->sort_priority = node_parent->sort_priority;
2811   ti_new->latest_engine = node_parent->latest_engine;
2812
2813   setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2814
2815   pushTreeInfo(&node_parent->node_group, ti_new);
2816 }
2817
2818
2819 /* -------------------------------------------------------------------------- */
2820 /* functions for handling level and custom artwork info cache                 */
2821 /* -------------------------------------------------------------------------- */
2822
2823 static void LoadArtworkInfoCache()
2824 {
2825   InitCacheDirectory();
2826
2827   if (artworkinfo_cache_old == NULL)
2828   {
2829     char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2830
2831     /* try to load artwork info hash from already existing cache file */
2832     artworkinfo_cache_old = loadSetupFileHash(filename);
2833
2834     /* if no artwork info cache file was found, start with empty hash */
2835     if (artworkinfo_cache_old == NULL)
2836       artworkinfo_cache_old = newSetupFileHash();
2837
2838     free(filename);
2839   }
2840
2841   if (artworkinfo_cache_new == NULL)
2842     artworkinfo_cache_new = newSetupFileHash();
2843 }
2844
2845 static void SaveArtworkInfoCache()
2846 {
2847   char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2848
2849   InitCacheDirectory();
2850
2851   saveSetupFileHash(artworkinfo_cache_new, filename);
2852
2853   free(filename);
2854 }
2855
2856 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
2857 {
2858   static char *prefix = NULL;
2859
2860   checked_free(prefix);
2861
2862   prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
2863
2864   return prefix;
2865 }
2866
2867 /* (identical to above function, but separate string buffer needed -- nasty) */
2868 static char *getCacheToken(char *prefix, char *suffix)
2869 {
2870   static char *token = NULL;
2871
2872   checked_free(token);
2873
2874   token = getStringCat2WithSeparator(prefix, suffix, ".");
2875
2876   return token;
2877 }
2878
2879 static char *getFileTimestampString(char *filename)
2880 {
2881 #if 1
2882   return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
2883 #else
2884   struct stat file_status;
2885
2886   if (stat(filename, &file_status) != 0)        /* cannot stat file */
2887     return getStringCopy(i_to_a(0));
2888
2889   return getStringCopy(i_to_a(file_status.st_mtime));
2890 #endif
2891 }
2892
2893 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
2894 {
2895   struct stat file_status;
2896
2897   if (timestamp_string == NULL)
2898     return TRUE;
2899
2900   if (stat(filename, &file_status) != 0)        /* cannot stat file */
2901     return TRUE;
2902
2903   return (file_status.st_mtime != atoi(timestamp_string));
2904 }
2905
2906 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
2907 {
2908   char *identifier = level_node->subdir;
2909   char *type_string = ARTWORK_DIRECTORY(type);
2910   char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2911   char *token_main = getCacheToken(token_prefix, "CACHED");
2912   char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2913   boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
2914   TreeInfo *artwork_info = NULL;
2915
2916   if (!use_artworkinfo_cache)
2917     return NULL;
2918
2919   if (cached)
2920   {
2921     int i;
2922
2923     artwork_info = newTreeInfo();
2924     setTreeInfoToDefaults(artwork_info, type);
2925
2926     /* set all structure fields according to the token/value pairs */
2927     ldi = *artwork_info;
2928     for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2929     {
2930       char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2931       char *value = getHashEntry(artworkinfo_cache_old, token);
2932
2933       setSetupInfo(artworkinfo_tokens, i, value);
2934
2935       /* check if cache entry for this item is invalid or incomplete */
2936       if (value == NULL)
2937       {
2938 #if 1
2939         Error(ERR_WARN, "cache entry '%s' invalid", token);
2940 #endif
2941
2942         cached = FALSE;
2943       }
2944     }
2945
2946     *artwork_info = ldi;
2947   }
2948
2949   if (cached)
2950   {
2951     char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2952                                         LEVELINFO_FILENAME);
2953     char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2954                                           ARTWORKINFO_FILENAME(type));
2955
2956     /* check if corresponding "levelinfo.conf" file has changed */
2957     token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2958     cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2959
2960     if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
2961       cached = FALSE;
2962
2963     /* check if corresponding "<artworkinfo>.conf" file has changed */
2964     token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2965     cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2966
2967     if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
2968       cached = FALSE;
2969
2970 #if 0
2971     if (!cached)
2972       printf("::: '%s': INVALIDATED FROM CACHE BY TIMESTAMP\n", identifier);
2973 #endif
2974
2975     checked_free(filename_levelinfo);
2976     checked_free(filename_artworkinfo);
2977   }
2978
2979   if (!cached && artwork_info != NULL)
2980   {
2981     freeTreeInfo(artwork_info);
2982
2983     return NULL;
2984   }
2985
2986   return artwork_info;
2987 }
2988
2989 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
2990                                      LevelDirTree *level_node, int type)
2991 {
2992   char *identifier = level_node->subdir;
2993   char *type_string = ARTWORK_DIRECTORY(type);
2994   char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2995   char *token_main = getCacheToken(token_prefix, "CACHED");
2996   boolean set_cache_timestamps = TRUE;
2997   int i;
2998
2999   setHashEntry(artworkinfo_cache_new, token_main, "true");
3000
3001   if (set_cache_timestamps)
3002   {
3003     char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3004                                         LEVELINFO_FILENAME);
3005     char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3006                                           ARTWORKINFO_FILENAME(type));
3007     char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
3008     char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
3009
3010     token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3011     setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
3012
3013     token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3014     setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
3015
3016     checked_free(filename_levelinfo);
3017     checked_free(filename_artworkinfo);
3018     checked_free(timestamp_levelinfo);
3019     checked_free(timestamp_artworkinfo);
3020   }
3021
3022   ldi = *artwork_info;
3023   for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3024   {
3025     char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3026     char *value = getSetupValue(artworkinfo_tokens[i].type,
3027                                 artworkinfo_tokens[i].value);
3028     if (value != NULL)
3029       setHashEntry(artworkinfo_cache_new, token, value);
3030   }
3031 }
3032
3033
3034 /* -------------------------------------------------------------------------- */
3035 /* functions for loading level info and custom artwork info                   */
3036 /* -------------------------------------------------------------------------- */
3037
3038 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
3039 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3040
3041 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3042                                           TreeInfo *node_parent,
3043                                           char *level_directory,
3044                                           char *directory_name)
3045 {
3046 #if 0
3047   static unsigned long progress_delay = 0;
3048   unsigned long progress_delay_value = 100;     /* (in milliseconds) */
3049 #endif
3050   char *directory_path = getPath2(level_directory, directory_name);
3051   char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3052   SetupFileHash *setup_file_hash;
3053   LevelDirTree *leveldir_new = NULL;
3054   int i;
3055
3056   /* unless debugging, silently ignore directories without "levelinfo.conf" */
3057   if (!options.debug && !fileExists(filename))
3058   {
3059     free(directory_path);
3060     free(filename);
3061
3062     return FALSE;
3063   }
3064
3065   setup_file_hash = loadSetupFileHash(filename);
3066
3067   if (setup_file_hash == NULL)
3068   {
3069     Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
3070
3071     free(directory_path);
3072     free(filename);
3073
3074     return FALSE;
3075   }
3076
3077   leveldir_new = newTreeInfo();
3078
3079   if (node_parent)
3080     setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3081   else
3082     setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3083
3084   leveldir_new->subdir = getStringCopy(directory_name);
3085
3086   checkSetupFileHashIdentifier(setup_file_hash, filename,
3087                                getCookie("LEVELINFO"));
3088
3089   /* set all structure fields according to the token/value pairs */
3090   ldi = *leveldir_new;
3091   for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3092     setSetupInfo(levelinfo_tokens, i,
3093                  getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3094   *leveldir_new = ldi;
3095
3096   if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3097     setString(&leveldir_new->name, leveldir_new->subdir);
3098
3099   if (leveldir_new->identifier == NULL)
3100     leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3101
3102   if (leveldir_new->name_sorting == NULL)
3103     leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3104
3105   if (node_parent == NULL)              /* top level group */
3106   {
3107     leveldir_new->basepath = getStringCopy(level_directory);
3108     leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3109   }
3110   else                                  /* sub level group */
3111   {
3112     leveldir_new->basepath = getStringCopy(node_parent->basepath);
3113     leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3114   }
3115
3116 #if 0
3117   if (leveldir_new->levels < 1)
3118     leveldir_new->levels = 1;
3119 #endif
3120
3121   leveldir_new->last_level =
3122     leveldir_new->first_level + leveldir_new->levels - 1;
3123
3124   leveldir_new->in_user_dir =
3125     (!strEqual(leveldir_new->basepath, options.level_directory));
3126
3127   /* adjust some settings if user's private level directory was detected */
3128   if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3129       leveldir_new->in_user_dir &&
3130       (strEqual(leveldir_new->subdir, getLoginName()) ||
3131        strEqual(leveldir_new->name,   getLoginName()) ||
3132        strEqual(leveldir_new->author, getRealName())))
3133   {
3134     leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3135     leveldir_new->readonly = FALSE;
3136   }
3137
3138   leveldir_new->user_defined =
3139     (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3140
3141   leveldir_new->color = LEVELCOLOR(leveldir_new);
3142
3143   setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3144
3145   leveldir_new->handicap_level =        /* set handicap to default value */
3146     (leveldir_new->user_defined || !leveldir_new->handicap ?
3147      leveldir_new->last_level : leveldir_new->first_level);
3148
3149 #if 1
3150 #if 1
3151   DrawInitTextExt(leveldir_new->name, 150, FC_YELLOW,
3152                   leveldir_new->level_group);
3153 #else
3154   if (leveldir_new->level_group ||
3155       DelayReached(&progress_delay, progress_delay_value))
3156     DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3157 #endif
3158 #else
3159   DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3160 #endif
3161
3162 #if 0
3163   /* !!! don't skip sets without levels (else artwork base sets are missing) */
3164 #if 1
3165   if (leveldir_new->levels < 1 && !leveldir_new->level_group)
3166   {
3167     /* skip level sets without levels (which are probably artwork base sets) */
3168
3169     freeSetupFileHash(setup_file_hash);
3170     free(directory_path);
3171     free(filename);
3172
3173     return FALSE;
3174   }
3175 #endif
3176 #endif
3177
3178   pushTreeInfo(node_first, leveldir_new);
3179
3180   freeSetupFileHash(setup_file_hash);
3181
3182   if (leveldir_new->level_group)
3183   {
3184     /* create node to link back to current level directory */
3185     createParentTreeInfoNode(leveldir_new);
3186
3187     /* recursively step into sub-directory and look for more level series */
3188     LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3189                               leveldir_new, directory_path);
3190   }
3191
3192   free(directory_path);
3193   free(filename);
3194
3195   return TRUE;
3196 }
3197
3198 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3199                                       TreeInfo *node_parent,
3200                                       char *level_directory)
3201 {
3202   DIR *dir;
3203   struct dirent *dir_entry;
3204   boolean valid_entry_found = FALSE;
3205
3206   if ((dir = opendir(level_directory)) == NULL)
3207   {
3208     Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3209     return;
3210   }
3211
3212   while ((dir_entry = readdir(dir)) != NULL)    /* loop until last dir entry */
3213   {
3214     struct stat file_status;
3215     char *directory_name = dir_entry->d_name;
3216     char *directory_path = getPath2(level_directory, directory_name);
3217
3218     /* skip entries for current and parent directory */
3219     if (strEqual(directory_name, ".") ||
3220         strEqual(directory_name, ".."))
3221     {
3222       free(directory_path);
3223       continue;
3224     }
3225
3226     /* find out if directory entry is itself a directory */
3227     if (stat(directory_path, &file_status) != 0 ||      /* cannot stat file */
3228         (file_status.st_mode & S_IFMT) != S_IFDIR)      /* not a directory */
3229     {
3230       free(directory_path);
3231       continue;
3232     }
3233
3234     free(directory_path);
3235
3236     if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3237         strEqual(directory_name, SOUNDS_DIRECTORY) ||
3238         strEqual(directory_name, MUSIC_DIRECTORY))
3239       continue;
3240
3241     valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3242                                                     level_directory,
3243                                                     directory_name);
3244   }
3245
3246   closedir(dir);
3247
3248   /* special case: top level directory may directly contain "levelinfo.conf" */
3249   if (node_parent == NULL && !valid_entry_found)
3250   {
3251     /* check if this directory directly contains a file "levelinfo.conf" */
3252     valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3253                                                     level_directory, ".");
3254   }
3255
3256   if (!valid_entry_found)
3257     Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
3258           level_directory);
3259 }
3260
3261 boolean AdjustGraphicsForEMC()
3262 {
3263   boolean settings_changed = FALSE;
3264
3265   settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3266   settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3267
3268   return settings_changed;
3269 }
3270
3271 void LoadLevelInfo()
3272 {
3273   InitUserLevelDirectory(getLoginName());
3274
3275   DrawInitText("Loading level series", 120, FC_GREEN);
3276
3277   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3278   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3279
3280   /* after loading all level set information, clone the level directory tree
3281      and remove all level sets without levels (these may still contain artwork
3282      to be offered in the setup menu as "custom artwork", and are therefore
3283      checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3284   leveldir_first_all = leveldir_first;
3285   cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3286
3287   AdjustGraphicsForEMC();
3288
3289   /* before sorting, the first entries will be from the user directory */
3290   leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3291
3292   if (leveldir_first == NULL)
3293     Error(ERR_EXIT, "cannot find any valid level series in any directory");
3294
3295   sortTreeInfo(&leveldir_first);
3296
3297 #if 0
3298   dumpTreeInfo(leveldir_first, 0);
3299 #endif
3300 }
3301
3302 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3303                                               TreeInfo *node_parent,
3304                                               char *base_directory,
3305                                               char *directory_name, int type)
3306 {
3307   char *directory_path = getPath2(base_directory, directory_name);
3308   char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3309   SetupFileHash *setup_file_hash = NULL;
3310   TreeInfo *artwork_new = NULL;
3311   int i;
3312
3313   if (fileExists(filename))
3314     setup_file_hash = loadSetupFileHash(filename);
3315
3316   if (setup_file_hash == NULL)  /* no config file -- look for artwork files */
3317   {
3318     DIR *dir;
3319     struct dirent *dir_entry;
3320     boolean valid_file_found = FALSE;
3321
3322     if ((dir = opendir(directory_path)) != NULL)
3323     {
3324       while ((dir_entry = readdir(dir)) != NULL)
3325       {
3326         char *entry_name = dir_entry->d_name;
3327
3328         if (FileIsArtworkType(entry_name, type))
3329         {
3330           valid_file_found = TRUE;
3331           break;
3332         }
3333       }
3334
3335       closedir(dir);
3336     }
3337
3338     if (!valid_file_found)
3339     {
3340       if (!strEqual(directory_name, "."))
3341         Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
3342
3343       free(directory_path);
3344       free(filename);
3345
3346       return FALSE;
3347     }
3348   }
3349
3350   artwork_new = newTreeInfo();
3351
3352   if (node_parent)
3353     setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3354   else
3355     setTreeInfoToDefaults(artwork_new, type);
3356
3357   artwork_new->subdir = getStringCopy(directory_name);
3358
3359   if (setup_file_hash)  /* (before defining ".color" and ".class_desc") */
3360   {
3361 #if 0
3362     checkSetupFileHashIdentifier(setup_file_hash, filename, getCookie("..."));
3363 #endif
3364
3365     /* set all structure fields according to the token/value pairs */
3366     ldi = *artwork_new;
3367     for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3368       setSetupInfo(levelinfo_tokens, i,
3369                    getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3370     *artwork_new = ldi;
3371
3372     if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3373       setString(&artwork_new->name, artwork_new->subdir);
3374
3375     if (artwork_new->identifier == NULL)
3376       artwork_new->identifier = getStringCopy(artwork_new->subdir);
3377
3378     if (artwork_new->name_sorting == NULL)
3379       artwork_new->name_sorting = getStringCopy(artwork_new->name);
3380   }
3381
3382   if (node_parent == NULL)              /* top level group */
3383   {
3384     artwork_new->basepath = getStringCopy(base_directory);
3385     artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3386   }
3387   else                                  /* sub level group */
3388   {
3389     artwork_new->basepath = getStringCopy(node_parent->basepath);
3390     artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3391   }
3392
3393   artwork_new->in_user_dir =
3394     (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3395
3396   /* (may use ".sort_priority" from "setup_file_hash" above) */
3397   artwork_new->color = ARTWORKCOLOR(artwork_new);
3398
3399   setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3400
3401   if (setup_file_hash == NULL)  /* (after determining ".user_defined") */
3402   {
3403     if (strEqual(artwork_new->subdir, "."))
3404     {
3405       if (artwork_new->user_defined)
3406       {
3407         setString(&artwork_new->identifier, "private");
3408         artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3409       }
3410       else
3411       {
3412         setString(&artwork_new->identifier, "classic");
3413         artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3414       }
3415
3416       /* set to new values after changing ".sort_priority" */
3417       artwork_new->color = ARTWORKCOLOR(artwork_new);
3418
3419       setString(&artwork_new->class_desc,
3420                 getLevelClassDescription(artwork_new));
3421     }
3422     else
3423     {
3424       setString(&artwork_new->identifier, artwork_new->subdir);
3425     }
3426
3427     setString(&artwork_new->name, artwork_new->identifier);
3428     setString(&artwork_new->name_sorting, artwork_new->name);
3429   }
3430
3431 #if 0
3432   DrawInitText(artwork_new->name, 150, FC_YELLOW);
3433 #endif
3434
3435   pushTreeInfo(node_first, artwork_new);
3436
3437   freeSetupFileHash(setup_file_hash);
3438
3439   free(directory_path);
3440   free(filename);
3441
3442   return TRUE;
3443 }
3444
3445 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3446                                           TreeInfo *node_parent,
3447                                           char *base_directory, int type)
3448 {
3449   DIR *dir;
3450   struct dirent *dir_entry;
3451   boolean valid_entry_found = FALSE;
3452
3453   if ((dir = opendir(base_directory)) == NULL)
3454   {
3455     /* display error if directory is main "options.graphics_directory" etc. */
3456     if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3457       Error(ERR_WARN, "cannot read directory '%s'", base_directory);
3458
3459     return;
3460   }
3461
3462   while ((dir_entry = readdir(dir)) != NULL)    /* loop until last dir entry */
3463   {
3464     struct stat file_status;
3465     char *directory_name = dir_entry->d_name;
3466     char *directory_path = getPath2(base_directory, directory_name);
3467
3468     /* skip directory entries for current and parent directory */
3469     if (strEqual(directory_name, ".") ||
3470         strEqual(directory_name, ".."))
3471     {
3472       free(directory_path);
3473       continue;
3474     }
3475
3476     /* skip directory entries which are not a directory or are not accessible */
3477     if (stat(directory_path, &file_status) != 0 ||      /* cannot stat file */
3478         (file_status.st_mode & S_IFMT) != S_IFDIR)      /* not a directory */
3479     {
3480       free(directory_path);
3481       continue;
3482     }
3483
3484     free(directory_path);
3485
3486     /* check if this directory contains artwork with or without config file */
3487     valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3488                                                         base_directory,
3489                                                         directory_name, type);
3490   }
3491
3492   closedir(dir);
3493
3494   /* check if this directory directly contains artwork itself */
3495   valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3496                                                       base_directory, ".",
3497                                                       type);
3498   if (!valid_entry_found)
3499     Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
3500           base_directory);
3501 }
3502
3503 static TreeInfo *getDummyArtworkInfo(int type)
3504 {
3505   /* this is only needed when there is completely no artwork available */
3506   TreeInfo *artwork_new = newTreeInfo();
3507
3508   setTreeInfoToDefaults(artwork_new, type);
3509
3510   setString(&artwork_new->subdir,   UNDEFINED_FILENAME);
3511   setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3512   setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3513
3514   setString(&artwork_new->identifier,   UNDEFINED_FILENAME);
3515   setString(&artwork_new->name,         UNDEFINED_FILENAME);
3516   setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3517
3518   return artwork_new;
3519 }
3520
3521 void LoadArtworkInfo()
3522 {
3523   LoadArtworkInfoCache();
3524
3525   DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3526
3527   LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3528                                 options.graphics_directory,
3529                                 TREE_TYPE_GRAPHICS_DIR);
3530   LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3531                                 getUserGraphicsDir(),
3532                                 TREE_TYPE_GRAPHICS_DIR);
3533
3534   LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3535                                 options.sounds_directory,
3536                                 TREE_TYPE_SOUNDS_DIR);
3537   LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3538                                 getUserSoundsDir(),
3539                                 TREE_TYPE_SOUNDS_DIR);
3540
3541   LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3542                                 options.music_directory,
3543                                 TREE_TYPE_MUSIC_DIR);
3544   LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3545                                 getUserMusicDir(),
3546                                 TREE_TYPE_MUSIC_DIR);
3547
3548   if (artwork.gfx_first == NULL)
3549     artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3550   if (artwork.snd_first == NULL)
3551     artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3552   if (artwork.mus_first == NULL)
3553     artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3554
3555   /* before sorting, the first entries will be from the user directory */
3556   artwork.gfx_current =
3557     getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3558   if (artwork.gfx_current == NULL)
3559     artwork.gfx_current =
3560       getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3561   if (artwork.gfx_current == NULL)
3562     artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3563
3564   artwork.snd_current =
3565     getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3566   if (artwork.snd_current == NULL)
3567     artwork.snd_current =
3568       getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3569   if (artwork.snd_current == NULL)
3570     artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3571
3572   artwork.mus_current =
3573     getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3574   if (artwork.mus_current == NULL)
3575     artwork.mus_current =
3576       getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3577   if (artwork.mus_current == NULL)
3578     artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3579
3580   artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3581   artwork.snd_current_identifier = artwork.snd_current->identifier;
3582   artwork.mus_current_identifier = artwork.mus_current->identifier;
3583
3584 #if 0
3585   printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
3586   printf("sounds set == %s\n\n", artwork.snd_current_identifier);
3587   printf("music set == %s\n\n", artwork.mus_current_identifier);
3588 #endif
3589
3590   sortTreeInfo(&artwork.gfx_first);
3591   sortTreeInfo(&artwork.snd_first);
3592   sortTreeInfo(&artwork.mus_first);
3593
3594 #if 0
3595   dumpTreeInfo(artwork.gfx_first, 0);
3596   dumpTreeInfo(artwork.snd_first, 0);
3597   dumpTreeInfo(artwork.mus_first, 0);
3598 #endif
3599 }
3600
3601 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
3602                                   LevelDirTree *level_node)
3603 {
3604 #if 0
3605   static unsigned long progress_delay = 0;
3606   unsigned long progress_delay_value = 100;     /* (in milliseconds) */
3607 #endif
3608   int type = (*artwork_node)->type;
3609
3610   /* recursively check all level directories for artwork sub-directories */
3611
3612   while (level_node)
3613   {
3614     /* check all tree entries for artwork, but skip parent link entries */
3615     if (!level_node->parent_link)
3616     {
3617       TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3618       boolean cached = (artwork_new != NULL);
3619
3620       if (cached)
3621       {
3622         pushTreeInfo(artwork_node, artwork_new);
3623       }
3624       else
3625       {
3626         TreeInfo *topnode_last = *artwork_node;
3627         char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3628                               ARTWORK_DIRECTORY(type));
3629
3630         LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3631
3632         if (topnode_last != *artwork_node)      /* check for newly added node */
3633         {
3634           artwork_new = *artwork_node;
3635
3636           setString(&artwork_new->identifier,   level_node->subdir);
3637           setString(&artwork_new->name,         level_node->name);
3638           setString(&artwork_new->name_sorting, level_node->name_sorting);
3639
3640           artwork_new->sort_priority = level_node->sort_priority;
3641           artwork_new->color = LEVELCOLOR(artwork_new);
3642         }
3643
3644         free(path);
3645       }
3646
3647       /* insert artwork info (from old cache or filesystem) into new cache */
3648       if (artwork_new != NULL)
3649         setArtworkInfoCacheEntry(artwork_new, level_node, type);
3650     }
3651
3652 #if 1
3653     DrawInitTextExt(level_node->name, 150, FC_YELLOW,
3654                     level_node->level_group);
3655 #else
3656     if (level_node->level_group ||
3657         DelayReached(&progress_delay, progress_delay_value))
3658       DrawInitText(level_node->name, 150, FC_YELLOW);
3659 #endif
3660
3661     if (level_node->node_group != NULL)
3662       LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
3663
3664     level_node = level_node->next;
3665   }
3666 }
3667
3668 void LoadLevelArtworkInfo()
3669 {
3670   DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
3671
3672   LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
3673   LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
3674   LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
3675
3676   SaveArtworkInfoCache();
3677
3678   /* needed for reloading level artwork not known at ealier stage */
3679
3680   if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
3681   {
3682     artwork.gfx_current =
3683       getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3684     if (artwork.gfx_current == NULL)
3685       artwork.gfx_current =
3686         getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3687     if (artwork.gfx_current == NULL)
3688       artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3689   }
3690
3691   if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
3692   {
3693     artwork.snd_current =
3694       getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3695     if (artwork.snd_current == NULL)
3696       artwork.snd_current =
3697         getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3698     if (artwork.snd_current == NULL)
3699       artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3700   }
3701
3702   if (!strEqual(artwork.mus_current_identifier, setup.music_set))
3703   {
3704     artwork.mus_current =
3705       getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3706     if (artwork.mus_current == NULL)
3707       artwork.mus_current =
3708         getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3709     if (artwork.mus_current == NULL)
3710       artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3711   }
3712
3713   sortTreeInfo(&artwork.gfx_first);
3714   sortTreeInfo(&artwork.snd_first);
3715   sortTreeInfo(&artwork.mus_first);
3716
3717 #if 0
3718   dumpTreeInfo(artwork.gfx_first, 0);
3719   dumpTreeInfo(artwork.snd_first, 0);
3720   dumpTreeInfo(artwork.mus_first, 0);
3721 #endif
3722 }
3723
3724 static void SaveUserLevelInfo()
3725 {
3726   LevelDirTree *level_info;
3727   char *filename;
3728   FILE *file;
3729   int i;
3730
3731   filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
3732
3733   if (!(file = fopen(filename, MODE_WRITE)))
3734   {
3735     Error(ERR_WARN, "cannot write level info file '%s'", filename);
3736     free(filename);
3737     return;
3738   }
3739
3740   level_info = newTreeInfo();
3741
3742   /* always start with reliable default values */
3743   setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
3744
3745   setString(&level_info->name, getLoginName());
3746   setString(&level_info->author, getRealName());
3747   level_info->levels = 100;
3748   level_info->first_level = 1;
3749
3750   token_value_position = TOKEN_VALUE_POSITION_SHORT;
3751
3752   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3753                                                  getCookie("LEVELINFO")));
3754
3755   ldi = *level_info;
3756   for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3757   {
3758     if (i == LEVELINFO_TOKEN_NAME ||
3759         i == LEVELINFO_TOKEN_AUTHOR ||
3760         i == LEVELINFO_TOKEN_LEVELS ||
3761         i == LEVELINFO_TOKEN_FIRST_LEVEL)
3762       fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
3763
3764     /* just to make things nicer :) */
3765     if (i == LEVELINFO_TOKEN_AUTHOR)
3766       fprintf(file, "\n");      
3767   }
3768
3769   token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
3770
3771   fclose(file);
3772
3773   SetFilePermissions(filename, PERMS_PRIVATE);
3774
3775   freeTreeInfo(level_info);
3776   free(filename);
3777 }
3778
3779 char *getSetupValue(int type, void *value)
3780 {
3781   static char value_string[MAX_LINE_LEN];
3782
3783   if (value == NULL)
3784     return NULL;
3785
3786   switch (type)
3787   {
3788     case TYPE_BOOLEAN:
3789       strcpy(value_string, (*(boolean *)value ? "true" : "false"));
3790       break;
3791
3792     case TYPE_SWITCH:
3793       strcpy(value_string, (*(boolean *)value ? "on" : "off"));
3794       break;
3795
3796     case TYPE_SWITCH3:
3797       strcpy(value_string, (*(int *)value == AUTO  ? "auto" :
3798                             *(int *)value == FALSE ? "off" : "on"));
3799       break;
3800
3801     case TYPE_YES_NO:
3802       strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
3803       break;
3804
3805     case TYPE_YES_NO_AUTO:
3806       strcpy(value_string, (*(int *)value == AUTO  ? "auto" :
3807                             *(int *)value == FALSE ? "no" : "yes"));
3808       break;
3809
3810     case TYPE_ECS_AGA:
3811       strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
3812       break;
3813
3814     case TYPE_KEY:
3815       strcpy(value_string, getKeyNameFromKey(*(Key *)value));
3816       break;
3817
3818     case TYPE_KEY_X11:
3819       strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
3820       break;
3821
3822     case TYPE_INTEGER:
3823       sprintf(value_string, "%d", *(int *)value);
3824       break;
3825
3826     case TYPE_STRING:
3827       if (*(char **)value == NULL)
3828         return NULL;
3829
3830       strcpy(value_string, *(char **)value);
3831       break;
3832
3833     default:
3834       value_string[0] = '\0';
3835       break;
3836   }
3837
3838   if (type & TYPE_GHOSTED)
3839     strcpy(value_string, "n/a");
3840
3841   return value_string;
3842 }
3843
3844 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
3845 {
3846   int i;
3847   char *line;
3848   static char token_string[MAX_LINE_LEN];
3849   int token_type = token_info[token_nr].type;
3850   void *setup_value = token_info[token_nr].value;
3851   char *token_text = token_info[token_nr].text;
3852   char *value_string = getSetupValue(token_type, setup_value);
3853
3854   /* build complete token string */
3855   sprintf(token_string, "%s%s", prefix, token_text);
3856
3857   /* build setup entry line */
3858   line = getFormattedSetupEntry(token_string, value_string);
3859
3860   if (token_type == TYPE_KEY_X11)
3861   {
3862     Key key = *(Key *)setup_value;
3863     char *keyname = getKeyNameFromKey(key);
3864
3865     /* add comment, if useful */
3866     if (!strEqual(keyname, "(undefined)") &&
3867         !strEqual(keyname, "(unknown)"))
3868     {
3869       /* add at least one whitespace */
3870       strcat(line, " ");
3871       for (i = strlen(line); i < token_comment_position; i++)
3872         strcat(line, " ");
3873
3874       strcat(line, "# ");
3875       strcat(line, keyname);
3876     }
3877   }
3878
3879   return line;
3880 }
3881
3882 void LoadLevelSetup_LastSeries()
3883 {
3884   /* ----------------------------------------------------------------------- */
3885   /* ~/.<program>/levelsetup.conf                                            */
3886   /* ----------------------------------------------------------------------- */
3887
3888   char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3889   SetupFileHash *level_setup_hash = NULL;
3890
3891   /* always start with reliable default values */
3892   leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3893
3894 #if defined(CREATE_SPECIAL_EDITION_RND_JUE)
3895   leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3896                                                "jue_start");
3897   if (leveldir_current == NULL)
3898     leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3899 #endif
3900
3901   if ((level_setup_hash = loadSetupFileHash(filename)))
3902   {
3903     char *last_level_series =
3904       getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
3905
3906     leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3907                                                  last_level_series);
3908     if (leveldir_current == NULL)
3909       leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3910
3911     checkSetupFileHashIdentifier(level_setup_hash, filename,
3912                                  getCookie("LEVELSETUP"));
3913
3914     freeSetupFileHash(level_setup_hash);
3915   }
3916   else
3917     Error(ERR_WARN, "using default setup values");
3918
3919   free(filename);
3920 }
3921
3922 void SaveLevelSetup_LastSeries()
3923 {
3924   /* ----------------------------------------------------------------------- */
3925   /* ~/.<program>/levelsetup.conf                                            */
3926   /* ----------------------------------------------------------------------- */
3927
3928   char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3929   char *level_subdir = leveldir_current->subdir;
3930   FILE *file;
3931
3932   InitUserDataDirectory();
3933
3934   if (!(file = fopen(filename, MODE_WRITE)))
3935   {
3936     Error(ERR_WARN, "cannot write setup file '%s'", filename);
3937     free(filename);
3938     return;
3939   }
3940
3941   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
3942                                                  getCookie("LEVELSETUP")));
3943   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
3944                                                level_subdir));
3945
3946   fclose(file);
3947
3948   SetFilePermissions(filename, PERMS_PRIVATE);
3949
3950   free(filename);
3951 }
3952
3953 static void checkSeriesInfo()
3954 {
3955   static char *level_directory = NULL;
3956   DIR *dir;
3957   struct dirent *dir_entry;
3958
3959   /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
3960
3961   level_directory = getPath2((leveldir_current->in_user_dir ?
3962                               getUserLevelDir(NULL) :
3963                               options.level_directory),
3964                              leveldir_current->fullpath);
3965
3966   if ((dir = opendir(level_directory)) == NULL)
3967   {
3968     Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3969     return;
3970   }
3971
3972   while ((dir_entry = readdir(dir)) != NULL)    /* last directory entry */
3973   {
3974     if (strlen(dir_entry->d_name) > 4 &&
3975         dir_entry->d_name[3] == '.' &&
3976         strEqual(&dir_entry->d_name[4], LEVELFILE_EXTENSION))
3977     {
3978       char levelnum_str[4];
3979       int levelnum_value;
3980
3981       strncpy(levelnum_str, dir_entry->d_name, 3);
3982       levelnum_str[3] = '\0';
3983
3984       levelnum_value = atoi(levelnum_str);
3985
3986 #if 0
3987       if (levelnum_value < leveldir_current->first_level)
3988       {
3989         Error(ERR_WARN, "additional level %d found", levelnum_value);
3990         leveldir_current->first_level = levelnum_value;
3991       }
3992       else if (levelnum_value > leveldir_current->last_level)
3993       {
3994         Error(ERR_WARN, "additional level %d found", levelnum_value);
3995         leveldir_current->last_level = levelnum_value;
3996       }
3997 #endif
3998     }
3999   }
4000
4001   closedir(dir);
4002 }
4003
4004 void LoadLevelSetup_SeriesInfo()
4005 {
4006   char *filename;
4007   SetupFileHash *level_setup_hash = NULL;
4008   char *level_subdir = leveldir_current->subdir;
4009
4010   /* always start with reliable default values */
4011   level_nr = leveldir_current->first_level;
4012
4013   checkSeriesInfo(leveldir_current);
4014
4015   /* ----------------------------------------------------------------------- */
4016   /* ~/.<program>/levelsetup/<level series>/levelsetup.conf                  */
4017   /* ----------------------------------------------------------------------- */
4018
4019   level_subdir = leveldir_current->subdir;
4020
4021   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4022
4023   if ((level_setup_hash = loadSetupFileHash(filename)))
4024   {
4025     char *token_value;
4026
4027     token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
4028
4029     if (token_value)
4030     {
4031       level_nr = atoi(token_value);
4032
4033       if (level_nr < leveldir_current->first_level)
4034         level_nr = leveldir_current->first_level;
4035       if (level_nr > leveldir_current->last_level)
4036         level_nr = leveldir_current->last_level;
4037     }
4038
4039     token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
4040
4041     if (token_value)
4042     {
4043       int level_nr = atoi(token_value);
4044
4045       if (level_nr < leveldir_current->first_level)
4046         level_nr = leveldir_current->first_level;
4047       if (level_nr > leveldir_current->last_level + 1)
4048         level_nr = leveldir_current->last_level;
4049
4050       if (leveldir_current->user_defined || !leveldir_current->handicap)
4051         level_nr = leveldir_current->last_level;
4052
4053       leveldir_current->handicap_level = level_nr;
4054     }
4055
4056     checkSetupFileHashIdentifier(level_setup_hash, filename,
4057                                  getCookie("LEVELSETUP"));
4058
4059     freeSetupFileHash(level_setup_hash);
4060   }
4061   else
4062     Error(ERR_WARN, "using default setup values");
4063
4064   free(filename);
4065 }
4066
4067 void SaveLevelSetup_SeriesInfo()
4068 {
4069   char *filename;
4070   char *level_subdir = leveldir_current->subdir;
4071   char *level_nr_str = int2str(level_nr, 0);
4072   char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
4073   FILE *file;
4074
4075   /* ----------------------------------------------------------------------- */
4076   /* ~/.<program>/levelsetup/<level series>/levelsetup.conf                  */
4077   /* ----------------------------------------------------------------------- */
4078
4079   InitLevelSetupDirectory(level_subdir);
4080
4081   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4082
4083   if (!(file = fopen(filename, MODE_WRITE)))
4084   {
4085     Error(ERR_WARN, "cannot write setup file '%s'", filename);
4086     free(filename);
4087     return;
4088   }
4089
4090   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
4091                                                  getCookie("LEVELSETUP")));
4092   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
4093                                                level_nr_str));
4094   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
4095                                                handicap_level_str));
4096
4097   fclose(file);
4098
4099   SetFilePermissions(filename, PERMS_PRIVATE);
4100
4101   free(filename);
4102 }