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