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