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