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