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