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