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