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