05978dd4a967e86f1d2eaf8ed6e1b8397737a324
[rocksndiamonds.git] / src / libgame / setup.c
1 /***********************************************************
2 * Artsoft Retro-Game Library                               *
3 *----------------------------------------------------------*
4 * (c) 1994-2002 Artsoft Entertainment                      *
5 *               Holger Schemel                             *
6 *               Detmolder Strasse 189                      *
7 *               33604 Bielefeld                            *
8 *               Germany                                    *
9 *               e-mail: info@artsoft.org                   *
10 *----------------------------------------------------------*
11 * setup.c                                                  *
12 ***********************************************************/
13
14 #include <sys/types.h>
15 #include <sys/stat.h>
16 #include <dirent.h>
17 #include <string.h>
18 #include <unistd.h>
19
20 #include "setup.h"
21 #include "joystick.h"
22 #include "text.h"
23 #include "misc.h"
24 #include "hash.h"
25
26
27 #define NUM_LEVELCLASS_DESC     8
28
29 static char *levelclass_desc[NUM_LEVELCLASS_DESC] =
30 {
31   "Tutorial Levels",
32   "Classic Originals",
33   "Contributions",
34   "Private Levels",
35   "Boulderdash",
36   "Emerald Mine",
37   "Supaplex",
38   "DX Boulderdash"
39 };
40
41
42 #define LEVELCOLOR(n)   (IS_LEVELCLASS_TUTORIAL(n) ?            FC_BLUE :    \
43                          IS_LEVELCLASS_CLASSICS(n) ?            FC_RED :     \
44                          IS_LEVELCLASS_BD(n) ?                  FC_GREEN :   \
45                          IS_LEVELCLASS_EM(n) ?                  FC_YELLOW :  \
46                          IS_LEVELCLASS_SP(n) ?                  FC_GREEN :   \
47                          IS_LEVELCLASS_DX(n) ?                  FC_YELLOW :  \
48                          IS_LEVELCLASS_CONTRIB(n) ?             FC_GREEN :   \
49                          IS_LEVELCLASS_PRIVATE(n) ?             FC_RED :     \
50                          FC_BLUE)
51
52 #define LEVELSORTING(n) (IS_LEVELCLASS_TUTORIAL(n) ?            0 :     \
53                          IS_LEVELCLASS_CLASSICS(n) ?            1 :     \
54                          IS_LEVELCLASS_BD(n) ?                  2 :     \
55                          IS_LEVELCLASS_EM(n) ?                  3 :     \
56                          IS_LEVELCLASS_SP(n) ?                  4 :     \
57                          IS_LEVELCLASS_DX(n) ?                  5 :     \
58                          IS_LEVELCLASS_CONTRIB(n) ?             6 :     \
59                          IS_LEVELCLASS_PRIVATE(n) ?             7 :     \
60                          9)
61
62 #define ARTWORKCOLOR(n) (IS_ARTWORKCLASS_CLASSICS(n) ?          FC_RED :     \
63                          IS_ARTWORKCLASS_CONTRIB(n) ?           FC_YELLOW :  \
64                          IS_ARTWORKCLASS_PRIVATE(n) ?           FC_RED :     \
65                          IS_ARTWORKCLASS_LEVEL(n) ?             FC_GREEN :   \
66                          FC_BLUE)
67
68 #define ARTWORKSORTING(n) (IS_ARTWORKCLASS_CLASSICS(n) ?        0 :     \
69                            IS_ARTWORKCLASS_LEVEL(n) ?           1 :     \
70                            IS_ARTWORKCLASS_CONTRIB(n) ?         2 :     \
71                            IS_ARTWORKCLASS_PRIVATE(n) ?         3 :     \
72                            9)
73
74 #define TOKEN_VALUE_POSITION            40
75 #define TOKEN_COMMENT_POSITION          60
76
77 #define MAX_COOKIE_LEN                  256
78
79
80 /* ------------------------------------------------------------------------- */
81 /* file functions                                                            */
82 /* ------------------------------------------------------------------------- */
83
84 static char *getLevelClassDescription(TreeInfo *ldi)
85 {
86   int position = ldi->sort_priority / 100;
87
88   if (position >= 0 && position < NUM_LEVELCLASS_DESC)
89     return levelclass_desc[position];
90   else
91     return "Unknown Level Class";
92 }
93
94 static char *getUserLevelDir(char *level_subdir)
95 {
96   static char *userlevel_dir = NULL;
97   char *data_dir = getUserDataDir();
98   char *userlevel_subdir = LEVELS_DIRECTORY;
99
100   if (userlevel_dir)
101     free(userlevel_dir);
102
103   if (level_subdir != NULL)
104     userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir);
105   else
106     userlevel_dir = getPath2(data_dir, userlevel_subdir);
107
108   return userlevel_dir;
109 }
110
111 static char *getTapeDir(char *level_subdir)
112 {
113   static char *tape_dir = NULL;
114   char *data_dir = getUserDataDir();
115   char *tape_subdir = TAPES_DIRECTORY;
116
117   if (tape_dir)
118     free(tape_dir);
119
120   if (level_subdir != NULL)
121     tape_dir = getPath3(data_dir, tape_subdir, level_subdir);
122   else
123     tape_dir = getPath2(data_dir, tape_subdir);
124
125   return tape_dir;
126 }
127
128 static char *getScoreDir(char *level_subdir)
129 {
130   static char *score_dir = NULL;
131   char *data_dir = getCommonDataDir();
132   char *score_subdir = SCORES_DIRECTORY;
133
134   if (score_dir)
135     free(score_dir);
136
137   if (level_subdir != NULL)
138     score_dir = getPath3(data_dir, score_subdir, level_subdir);
139   else
140     score_dir = getPath2(data_dir, score_subdir);
141
142   return score_dir;
143 }
144
145 static char *getLevelSetupDir(char *level_subdir)
146 {
147   static char *levelsetup_dir = NULL;
148   char *data_dir = getUserDataDir();
149   char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
150
151   if (levelsetup_dir)
152     free(levelsetup_dir);
153
154   if (level_subdir != NULL)
155     levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir);
156   else
157     levelsetup_dir = getPath2(data_dir, levelsetup_subdir);
158
159   return levelsetup_dir;
160 }
161
162 static char *getLevelDirFromTreeInfo(TreeInfo *node)
163 {
164   static char *level_dir = NULL;
165
166   if (node == NULL)
167     return options.level_directory;
168
169   if (level_dir)
170     free(level_dir);
171
172   level_dir = getPath2((node->user_defined ? getUserLevelDir(NULL) :
173                         options.level_directory), node->fullpath);
174
175   return level_dir;
176 }
177
178 static char *getCurrentLevelDir()
179 {
180   return getLevelDirFromTreeInfo(leveldir_current);
181 }
182
183 static char *getDefaultGraphicsDir(char *graphics_subdir)
184 {
185   static char *graphics_dir = NULL;
186
187   if (graphics_subdir == NULL)
188     return options.graphics_directory;
189
190   if (graphics_dir)
191     free(graphics_dir);
192
193   graphics_dir = getPath2(options.graphics_directory, graphics_subdir);
194
195   return graphics_dir;
196 }
197
198 static char *getDefaultSoundsDir(char *sounds_subdir)
199 {
200   static char *sounds_dir = NULL;
201
202   if (sounds_subdir == NULL)
203     return options.sounds_directory;
204
205   if (sounds_dir)
206     free(sounds_dir);
207
208   sounds_dir = getPath2(options.sounds_directory, sounds_subdir);
209
210   return sounds_dir;
211 }
212
213 static char *getDefaultMusicDir(char *music_subdir)
214 {
215   static char *music_dir = NULL;
216
217   if (music_subdir == NULL)
218     return options.music_directory;
219
220   if (music_dir)
221     free(music_dir);
222
223   music_dir = getPath2(options.music_directory, music_subdir);
224
225   return music_dir;
226 }
227
228 static char *getDefaultArtworkSet(int type)
229 {
230   return (type == TREE_TYPE_GRAPHICS_DIR ? GFX_CLASSIC_SUBDIR :
231           type == TREE_TYPE_SOUNDS_DIR   ? SND_CLASSIC_SUBDIR :
232           type == TREE_TYPE_MUSIC_DIR    ? MUS_CLASSIC_SUBDIR : "");
233 }
234
235 static char *getDefaultArtworkDir(int type)
236 {
237   return (type == TREE_TYPE_GRAPHICS_DIR ?
238           getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR) :
239           type == TREE_TYPE_SOUNDS_DIR ?
240           getDefaultSoundsDir(SND_CLASSIC_SUBDIR) :
241           type == TREE_TYPE_MUSIC_DIR ?
242           getDefaultMusicDir(MUS_CLASSIC_SUBDIR) : "");
243 }
244
245 static char *getUserGraphicsDir()
246 {
247   static char *usergraphics_dir = NULL;
248
249   if (usergraphics_dir == NULL)
250     usergraphics_dir = getPath2(getUserDataDir(), GRAPHICS_DIRECTORY);
251
252   return usergraphics_dir;
253 }
254
255 static char *getUserSoundsDir()
256 {
257   static char *usersounds_dir = NULL;
258
259   if (usersounds_dir == NULL)
260     usersounds_dir = getPath2(getUserDataDir(), SOUNDS_DIRECTORY);
261
262   return usersounds_dir;
263 }
264
265 static char *getUserMusicDir()
266 {
267   static char *usermusic_dir = NULL;
268
269   if (usermusic_dir == NULL)
270     usermusic_dir = getPath2(getUserDataDir(), MUSIC_DIRECTORY);
271
272   return usermusic_dir;
273 }
274
275 static char *getSetupArtworkDir(TreeInfo *ti)
276 {
277   static char *artwork_dir = NULL;
278
279   if (artwork_dir != NULL)
280     free(artwork_dir);
281
282   artwork_dir = getPath2(ti->basepath, ti->fullpath);
283
284   return artwork_dir;
285 }
286
287 char *setLevelArtworkDir(TreeInfo *ti)
288 {
289   char **artwork_path_ptr, **artwork_set_ptr;
290   TreeInfo *level_artwork;
291
292   if (ti == NULL || leveldir_current == NULL)
293     return NULL;
294
295   artwork_path_ptr = &(LEVELDIR_ARTWORK_PATH(leveldir_current, ti->type));
296   artwork_set_ptr  = &(LEVELDIR_ARTWORK_SET( leveldir_current, ti->type));
297
298   if (*artwork_path_ptr != NULL)
299     free(*artwork_path_ptr);
300
301   if ((level_artwork = getTreeInfoFromIdentifier(ti, *artwork_set_ptr)))
302     *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
303   else
304   {
305     /* No (or non-existing) artwork configured in "levelinfo.conf". This would
306        normally result in using the artwork configured in the setup menu. But
307        if an artwork subdirectory exists (which might contain custom artwork
308        or an artwork configuration file), this level artwork must be treated
309        as relative to the default "classic" artwork, not to the artwork that
310        is currently configured in the setup menu. */
311
312     char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
313
314     if (*artwork_set_ptr != NULL)
315       free(*artwork_set_ptr);
316
317     if (fileExists(dir))
318     {
319       *artwork_path_ptr = getStringCopy(getDefaultArtworkDir(ti->type));
320       *artwork_set_ptr = getStringCopy(getDefaultArtworkSet(ti->type));
321     }
322     else
323     {
324       *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
325       *artwork_set_ptr = NULL;
326     }
327
328     free(dir);
329   }
330
331   return *artwork_set_ptr;
332 }
333
334 inline static char *getLevelArtworkSet(int type)
335 {
336   if (leveldir_current == NULL)
337     return NULL;
338
339   return LEVELDIR_ARTWORK_SET(leveldir_current, type);
340 }
341
342 inline static char *getLevelArtworkDir(int type)
343 {
344   if (leveldir_current == NULL)
345     return UNDEFINED_FILENAME;
346
347   return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
348 }
349
350 char *getLevelFilename(int nr)
351 {
352   static char *filename = NULL;
353   char basename[MAX_FILENAME_LEN];
354
355   if (filename != NULL)
356     free(filename);
357
358   if (nr < 0)
359     sprintf(basename, "template.%s", LEVELFILE_EXTENSION);
360   else
361     sprintf(basename, "%03d.%s", nr, LEVELFILE_EXTENSION);
362
363   filename = getPath2(getCurrentLevelDir(), basename);
364
365   return filename;
366 }
367
368 char *getTapeFilename(int nr)
369 {
370   static char *filename = NULL;
371   char basename[MAX_FILENAME_LEN];
372
373   if (filename != NULL)
374     free(filename);
375
376   sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
377   filename = getPath2(getTapeDir(leveldir_current->filename), basename);
378
379   return filename;
380 }
381
382 char *getScoreFilename(int nr)
383 {
384   static char *filename = NULL;
385   char basename[MAX_FILENAME_LEN];
386
387   if (filename != NULL)
388     free(filename);
389
390   sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
391   filename = getPath2(getScoreDir(leveldir_current->filename), basename);
392
393   return filename;
394 }
395
396 char *getSetupFilename()
397 {
398   static char *filename = NULL;
399
400   if (filename != NULL)
401     free(filename);
402
403   filename = getPath2(getSetupDir(), SETUP_FILENAME);
404
405   return filename;
406 }
407
408 char *getEditorSetupFilename()
409 {
410   static char *filename = NULL;
411
412   if (filename != NULL)
413     free(filename);
414
415   filename = getPath2(getSetupDir(), EDITORSETUP_FILENAME);
416
417   return filename;
418 }
419
420 char *getHelpAnimFilename()
421 {
422   static char *filename = NULL;
423
424   if (filename != NULL)
425     free(filename);
426
427   filename = getPath2(getCurrentLevelDir(), HELPANIM_FILENAME);
428
429   return filename;
430 }
431
432 char *getHelpTextFilename()
433 {
434   static char *filename = NULL;
435
436   if (filename != NULL)
437     free(filename);
438
439   filename = getPath2(getCurrentLevelDir(), HELPTEXT_FILENAME);
440
441   return filename;
442 }
443
444 static char *getCorrectedArtworkBasename(char *basename)
445 {
446   char *basename_corrected = basename;
447
448 #if defined(PLATFORM_MSDOS)
449   if (program.filename_prefix != NULL)
450   {
451     int prefix_len = strlen(program.filename_prefix);
452
453     if (strncmp(basename, program.filename_prefix, prefix_len) == 0)
454       basename_corrected = &basename[prefix_len];
455
456     /* if corrected filename is still longer than standard MS-DOS filename
457        size (8 characters + 1 dot + 3 characters file extension), shorten
458        filename by writing file extension after 8th basename character */
459     if (strlen(basename_corrected) > 8 + 1 + 3)
460     {
461       static char *msdos_filename = NULL;
462
463       if (msdos_filename != NULL)
464         free(msdos_filename);
465
466       msdos_filename = getStringCopy(basename_corrected);
467       strncpy(&msdos_filename[8], &basename[strlen(basename) - (1+3)], 1+3 +1);
468
469       basename_corrected = msdos_filename;
470     }
471   }
472 #endif
473
474   return basename_corrected;
475 }
476
477 char *getCustomImageFilename(char *basename)
478 {
479   static char *filename = NULL;
480   boolean skip_setup_artwork = FALSE;
481
482   if (filename != NULL)
483     free(filename);
484
485   basename = getCorrectedArtworkBasename(basename);
486
487   if (!setup.override_level_graphics)
488   {
489     /* 1st try: look for special artwork in current level series directory */
490     filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
491     if (fileExists(filename))
492       return filename;
493
494     free(filename);
495
496     /* check if there is special artwork configured in level series config */
497     if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
498     {
499       /* 2nd try: look for special artwork configured in level series config */
500       filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
501       if (fileExists(filename))
502         return filename;
503
504       free(filename);
505
506       /* take missing artwork configured in level set config from default */
507       skip_setup_artwork = TRUE;
508     }
509   }
510
511   if (!skip_setup_artwork)
512   {
513     /* 3rd try: look for special artwork in configured artwork directory */
514     filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
515     if (fileExists(filename))
516       return filename;
517
518     free(filename);
519   }
520
521   /* 4th try: look for default artwork in new default artwork directory */
522   filename = getPath2(getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR), basename);
523   if (fileExists(filename))
524     return filename;
525
526   free(filename);
527
528   /* 5th try: look for default artwork in old default artwork directory */
529   filename = getPath2(options.graphics_directory, basename);
530   if (fileExists(filename))
531     return filename;
532
533   return NULL;          /* cannot find specified artwork file anywhere */
534 }
535
536 char *getCustomSoundFilename(char *basename)
537 {
538   static char *filename = NULL;
539   boolean skip_setup_artwork = FALSE;
540
541   if (filename != NULL)
542     free(filename);
543
544   basename = getCorrectedArtworkBasename(basename);
545
546   if (!setup.override_level_sounds)
547   {
548     /* 1st try: look for special artwork in current level series directory */
549     filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
550     if (fileExists(filename))
551       return filename;
552
553     free(filename);
554
555     /* check if there is special artwork configured in level series config */
556     if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
557     {
558       /* 2nd try: look for special artwork configured in level series config */
559       filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
560       if (fileExists(filename))
561         return filename;
562
563       free(filename);
564
565       /* take missing artwork configured in level set config from default */
566       skip_setup_artwork = TRUE;
567     }
568   }
569
570   if (!skip_setup_artwork)
571   {
572     /* 3rd try: look for special artwork in configured artwork directory */
573     filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
574     if (fileExists(filename))
575       return filename;
576
577     free(filename);
578   }
579
580   /* 4th try: look for default artwork in new default artwork directory */
581   filename = getPath2(getDefaultSoundsDir(SND_CLASSIC_SUBDIR), basename);
582   if (fileExists(filename))
583     return filename;
584
585   free(filename);
586
587   /* 5th try: look for default artwork in old default artwork directory */
588   filename = getPath2(options.sounds_directory, basename);
589   if (fileExists(filename))
590     return filename;
591
592   return NULL;          /* cannot find specified artwork file anywhere */
593 }
594
595 char *getCustomMusicFilename(char *basename)
596 {
597   static char *filename = NULL;
598   boolean skip_setup_artwork = FALSE;
599
600   if (filename != NULL)
601     free(filename);
602
603   basename = getCorrectedArtworkBasename(basename);
604
605   if (!setup.override_level_music)
606   {
607     /* 1st try: look for special artwork in current level series directory */
608     filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
609     if (fileExists(filename))
610       return filename;
611
612     free(filename);
613
614     /* check if there is special artwork configured in level series config */
615     if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
616     {
617       /* 2nd try: look for special artwork configured in level series config */
618       filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
619       if (fileExists(filename))
620         return filename;
621
622       free(filename);
623
624       /* take missing artwork configured in level set config from default */
625       skip_setup_artwork = TRUE;
626     }
627   }
628
629   if (!skip_setup_artwork)
630   {
631     /* 3rd try: look for special artwork in configured artwork directory */
632     filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
633     if (fileExists(filename))
634       return filename;
635
636     free(filename);
637   }
638
639   /* 4th try: look for default artwork in new default artwork directory */
640   filename = getPath2(getDefaultMusicDir(MUS_CLASSIC_SUBDIR), basename);
641   if (fileExists(filename))
642     return filename;
643
644   free(filename);
645
646   /* 5th try: look for default artwork in old default artwork directory */
647   filename = getPath2(options.music_directory, basename);
648   if (fileExists(filename))
649     return filename;
650
651   return NULL;          /* cannot find specified artwork file anywhere */
652 }
653
654 char *getCustomArtworkFilename(char *basename, int type)
655 {
656   if (type == ARTWORK_TYPE_GRAPHICS)
657     return getCustomImageFilename(basename);
658   else if (type == ARTWORK_TYPE_SOUNDS)
659     return getCustomSoundFilename(basename);
660   else if (type == ARTWORK_TYPE_MUSIC)
661     return getCustomMusicFilename(basename);
662   else
663     return UNDEFINED_FILENAME;
664 }
665
666 char *getCustomArtworkConfigFilename(int type)
667 {
668   return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
669 }
670
671 char *getCustomArtworkLevelConfigFilename(int type)
672 {
673   static char *filename = NULL;
674
675   if (filename != NULL)
676     free(filename);
677
678   filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
679
680   return filename;
681 }
682
683 char *getCustomMusicDirectory(void)
684 {
685   static char *directory = NULL;
686   boolean skip_setup_artwork = FALSE;
687
688   if (directory != NULL)
689     free(directory);
690
691   if (!setup.override_level_music)
692   {
693     /* 1st try: look for special artwork in current level series directory */
694     directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
695     if (fileExists(directory))
696       return directory;
697
698     free(directory);
699
700     /* check if there is special artwork configured in level series config */
701     if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
702     {
703       /* 2nd try: look for special artwork configured in level series config */
704       directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
705       if (fileExists(directory))
706         return directory;
707
708       free(directory);
709
710       /* take missing artwork configured in level set config from default */
711       skip_setup_artwork = TRUE;
712     }
713   }
714
715   if (!skip_setup_artwork)
716   {
717     /* 3rd try: look for special artwork in configured artwork directory */
718     directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
719     if (fileExists(directory))
720       return directory;
721
722     free(directory);
723   }
724
725   /* 4th try: look for default artwork in new default artwork directory */
726   directory = getStringCopy(getDefaultMusicDir(MUS_CLASSIC_SUBDIR));
727   if (fileExists(directory))
728     return directory;
729
730   free(directory);
731
732   /* 5th try: look for default artwork in old default artwork directory */
733   directory = getStringCopy(options.music_directory);
734   if (fileExists(directory))
735     return directory;
736
737   return NULL;          /* cannot find specified artwork file anywhere */
738 }
739
740 void InitTapeDirectory(char *level_subdir)
741 {
742   createDirectory(getUserDataDir(), "user data", PERMS_PRIVATE);
743   createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
744   createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
745 }
746
747 void InitScoreDirectory(char *level_subdir)
748 {
749   createDirectory(getCommonDataDir(), "common data", PERMS_PUBLIC);
750   createDirectory(getScoreDir(NULL), "main score", PERMS_PUBLIC);
751   createDirectory(getScoreDir(level_subdir), "level score", PERMS_PUBLIC);
752 }
753
754 static void SaveUserLevelInfo();
755
756 void InitUserLevelDirectory(char *level_subdir)
757 {
758   if (access(getUserLevelDir(level_subdir), F_OK) != 0)
759   {
760     createDirectory(getUserDataDir(), "user data", PERMS_PRIVATE);
761     createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
762     createDirectory(getUserLevelDir(level_subdir), "user level",PERMS_PRIVATE);
763
764     SaveUserLevelInfo();
765   }
766 }
767
768 void InitLevelSetupDirectory(char *level_subdir)
769 {
770   createDirectory(getUserDataDir(), "user data", PERMS_PRIVATE);
771   createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
772   createDirectory(getLevelSetupDir(level_subdir), "level setup",PERMS_PRIVATE);
773 }
774
775
776 /* ------------------------------------------------------------------------- */
777 /* some functions to handle lists of level directories                       */
778 /* ------------------------------------------------------------------------- */
779
780 TreeInfo *newTreeInfo()
781 {
782   return checked_calloc(sizeof(TreeInfo));
783 }
784
785 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
786 {
787   node_new->next = *node_first;
788   *node_first = node_new;
789 }
790
791 int numTreeInfo(TreeInfo *node)
792 {
793   int num = 0;
794
795   while (node)
796   {
797     num++;
798     node = node->next;
799   }
800
801   return num;
802 }
803
804 boolean validLevelSeries(TreeInfo *node)
805 {
806   return (node != NULL && !node->node_group && !node->parent_link);
807 }
808
809 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
810 {
811   if (node == NULL)
812     return NULL;
813
814   if (node->node_group)         /* enter level group (step down into tree) */
815     return getFirstValidTreeInfoEntry(node->node_group);
816   else if (node->parent_link)   /* skip start entry of level group */
817   {
818     if (node->next)             /* get first real level series entry */
819       return getFirstValidTreeInfoEntry(node->next);
820     else                        /* leave empty level group and go on */
821       return getFirstValidTreeInfoEntry(node->node_parent->next);
822   }
823   else                          /* this seems to be a regular level series */
824     return node;
825 }
826
827 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
828 {
829   if (node == NULL)
830     return NULL;
831
832   if (node->node_parent == NULL)                /* top level group */
833     return *node->node_top;
834   else                                          /* sub level group */
835     return node->node_parent->node_group;
836 }
837
838 int numTreeInfoInGroup(TreeInfo *node)
839 {
840   return numTreeInfo(getTreeInfoFirstGroupEntry(node));
841 }
842
843 int posTreeInfo(TreeInfo *node)
844 {
845   TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
846   int pos = 0;
847
848   while (node_cmp)
849   {
850     if (node_cmp == node)
851       return pos;
852
853     pos++;
854     node_cmp = node_cmp->next;
855   }
856
857   return 0;
858 }
859
860 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
861 {
862   TreeInfo *node_default = node;
863   int pos_cmp = 0;
864
865   while (node)
866   {
867     if (pos_cmp == pos)
868       return node;
869
870     pos_cmp++;
871     node = node->next;
872   }
873
874   return node_default;
875 }
876
877 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
878 {
879   if (identifier == NULL)
880     return NULL;
881
882   while (node)
883   {
884     if (node->node_group)
885     {
886       TreeInfo *node_group;
887
888       node_group = getTreeInfoFromIdentifier(node->node_group, identifier);
889
890       if (node_group)
891         return node_group;
892     }
893     else if (!node->parent_link)
894     {
895       if (strcmp(identifier, node->identifier) == 0)
896         return node;
897     }
898
899     node = node->next;
900   }
901
902   return NULL;
903 }
904
905 void dumpTreeInfo(TreeInfo *node, int depth)
906 {
907   int i;
908
909   printf("Dumping TreeInfo:\n");
910
911   while (node)
912   {
913     for (i = 0; i < (depth + 1) * 3; i++)
914       printf(" ");
915
916 #if 1
917     printf("filename == '%s' ['%s', '%s'] [%d])\n",
918            node->filename, node->fullpath, node->basepath, node->user_defined);
919 #else
920     printf("filename == '%s' (%s) [%s] (%d)\n",
921            node->filename, node->name, node->identifier, node->sort_priority);
922 #endif
923
924     if (node->node_group != NULL)
925       dumpTreeInfo(node->node_group, depth + 1);
926
927     node = node->next;
928   }
929 }
930
931 void sortTreeInfo(TreeInfo **node_first,
932                   int (*compare_function)(const void *, const void *))
933 {
934   int num_nodes = numTreeInfo(*node_first);
935   TreeInfo **sort_array;
936   TreeInfo *node = *node_first;
937   int i = 0;
938
939   if (num_nodes == 0)
940     return;
941
942   /* allocate array for sorting structure pointers */
943   sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
944
945   /* writing structure pointers to sorting array */
946   while (i < num_nodes && node)         /* double boundary check... */
947   {
948     sort_array[i] = node;
949
950     i++;
951     node = node->next;
952   }
953
954   /* sorting the structure pointers in the sorting array */
955   qsort(sort_array, num_nodes, sizeof(TreeInfo *),
956         compare_function);
957
958   /* update the linkage of list elements with the sorted node array */
959   for (i = 0; i < num_nodes - 1; i++)
960     sort_array[i]->next = sort_array[i + 1];
961   sort_array[num_nodes - 1]->next = NULL;
962
963   /* update the linkage of the main list anchor pointer */
964   *node_first = sort_array[0];
965
966   free(sort_array);
967
968   /* now recursively sort the level group structures */
969   node = *node_first;
970   while (node)
971   {
972     if (node->node_group != NULL)
973       sortTreeInfo(&node->node_group, compare_function);
974
975     node = node->next;
976   }
977 }
978
979
980 /* ========================================================================= */
981 /* some stuff from "files.c"                                                 */
982 /* ========================================================================= */
983
984 #if defined(PLATFORM_WIN32)
985 #ifndef S_IRGRP
986 #define S_IRGRP S_IRUSR
987 #endif
988 #ifndef S_IROTH
989 #define S_IROTH S_IRUSR
990 #endif
991 #ifndef S_IWGRP
992 #define S_IWGRP S_IWUSR
993 #endif
994 #ifndef S_IWOTH
995 #define S_IWOTH S_IWUSR
996 #endif
997 #ifndef S_IXGRP
998 #define S_IXGRP S_IXUSR
999 #endif
1000 #ifndef S_IXOTH
1001 #define S_IXOTH S_IXUSR
1002 #endif
1003 #ifndef S_IRWXG
1004 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1005 #endif
1006 #ifndef S_ISGID
1007 #define S_ISGID 0
1008 #endif
1009 #endif  /* PLATFORM_WIN32 */
1010
1011 /* file permissions for newly written files */
1012 #define MODE_R_ALL              (S_IRUSR | S_IRGRP | S_IROTH)
1013 #define MODE_W_ALL              (S_IWUSR | S_IWGRP | S_IWOTH)
1014 #define MODE_X_ALL              (S_IXUSR | S_IXGRP | S_IXOTH)
1015
1016 #define MODE_W_PRIVATE          (S_IWUSR)
1017 #define MODE_W_PUBLIC           (S_IWUSR | S_IWGRP)
1018 #define MODE_W_PUBLIC_DIR       (S_IWUSR | S_IWGRP | S_ISGID)
1019
1020 #define DIR_PERMS_PRIVATE       (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1021 #define DIR_PERMS_PUBLIC        (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1022
1023 #define FILE_PERMS_PRIVATE      (MODE_R_ALL | MODE_W_PRIVATE)
1024 #define FILE_PERMS_PUBLIC       (MODE_R_ALL | MODE_W_PUBLIC)
1025
1026 char *getUserDataDir(void)
1027 {
1028   static char *userdata_dir = NULL;
1029
1030   if (userdata_dir == NULL)
1031     userdata_dir = getPath2(getHomeDir(), program.userdata_directory);
1032
1033   return userdata_dir;
1034 }
1035
1036 char *getCommonDataDir(void)
1037 {
1038   static char *common_data_dir = NULL;
1039
1040 #if defined(PLATFORM_WIN32)
1041   if (common_data_dir == NULL)
1042   {
1043     char *dir = checked_malloc(MAX_PATH + 1);
1044
1045     if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
1046         && strcmp(dir, "") != 0)        /* empty for Windows 95/98 */
1047       common_data_dir = getPath2(dir, program.userdata_directory);
1048     else
1049       common_data_dir = options.rw_base_directory;
1050   }
1051 #else
1052   if (common_data_dir == NULL)
1053     common_data_dir = options.rw_base_directory;
1054 #endif
1055
1056   return common_data_dir;
1057 }
1058
1059 char *getSetupDir()
1060 {
1061   return getUserDataDir();
1062 }
1063
1064 static mode_t posix_umask(mode_t mask)
1065 {
1066 #if defined(PLATFORM_UNIX)
1067   return umask(mask);
1068 #else
1069   return 0;
1070 #endif
1071 }
1072
1073 static int posix_mkdir(const char *pathname, mode_t mode)
1074 {
1075 #if defined(PLATFORM_WIN32)
1076   return mkdir(pathname);
1077 #else
1078   return mkdir(pathname, mode);
1079 #endif
1080 }
1081
1082 void createDirectory(char *dir, char *text, int permission_class)
1083 {
1084   /* leave "other" permissions in umask untouched, but ensure group parts
1085      of USERDATA_DIR_MODE are not masked */
1086   mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1087                      DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1088   mode_t normal_umask = posix_umask(0);
1089   mode_t group_umask = ~(dir_mode & S_IRWXG);
1090   posix_umask(normal_umask & group_umask);
1091
1092   if (access(dir, F_OK) != 0)
1093     if (posix_mkdir(dir, dir_mode) != 0)
1094       Error(ERR_WARN, "cannot create %s directory '%s'", text, dir);
1095
1096   posix_umask(normal_umask);            /* reset normal umask */
1097 }
1098
1099 void InitUserDataDirectory()
1100 {
1101   createDirectory(getUserDataDir(), "user data", PERMS_PRIVATE);
1102 }
1103
1104 void SetFilePermissions(char *filename, int permission_class)
1105 {
1106   chmod(filename, (permission_class == PERMS_PRIVATE ?
1107                    FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC));
1108 }
1109
1110 char *getCookie(char *file_type)
1111 {
1112   static char cookie[MAX_COOKIE_LEN + 1];
1113
1114   if (strlen(program.cookie_prefix) + 1 +
1115       strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1116     return "[COOKIE ERROR]";    /* should never happen */
1117
1118   sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1119           program.cookie_prefix, file_type,
1120           program.version_major, program.version_minor);
1121
1122   return cookie;
1123 }
1124
1125 int getFileVersionFromCookieString(const char *cookie)
1126 {
1127   const char *ptr_cookie1, *ptr_cookie2;
1128   const char *pattern1 = "_FILE_VERSION_";
1129   const char *pattern2 = "?.?";
1130   const int len_cookie = strlen(cookie);
1131   const int len_pattern1 = strlen(pattern1);
1132   const int len_pattern2 = strlen(pattern2);
1133   const int len_pattern = len_pattern1 + len_pattern2;
1134   int version_major, version_minor;
1135
1136   if (len_cookie <= len_pattern)
1137     return -1;
1138
1139   ptr_cookie1 = &cookie[len_cookie - len_pattern];
1140   ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1141
1142   if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1143     return -1;
1144
1145   if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1146       ptr_cookie2[1] != '.' ||
1147       ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1148     return -1;
1149
1150   version_major = ptr_cookie2[0] - '0';
1151   version_minor = ptr_cookie2[2] - '0';
1152
1153   return VERSION_IDENT(version_major, version_minor, 0, 0);
1154 }
1155
1156 boolean checkCookieString(const char *cookie, const char *template)
1157 {
1158   const char *pattern = "_FILE_VERSION_?.?";
1159   const int len_cookie = strlen(cookie);
1160   const int len_template = strlen(template);
1161   const int len_pattern = strlen(pattern);
1162
1163   if (len_cookie != len_template)
1164     return FALSE;
1165
1166   if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1167     return FALSE;
1168
1169   return TRUE;
1170 }
1171
1172 /* ------------------------------------------------------------------------- */
1173 /* setup file list and hash handling functions                               */
1174 /* ------------------------------------------------------------------------- */
1175
1176 char *getFormattedSetupEntry(char *token, char *value)
1177 {
1178   int i;
1179   static char entry[MAX_LINE_LEN];
1180
1181   /* start with the token and some spaces to format output line */
1182   sprintf(entry, "%s:", token);
1183   for (i = strlen(entry); i < TOKEN_VALUE_POSITION; i++)
1184     strcat(entry, " ");
1185
1186   /* continue with the token's value */
1187   strcat(entry, value);
1188
1189   return entry;
1190 }
1191
1192 SetupFileList *newSetupFileList(char *token, char *value)
1193 {
1194   SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1195
1196   new->token = getStringCopy(token);
1197   new->value = getStringCopy(value);
1198
1199   new->next = NULL;
1200
1201   return new;
1202 }
1203
1204 void freeSetupFileList(SetupFileList *list)
1205 {
1206   if (list == NULL)
1207     return;
1208
1209   if (list->token)
1210     free(list->token);
1211   if (list->value)
1212     free(list->value);
1213   if (list->next)
1214     freeSetupFileList(list->next);
1215   free(list);
1216 }
1217
1218 char *getListEntry(SetupFileList *list, char *token)
1219 {
1220   if (list == NULL)
1221     return NULL;
1222
1223   if (strcmp(list->token, token) == 0)
1224     return list->value;
1225   else
1226     return getListEntry(list->next, token);
1227 }
1228
1229 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1230 {
1231   if (list == NULL)
1232     return NULL;
1233
1234   if (strcmp(list->token, token) == 0)
1235   {
1236     if (list->value)
1237       free(list->value);
1238
1239     list->value = getStringCopy(value);
1240
1241     return list;
1242   }
1243   else if (list->next == NULL)
1244     return (list->next = newSetupFileList(token, value));
1245   else
1246     return setListEntry(list->next, token, value);
1247 }
1248
1249 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1250 {
1251   if (list == NULL)
1252     return NULL;
1253
1254   if (list->next == NULL)
1255     return (list->next = newSetupFileList(token, value));
1256   else
1257     return addListEntry(list->next, token, value);
1258 }
1259
1260 #ifdef DEBUG
1261 static void printSetupFileList(SetupFileList *list)
1262 {
1263   if (!list)
1264     return;
1265
1266   printf("token: '%s'\n", list->token);
1267   printf("value: '%s'\n", list->value);
1268
1269   printSetupFileList(list->next);
1270 }
1271 #endif
1272
1273 #ifdef DEBUG
1274 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1275 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1276 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1277 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1278 #else
1279 #define insert_hash_entry hashtable_insert
1280 #define search_hash_entry hashtable_search
1281 #define change_hash_entry hashtable_change
1282 #define remove_hash_entry hashtable_remove
1283 #endif
1284
1285 static unsigned int get_hash_from_key(void *key)
1286 {
1287   /*
1288     djb2
1289
1290     This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1291     'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1292     uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1293     it works better than many other constants, prime or not) has never been
1294     adequately explained.
1295
1296     If you just want to have a good hash function, and cannot wait, djb2
1297     is one of the best string hash functions i know. It has excellent
1298     distribution and speed on many different sets of keys and table sizes.
1299     You are not likely to do better with one of the "well known" functions
1300     such as PJW, K&R, etc.
1301
1302     Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1303   */
1304
1305   char *str = (char *)key;
1306   unsigned int hash = 5381;
1307   int c;
1308
1309   while ((c = *str++))
1310     hash = ((hash << 5) + hash) + c;    /* hash * 33 + c */
1311
1312   return hash;
1313 }
1314
1315 static int keys_are_equal(void *key1, void *key2)
1316 {
1317   return (strcmp((char *)key1, (char *)key2) == 0);
1318 }
1319
1320 SetupFileHash *newSetupFileHash()
1321 {
1322   SetupFileHash *new_hash =
1323     create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1324
1325   if (new_hash == NULL)
1326     Error(ERR_EXIT, "create_hashtable() failed -- out of memory");
1327
1328   return new_hash;
1329 }
1330
1331 void freeSetupFileHash(SetupFileHash *hash)
1332 {
1333   if (hash == NULL)
1334     return;
1335
1336   hashtable_destroy(hash, 1);   /* 1 == also free values stored in hash */
1337 }
1338
1339 char *getHashEntry(SetupFileHash *hash, char *token)
1340 {
1341   if (hash == NULL)
1342     return NULL;
1343
1344   return search_hash_entry(hash, token);
1345 }
1346
1347 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1348 {
1349   char *value_copy;
1350
1351   if (hash == NULL)
1352     return;
1353
1354   value_copy = getStringCopy(value);
1355
1356   /* change value; if it does not exist, insert it as new */
1357   if (!change_hash_entry(hash, token, value_copy))
1358     if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1359       Error(ERR_EXIT, "cannot insert into hash -- aborting");
1360 }
1361
1362 #if 0
1363 #ifdef DEBUG
1364 static void printSetupFileHash(SetupFileHash *hash)
1365 {
1366   BEGIN_HASH_ITERATION(hash, itr)
1367   {
1368     printf("token: '%s'\n", HASH_ITERATION_TOKEN(itr));
1369     printf("value: '%s'\n", HASH_ITERATION_VALUE(itr));
1370   }
1371   END_HASH_ITERATION(hash, itr)
1372 }
1373 #endif
1374 #endif
1375
1376 static void *loadSetupFileData(char *filename, boolean use_hash)
1377 {
1378   int line_len;
1379   char line[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
1380   char *token, *value, *line_ptr;
1381   void *setup_file_data, *insert_ptr = NULL;
1382   boolean read_continued_line = FALSE;
1383   FILE *file;
1384
1385   if (use_hash)
1386     setup_file_data = newSetupFileHash();
1387   else
1388     insert_ptr = setup_file_data = newSetupFileList("", "");
1389
1390   if (!(file = fopen(filename, MODE_READ)))
1391   {
1392     Error(ERR_WARN, "cannot open configuration file '%s'", filename);
1393     return NULL;
1394   }
1395
1396   while (!feof(file))
1397   {
1398     /* read next line of input file */
1399     if (!fgets(line, MAX_LINE_LEN, file))
1400       break;
1401
1402     /* cut trailing newline or carriage return */
1403     for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1404       if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
1405         *line_ptr = '\0';
1406
1407     if (read_continued_line)
1408     {
1409       /* cut leading whitespaces from input line */
1410       for (line_ptr = line; *line_ptr; line_ptr++)
1411         if (*line_ptr != ' ' && *line_ptr != '\t')
1412           break;
1413
1414       /* append new line to existing line, if there is enough space */
1415       if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
1416         strcat(previous_line, line_ptr);
1417
1418       strcpy(line, previous_line);      /* copy storage buffer to line */
1419
1420       read_continued_line = FALSE;
1421     }
1422
1423     /* if the last character is '\', continue at next line */
1424     if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
1425     {
1426       line[strlen(line) - 1] = '\0';    /* cut off trailing backslash */
1427       strcpy(previous_line, line);      /* copy line to storage buffer */
1428
1429       read_continued_line = TRUE;
1430
1431       continue;
1432     }
1433
1434     /* cut trailing comment from input line */
1435     for (line_ptr = line; *line_ptr; line_ptr++)
1436     {
1437       if (*line_ptr == '#')
1438       {
1439         *line_ptr = '\0';
1440         break;
1441       }
1442     }
1443
1444     /* cut trailing whitespaces from input line */
1445     for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1446       if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1447         *line_ptr = '\0';
1448
1449     /* ignore empty lines */
1450     if (*line == '\0')
1451       continue;
1452
1453     line_len = strlen(line);
1454
1455     /* cut leading whitespaces from token */
1456     for (token = line; *token; token++)
1457       if (*token != ' ' && *token != '\t')
1458         break;
1459
1460     /* find end of token */
1461     for (line_ptr = token; *line_ptr; line_ptr++)
1462     {
1463       if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
1464       {
1465         *line_ptr = '\0';
1466         break;
1467       }
1468     }
1469
1470     if (line_ptr < line + line_len)
1471       value = line_ptr + 1;
1472     else
1473 #if 1
1474       value = "true";   /* treat tokens without value as "true" */
1475 #else
1476       value = "\0";
1477 #endif
1478
1479     /* cut leading whitespaces from value */
1480     for (; *value; value++)
1481       if (*value != ' ' && *value != '\t')
1482         break;
1483
1484     if (*token && *value)
1485     {
1486       if (use_hash)
1487         setHashEntry((SetupFileHash *)setup_file_data, token, value);
1488       else
1489         insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
1490     }
1491   }
1492
1493   fclose(file);
1494
1495   if (use_hash)
1496   {
1497     if (hashtable_count((SetupFileHash *)setup_file_data) == 0)
1498       Error(ERR_WARN, "configuration file '%s' is empty", filename);
1499   }
1500   else
1501   {
1502     SetupFileList *setup_file_list = (SetupFileList *)setup_file_data;
1503     SetupFileList *first_valid_list_entry = setup_file_list->next;
1504
1505     /* free empty list header */
1506     setup_file_list->next = NULL;
1507     freeSetupFileList(setup_file_list);
1508     setup_file_data = first_valid_list_entry;
1509
1510     if (first_valid_list_entry == NULL)
1511       Error(ERR_WARN, "configuration file '%s' is empty", filename);
1512   }
1513
1514   return setup_file_data;
1515 }
1516
1517 SetupFileList *loadSetupFileList(char *filename)
1518 {
1519   return (SetupFileList *)loadSetupFileData(filename, FALSE);
1520 }
1521
1522 SetupFileHash *loadSetupFileHash(char *filename)
1523 {
1524   return (SetupFileHash *)loadSetupFileData(filename, TRUE);
1525 }
1526
1527 void checkSetupFileHashIdentifier(SetupFileHash *setup_file_hash,
1528                                   char *identifier)
1529 {
1530   char *value = getHashEntry(setup_file_hash, TOKEN_STR_FILE_IDENTIFIER);
1531
1532   if (value == NULL)
1533     Error(ERR_WARN, "configuration file has no file identifier");
1534   else if (!checkCookieString(value, identifier))
1535     Error(ERR_WARN, "configuration file has wrong file identifier");
1536 }
1537
1538
1539 /* ========================================================================= */
1540 /* setup file stuff                                                          */
1541 /* ========================================================================= */
1542
1543 #define TOKEN_STR_LAST_LEVEL_SERIES     "last_level_series"
1544 #define TOKEN_STR_LAST_PLAYED_LEVEL     "last_played_level"
1545 #define TOKEN_STR_HANDICAP_LEVEL        "handicap_level"
1546
1547 /* level directory info */
1548 #define LEVELINFO_TOKEN_IDENTIFIER      0
1549 #define LEVELINFO_TOKEN_NAME            1
1550 #define LEVELINFO_TOKEN_NAME_SORTING    2
1551 #define LEVELINFO_TOKEN_AUTHOR          3
1552 #define LEVELINFO_TOKEN_IMPORTED_FROM   4
1553 #define LEVELINFO_TOKEN_LEVELS          5
1554 #define LEVELINFO_TOKEN_FIRST_LEVEL     6
1555 #define LEVELINFO_TOKEN_SORT_PRIORITY   7
1556 #define LEVELINFO_TOKEN_LATEST_ENGINE   8
1557 #define LEVELINFO_TOKEN_LEVEL_GROUP     9
1558 #define LEVELINFO_TOKEN_READONLY        10
1559 #define LEVELINFO_TOKEN_GRAPHICS_SET    11
1560 #define LEVELINFO_TOKEN_SOUNDS_SET      12
1561 #define LEVELINFO_TOKEN_MUSIC_SET       13
1562
1563 #define NUM_LEVELINFO_TOKENS            14
1564
1565 static LevelDirTree ldi;
1566
1567 static struct TokenInfo levelinfo_tokens[] =
1568 {
1569   /* level directory info */
1570   { TYPE_STRING,  &ldi.identifier,      "identifier"    },
1571   { TYPE_STRING,  &ldi.name,            "name"          },
1572   { TYPE_STRING,  &ldi.name_sorting,    "name_sorting"  },
1573   { TYPE_STRING,  &ldi.author,          "author"        },
1574   { TYPE_STRING,  &ldi.imported_from,   "imported_from" },
1575   { TYPE_INTEGER, &ldi.levels,          "levels"        },
1576   { TYPE_INTEGER, &ldi.first_level,     "first_level"   },
1577   { TYPE_INTEGER, &ldi.sort_priority,   "sort_priority" },
1578   { TYPE_BOOLEAN, &ldi.latest_engine,   "latest_engine" },
1579   { TYPE_BOOLEAN, &ldi.level_group,     "level_group"   },
1580   { TYPE_BOOLEAN, &ldi.readonly,        "readonly"      },
1581   { TYPE_STRING,  &ldi.graphics_set,    "graphics_set"  },
1582   { TYPE_STRING,  &ldi.sounds_set,      "sounds_set"    },
1583   { TYPE_STRING,  &ldi.music_set,       "music_set"     }
1584 };
1585
1586 static void setTreeInfoToDefaults(TreeInfo *ldi, int type)
1587 {
1588   ldi->type = type;
1589
1590   ldi->node_top = (ldi->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
1591                    ldi->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
1592                    ldi->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
1593                    ldi->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
1594                    NULL);
1595
1596   ldi->node_parent = NULL;
1597   ldi->node_group = NULL;
1598   ldi->next = NULL;
1599
1600   ldi->cl_first = -1;
1601   ldi->cl_cursor = -1;
1602
1603   ldi->filename = NULL;
1604   ldi->fullpath = NULL;
1605   ldi->basepath = NULL;
1606   ldi->identifier = NULL;
1607   ldi->name = getStringCopy(ANONYMOUS_NAME);
1608   ldi->name_sorting = NULL;
1609   ldi->author = getStringCopy(ANONYMOUS_NAME);
1610
1611   ldi->sort_priority = LEVELCLASS_UNDEFINED;    /* default: least priority */
1612   ldi->latest_engine = FALSE;                   /* default: get from level */
1613   ldi->parent_link = FALSE;
1614   ldi->user_defined = FALSE;
1615   ldi->color = 0;
1616   ldi->class_desc = NULL;
1617
1618   if (ldi->type == TREE_TYPE_LEVEL_DIR)
1619   {
1620     ldi->imported_from = NULL;
1621
1622     ldi->graphics_set = NULL;
1623     ldi->sounds_set = NULL;
1624     ldi->music_set = NULL;
1625     ldi->graphics_path = getStringCopy(UNDEFINED_FILENAME);
1626     ldi->sounds_path = getStringCopy(UNDEFINED_FILENAME);
1627     ldi->music_path = getStringCopy(UNDEFINED_FILENAME);
1628
1629     ldi->levels = 0;
1630     ldi->first_level = 0;
1631     ldi->last_level = 0;
1632     ldi->level_group = FALSE;
1633     ldi->handicap_level = 0;
1634     ldi->readonly = TRUE;
1635   }
1636 }
1637
1638 static void setTreeInfoToDefaultsFromParent(TreeInfo *ldi, TreeInfo *parent)
1639 {
1640   if (parent == NULL)
1641   {
1642     Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
1643
1644     setTreeInfoToDefaults(ldi, TREE_TYPE_UNDEFINED);
1645
1646     return;
1647   }
1648
1649 #if 1
1650   /* copy all values from the parent structure */
1651
1652   ldi->type = parent->type;
1653
1654   ldi->node_top = parent->node_top;
1655   ldi->node_parent = parent;
1656   ldi->node_group = NULL;
1657   ldi->next = NULL;
1658
1659   ldi->cl_first = -1;
1660   ldi->cl_cursor = -1;
1661
1662   ldi->filename = NULL;
1663   ldi->fullpath = NULL;
1664   ldi->basepath = NULL;
1665   ldi->identifier = NULL;
1666   ldi->name = getStringCopy(ANONYMOUS_NAME);
1667   ldi->name_sorting = NULL;
1668   ldi->author = getStringCopy(parent->author);
1669
1670   ldi->sort_priority = parent->sort_priority;
1671   ldi->latest_engine = parent->latest_engine;
1672   ldi->parent_link = FALSE;
1673   ldi->user_defined = parent->user_defined;
1674   ldi->color = parent->color;
1675   ldi->class_desc = getStringCopy(parent->class_desc);
1676
1677   if (ldi->type == TREE_TYPE_LEVEL_DIR)
1678   {
1679     ldi->imported_from = getStringCopy(parent->imported_from);
1680
1681     ldi->graphics_set = NULL;
1682     ldi->sounds_set = NULL;
1683     ldi->music_set = NULL;
1684     ldi->graphics_path = getStringCopy(UNDEFINED_FILENAME);
1685     ldi->sounds_path = getStringCopy(UNDEFINED_FILENAME);
1686     ldi->music_path = getStringCopy(UNDEFINED_FILENAME);
1687
1688     ldi->levels = 0;
1689     ldi->first_level = 0;
1690     ldi->last_level = 0;
1691     ldi->level_group = FALSE;
1692     ldi->handicap_level = 0;
1693     ldi->readonly = TRUE;
1694   }
1695
1696
1697 #else
1698
1699   /* first copy all values from the parent structure ... */
1700   *ldi = *parent;
1701
1702   /* ... then set all fields to default that cannot be inherited from parent.
1703      This is especially important for all those fields that can be set from
1704      the 'levelinfo.conf' config file, because the function 'setSetupInfo()'
1705      calls 'free()' for all already set token values which requires that no
1706      other structure's pointer may point to them!
1707   */
1708
1709   ldi->filename = NULL;
1710   ldi->fullpath = NULL;
1711   ldi->basepath = NULL;
1712   ldi->identifier = NULL;
1713   ldi->name = getStringCopy(ANONYMOUS_NAME);
1714   ldi->name_sorting = NULL;
1715   ldi->author = getStringCopy(parent->author);
1716
1717   ldi->imported_from = getStringCopy(parent->imported_from);
1718   ldi->class_desc = getStringCopy(parent->class_desc);
1719
1720   ldi->graphics_set = NULL;
1721   ldi->sounds_set = NULL;
1722   ldi->music_set = NULL;
1723   ldi->graphics_path = NULL;
1724   ldi->sounds_path = NULL;
1725   ldi->music_path = NULL;
1726
1727   ldi->level_group = FALSE;
1728   ldi->parent_link = FALSE;
1729
1730   ldi->node_top = parent->node_top;
1731   ldi->node_parent = parent;
1732   ldi->node_group = NULL;
1733   ldi->next = NULL;
1734
1735 #endif
1736 }
1737
1738 static void freeTreeInfo(TreeInfo *ldi)
1739 {
1740   if (ldi->filename)
1741     free(ldi->filename);
1742   if (ldi->fullpath)
1743     free(ldi->fullpath);
1744   if (ldi->basepath)
1745     free(ldi->basepath);
1746   if (ldi->identifier)
1747     free(ldi->identifier);
1748
1749   if (ldi->name)
1750     free(ldi->name);
1751   if (ldi->name_sorting)
1752     free(ldi->name_sorting);
1753   if (ldi->author)
1754     free(ldi->author);
1755
1756   if (ldi->class_desc)
1757     free(ldi->class_desc);
1758
1759   if (ldi->type == TREE_TYPE_LEVEL_DIR)
1760   {
1761     if (ldi->graphics_set)
1762       free(ldi->graphics_set);
1763     if (ldi->sounds_set)
1764       free(ldi->sounds_set);
1765     if (ldi->music_set)
1766       free(ldi->music_set);
1767
1768     if (ldi->graphics_path)
1769       free(ldi->graphics_path);
1770     if (ldi->sounds_path)
1771       free(ldi->sounds_path);
1772     if (ldi->music_path)
1773       free(ldi->music_path);
1774   }
1775 }
1776
1777 void setSetupInfo(struct TokenInfo *token_info,
1778                   int token_nr, char *token_value)
1779 {
1780   int token_type = token_info[token_nr].type;
1781   void *setup_value = token_info[token_nr].value;
1782
1783   if (token_value == NULL)
1784     return;
1785
1786   /* set setup field to corresponding token value */
1787   switch (token_type)
1788   {
1789     case TYPE_BOOLEAN:
1790     case TYPE_SWITCH:
1791       *(boolean *)setup_value = get_boolean_from_string(token_value);
1792       break;
1793
1794     case TYPE_KEY:
1795       *(Key *)setup_value = getKeyFromKeyName(token_value);
1796       break;
1797
1798     case TYPE_KEY_X11:
1799       *(Key *)setup_value = getKeyFromX11KeyName(token_value);
1800       break;
1801
1802     case TYPE_INTEGER:
1803       *(int *)setup_value = get_integer_from_string(token_value);
1804       break;
1805
1806     case TYPE_STRING:
1807       if (*(char **)setup_value != NULL)
1808         free(*(char **)setup_value);
1809       *(char **)setup_value = getStringCopy(token_value);
1810       break;
1811
1812     default:
1813       break;
1814   }
1815 }
1816
1817 static int compareTreeInfoEntries(const void *object1, const void *object2)
1818 {
1819   const TreeInfo *entry1 = *((TreeInfo **)object1);
1820   const TreeInfo *entry2 = *((TreeInfo **)object2);
1821   int class_sorting1, class_sorting2;
1822   int compare_result;
1823
1824   if (entry1->type == TREE_TYPE_LEVEL_DIR)
1825   {
1826     class_sorting1 = LEVELSORTING(entry1);
1827     class_sorting2 = LEVELSORTING(entry2);
1828   }
1829   else
1830   {
1831     class_sorting1 = ARTWORKSORTING(entry1);
1832     class_sorting2 = ARTWORKSORTING(entry2);
1833   }
1834
1835   if (entry1->parent_link || entry2->parent_link)
1836     compare_result = (entry1->parent_link ? -1 : +1);
1837   else if (entry1->sort_priority == entry2->sort_priority)
1838   {
1839     char *name1 = getStringToLower(entry1->name_sorting);
1840     char *name2 = getStringToLower(entry2->name_sorting);
1841
1842     compare_result = strcmp(name1, name2);
1843
1844     free(name1);
1845     free(name2);
1846   }
1847   else if (class_sorting1 == class_sorting2)
1848     compare_result = entry1->sort_priority - entry2->sort_priority;
1849   else
1850     compare_result = class_sorting1 - class_sorting2;
1851
1852   return compare_result;
1853 }
1854
1855 static void createParentTreeInfoNode(TreeInfo *node_parent)
1856 {
1857   TreeInfo *ti_new;
1858
1859   if (node_parent == NULL)
1860     return;
1861
1862   ti_new = newTreeInfo();
1863   setTreeInfoToDefaults(ti_new, node_parent->type);
1864
1865   ti_new->node_parent = node_parent;
1866   ti_new->parent_link = TRUE;
1867
1868 #if 1
1869   setString(&ti_new->identifier, node_parent->identifier);
1870   setString(&ti_new->name, ".. (parent directory)");
1871   setString(&ti_new->name_sorting, ti_new->name);
1872
1873   setString(&ti_new->filename, "..");
1874   setString(&ti_new->fullpath, node_parent->fullpath);
1875
1876   ti_new->sort_priority = node_parent->sort_priority;
1877   ti_new->latest_engine = node_parent->latest_engine;
1878
1879   setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
1880 #else
1881   ti_new->identifier = getStringCopy(node_parent->identifier);
1882   ti_new->name = ".. (parent directory)";
1883   ti_new->name_sorting = getStringCopy(ti_new->name);
1884
1885   ti_new->filename = "..";
1886   ti_new->fullpath = getStringCopy(node_parent->fullpath);
1887
1888   ti_new->sort_priority = node_parent->sort_priority;
1889   ti_new->latest_engine = node_parent->latest_engine;
1890
1891   ti_new->class_desc = getLevelClassDescription(ti_new);
1892 #endif
1893
1894   pushTreeInfo(&node_parent->node_group, ti_new);
1895 }
1896
1897 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
1898 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
1899
1900 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
1901                                           TreeInfo *node_parent,
1902                                           char *level_directory,
1903                                           char *directory_name)
1904 {
1905   char *directory_path = getPath2(level_directory, directory_name);
1906   char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
1907   SetupFileHash *setup_file_hash = loadSetupFileHash(filename);
1908   LevelDirTree *leveldir_new = NULL;
1909   int i;
1910
1911   if (setup_file_hash == NULL)
1912   {
1913     Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
1914
1915     free(directory_path);
1916     free(filename);
1917
1918     return FALSE;
1919   }
1920
1921   leveldir_new = newTreeInfo();
1922
1923   if (node_parent)
1924     setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
1925   else
1926     setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
1927
1928   leveldir_new->filename = getStringCopy(directory_name);
1929
1930   checkSetupFileHashIdentifier(setup_file_hash, getCookie("LEVELINFO"));
1931
1932   /* set all structure fields according to the token/value pairs */
1933   ldi = *leveldir_new;
1934   for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
1935     setSetupInfo(levelinfo_tokens, i,
1936                  getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
1937   *leveldir_new = ldi;
1938
1939 #if 1
1940   if (strcmp(leveldir_new->name, ANONYMOUS_NAME) == 0)
1941     setString(&leveldir_new->name, leveldir_new->filename);
1942 #else
1943   if (strcmp(leveldir_new->name, ANONYMOUS_NAME) == 0)
1944   {
1945     free(leveldir_new->name);
1946     leveldir_new->name = getStringCopy(leveldir_new->filename);
1947   }
1948 #endif
1949
1950   DrawInitText(leveldir_new->name, 150, FC_YELLOW);
1951
1952   if (leveldir_new->identifier == NULL)
1953     leveldir_new->identifier = getStringCopy(leveldir_new->filename);
1954
1955   if (leveldir_new->name_sorting == NULL)
1956     leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
1957
1958   if (node_parent == NULL)              /* top level group */
1959   {
1960     leveldir_new->basepath = getStringCopy(level_directory);
1961     leveldir_new->fullpath = getStringCopy(leveldir_new->filename);
1962   }
1963   else                                  /* sub level group */
1964   {
1965     leveldir_new->basepath = getStringCopy(node_parent->basepath);
1966     leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
1967   }
1968
1969   if (leveldir_new->levels < 1)
1970     leveldir_new->levels = 1;
1971
1972   leveldir_new->last_level =
1973     leveldir_new->first_level + leveldir_new->levels - 1;
1974
1975 #if 1
1976   leveldir_new->user_defined =
1977     (strcmp(leveldir_new->basepath, options.level_directory) != 0);
1978 #else
1979   leveldir_new->user_defined =
1980     (leveldir_new->basepath == options.level_directory ? FALSE : TRUE);
1981 #endif
1982
1983   leveldir_new->color = LEVELCOLOR(leveldir_new);
1984 #if 1
1985   setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
1986 #else
1987   leveldir_new->class_desc = getLevelClassDescription(leveldir_new);
1988 #endif
1989
1990   leveldir_new->handicap_level =        /* set handicap to default value */
1991     (leveldir_new->user_defined ?
1992      leveldir_new->last_level :
1993      leveldir_new->first_level);
1994
1995   pushTreeInfo(node_first, leveldir_new);
1996
1997   freeSetupFileHash(setup_file_hash);
1998
1999   if (leveldir_new->level_group)
2000   {
2001     /* create node to link back to current level directory */
2002     createParentTreeInfoNode(leveldir_new);
2003
2004     /* step into sub-directory and look for more level series */
2005     LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
2006                               leveldir_new, directory_path);
2007   }
2008
2009   free(directory_path);
2010   free(filename);
2011
2012   return TRUE;
2013 }
2014
2015 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
2016                                       TreeInfo *node_parent,
2017                                       char *level_directory)
2018 {
2019   DIR *dir;
2020   struct dirent *dir_entry;
2021   boolean valid_entry_found = FALSE;
2022
2023   if ((dir = opendir(level_directory)) == NULL)
2024   {
2025     Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
2026     return;
2027   }
2028
2029   while ((dir_entry = readdir(dir)) != NULL)    /* loop until last dir entry */
2030   {
2031     struct stat file_status;
2032     char *directory_name = dir_entry->d_name;
2033     char *directory_path = getPath2(level_directory, directory_name);
2034
2035     /* skip entries for current and parent directory */
2036     if (strcmp(directory_name, ".")  == 0 ||
2037         strcmp(directory_name, "..") == 0)
2038     {
2039       free(directory_path);
2040       continue;
2041     }
2042
2043     /* find out if directory entry is itself a directory */
2044     if (stat(directory_path, &file_status) != 0 ||      /* cannot stat file */
2045         (file_status.st_mode & S_IFMT) != S_IFDIR)      /* not a directory */
2046     {
2047       free(directory_path);
2048       continue;
2049     }
2050
2051     free(directory_path);
2052
2053     if (strcmp(directory_name, GRAPHICS_DIRECTORY) == 0 ||
2054         strcmp(directory_name, SOUNDS_DIRECTORY) == 0 ||
2055         strcmp(directory_name, MUSIC_DIRECTORY) == 0)
2056       continue;
2057
2058     valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
2059                                                     level_directory,
2060                                                     directory_name);
2061   }
2062
2063   closedir(dir);
2064
2065   if (!valid_entry_found)
2066   {
2067     /* check if this directory directly contains a file "levelinfo.conf" */
2068     valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
2069                                                     level_directory, ".");
2070   }
2071
2072   if (!valid_entry_found)
2073     Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
2074           level_directory);
2075 }
2076
2077 void LoadLevelInfo()
2078 {
2079   InitUserLevelDirectory(getLoginName());
2080
2081   DrawInitText("Loading level series:", 120, FC_GREEN);
2082
2083   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
2084   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
2085
2086   /* before sorting, the first entries will be from the user directory */
2087   leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
2088
2089   if (leveldir_first == NULL)
2090     Error(ERR_EXIT, "cannot find any valid level series in any directory");
2091
2092   sortTreeInfo(&leveldir_first, compareTreeInfoEntries);
2093
2094 #if 0
2095   dumpTreeInfo(leveldir_first, 0);
2096 #endif
2097 }
2098
2099 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
2100                                               TreeInfo *node_parent,
2101                                               char *base_directory,
2102                                               char *directory_name, int type)
2103 {
2104   char *directory_path = getPath2(base_directory, directory_name);
2105   char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
2106   SetupFileHash *setup_file_hash = NULL;
2107   TreeInfo *artwork_new = NULL;
2108   int i;
2109
2110   if (access(filename, F_OK) == 0)              /* file exists */
2111     setup_file_hash = loadSetupFileHash(filename);
2112
2113   if (setup_file_hash == NULL)  /* no config file -- look for artwork files */
2114   {
2115     DIR *dir;
2116     struct dirent *dir_entry;
2117     boolean valid_file_found = FALSE;
2118
2119     if ((dir = opendir(directory_path)) != NULL)
2120     {
2121       while ((dir_entry = readdir(dir)) != NULL)
2122       {
2123         char *entry_name = dir_entry->d_name;
2124
2125         if (FileIsArtworkType(entry_name, type))
2126         {
2127           valid_file_found = TRUE;
2128           break;
2129         }
2130       }
2131
2132       closedir(dir);
2133     }
2134
2135     if (!valid_file_found)
2136     {
2137       if (strcmp(directory_name, ".") != 0)
2138         Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
2139
2140       free(directory_path);
2141       free(filename);
2142
2143       return FALSE;
2144     }
2145   }
2146
2147   artwork_new = newTreeInfo();
2148
2149   if (node_parent)
2150     setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
2151   else
2152     setTreeInfoToDefaults(artwork_new, type);
2153
2154   artwork_new->filename = getStringCopy(directory_name);
2155
2156   if (setup_file_hash)  /* (before defining ".color" and ".class_desc") */
2157   {
2158 #if 0
2159     checkSetupFileHashIdentifier(setup_file_hash, getCookie("..."));
2160 #endif
2161
2162     /* set all structure fields according to the token/value pairs */
2163     ldi = *artwork_new;
2164     for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
2165       setSetupInfo(levelinfo_tokens, i,
2166                    getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
2167     *artwork_new = ldi;
2168
2169 #if 1
2170     if (strcmp(artwork_new->name, ANONYMOUS_NAME) == 0)
2171       setString(&artwork_new->name, artwork_new->filename);
2172 #else
2173     if (strcmp(artwork_new->name, ANONYMOUS_NAME) == 0)
2174     {
2175       free(artwork_new->name);
2176       artwork_new->name = getStringCopy(artwork_new->filename);
2177     }
2178 #endif
2179
2180 #if 0
2181     DrawInitText(artwork_new->name, 150, FC_YELLOW);
2182 #endif
2183
2184     if (artwork_new->identifier == NULL)
2185       artwork_new->identifier = getStringCopy(artwork_new->filename);
2186
2187     if (artwork_new->name_sorting == NULL)
2188       artwork_new->name_sorting = getStringCopy(artwork_new->name);
2189   }
2190
2191   if (node_parent == NULL)              /* top level group */
2192   {
2193     artwork_new->basepath = getStringCopy(base_directory);
2194     artwork_new->fullpath = getStringCopy(artwork_new->filename);
2195   }
2196   else                                  /* sub level group */
2197   {
2198     artwork_new->basepath = getStringCopy(node_parent->basepath);
2199     artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
2200   }
2201
2202 #if 1
2203   artwork_new->user_defined =
2204     (strcmp(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)) != 0);
2205 #else
2206   artwork_new->user_defined =
2207     (artwork_new->basepath == OPTIONS_ARTWORK_DIRECTORY(type) ? FALSE : TRUE);
2208 #endif
2209
2210   /* (may use ".sort_priority" from "setup_file_hash" above) */
2211   artwork_new->color = ARTWORKCOLOR(artwork_new);
2212 #if 1
2213   setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
2214 #else
2215   artwork_new->class_desc = getLevelClassDescription(artwork_new);
2216 #endif
2217
2218   if (setup_file_hash == NULL)  /* (after determining ".user_defined") */
2219   {
2220 #if 0
2221     if (artwork_new->name != NULL)
2222     {
2223       free(artwork_new->name);
2224       artwork_new->name = NULL;
2225     }
2226 #endif
2227
2228 #if 0
2229     if (artwork_new->identifier != NULL)
2230     {
2231       free(artwork_new->identifier);
2232       artwork_new->identifier = NULL;
2233     }
2234 #endif
2235
2236     if (strcmp(artwork_new->filename, ".") == 0)
2237     {
2238       if (artwork_new->user_defined)
2239       {
2240 #if 1
2241         setString(&artwork_new->identifier, "private");
2242 #else
2243         artwork_new->identifier = getStringCopy("private");
2244 #endif
2245         artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
2246       }
2247       else
2248       {
2249 #if 1
2250         setString(&artwork_new->identifier, "classic");
2251 #else
2252         artwork_new->identifier = getStringCopy("classic");
2253 #endif
2254         artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
2255       }
2256
2257       /* set to new values after changing ".sort_priority" */
2258       artwork_new->color = ARTWORKCOLOR(artwork_new);
2259 #if 1
2260       setString(&artwork_new->class_desc,
2261                 getLevelClassDescription(artwork_new));
2262 #else
2263       artwork_new->class_desc = getLevelClassDescription(artwork_new);
2264 #endif
2265     }
2266     else
2267     {
2268 #if 1
2269       setString(&artwork_new->identifier, artwork_new->filename);
2270 #else
2271       artwork_new->identifier = getStringCopy(artwork_new->filename);
2272 #endif
2273     }
2274
2275 #if 1
2276     setString(&artwork_new->name, artwork_new->identifier);
2277     setString(&artwork_new->name_sorting, artwork_new->name);
2278 #else
2279     artwork_new->name = getStringCopy(artwork_new->identifier);
2280     artwork_new->name_sorting = getStringCopy(artwork_new->name);
2281 #endif
2282   }
2283
2284   DrawInitText(artwork_new->name, 150, FC_YELLOW);
2285
2286   pushTreeInfo(node_first, artwork_new);
2287
2288   freeSetupFileHash(setup_file_hash);
2289
2290   free(directory_path);
2291   free(filename);
2292
2293   return TRUE;
2294 }
2295
2296 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
2297                                           TreeInfo *node_parent,
2298                                           char *base_directory, int type)
2299 {
2300   DIR *dir;
2301   struct dirent *dir_entry;
2302   boolean valid_entry_found = FALSE;
2303
2304   if ((dir = opendir(base_directory)) == NULL)
2305   {
2306     if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
2307       Error(ERR_WARN, "cannot read directory '%s'", base_directory);
2308     return;
2309   }
2310
2311   while ((dir_entry = readdir(dir)) != NULL)    /* loop until last dir entry */
2312   {
2313     struct stat file_status;
2314     char *directory_name = dir_entry->d_name;
2315     char *directory_path = getPath2(base_directory, directory_name);
2316
2317     /* skip entries for current and parent directory */
2318     if (strcmp(directory_name, ".")  == 0 ||
2319         strcmp(directory_name, "..") == 0)
2320     {
2321       free(directory_path);
2322       continue;
2323     }
2324
2325     /* find out if directory entry is itself a directory */
2326     if (stat(directory_path, &file_status) != 0 ||      /* cannot stat file */
2327         (file_status.st_mode & S_IFMT) != S_IFDIR)      /* not a directory */
2328     {
2329       free(directory_path);
2330       continue;
2331     }
2332
2333     free(directory_path);
2334
2335     /* check if this directory contains artwork with or without config file */
2336     valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first,node_parent,
2337                                                         base_directory,
2338                                                         directory_name, type);
2339   }
2340
2341   closedir(dir);
2342
2343   /* check if this directory directly contains artwork itself */
2344   valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first,node_parent,
2345                                                       base_directory, ".",
2346                                                       type);
2347   if (!valid_entry_found)
2348     Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
2349           base_directory);
2350 }
2351
2352 static TreeInfo *getDummyArtworkInfo(int type)
2353 {
2354   /* this is only needed when there is completely no artwork available */
2355   TreeInfo *artwork_new = newTreeInfo();
2356
2357   setTreeInfoToDefaults(artwork_new, type);
2358
2359 #if 1
2360   setString(&artwork_new->filename, UNDEFINED_FILENAME);
2361   setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
2362   setString(&artwork_new->basepath, UNDEFINED_FILENAME);
2363
2364   setString(&artwork_new->identifier,   UNDEFINED_FILENAME);
2365   setString(&artwork_new->name,         UNDEFINED_FILENAME);
2366   setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
2367 #else
2368   artwork_new->filename = getStringCopy(UNDEFINED_FILENAME);
2369   artwork_new->fullpath = getStringCopy(UNDEFINED_FILENAME);
2370   artwork_new->basepath = getStringCopy(UNDEFINED_FILENAME);
2371
2372   if (artwork_new->name != NULL)
2373     free(artwork_new->name);
2374
2375   artwork_new->identifier   = getStringCopy(UNDEFINED_FILENAME);
2376   artwork_new->name         = getStringCopy(UNDEFINED_FILENAME);
2377   artwork_new->name_sorting = getStringCopy(UNDEFINED_FILENAME);
2378 #endif
2379
2380   return artwork_new;
2381 }
2382
2383 void LoadArtworkInfo()
2384 {
2385   DrawInitText("Looking for custom artwork:", 120, FC_GREEN);
2386
2387   LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
2388                                 options.graphics_directory,
2389                                 TREE_TYPE_GRAPHICS_DIR);
2390   LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
2391                                 getUserGraphicsDir(),
2392                                 TREE_TYPE_GRAPHICS_DIR);
2393
2394   LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
2395                                 options.sounds_directory,
2396                                 TREE_TYPE_SOUNDS_DIR);
2397   LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
2398                                 getUserSoundsDir(),
2399                                 TREE_TYPE_SOUNDS_DIR);
2400
2401   LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
2402                                 options.music_directory,
2403                                 TREE_TYPE_MUSIC_DIR);
2404   LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
2405                                 getUserMusicDir(),
2406                                 TREE_TYPE_MUSIC_DIR);
2407
2408   if (artwork.gfx_first == NULL)
2409     artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
2410   if (artwork.snd_first == NULL)
2411     artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
2412   if (artwork.mus_first == NULL)
2413     artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
2414
2415   /* before sorting, the first entries will be from the user directory */
2416   artwork.gfx_current =
2417     getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
2418   if (artwork.gfx_current == NULL)
2419     artwork.gfx_current =
2420       getTreeInfoFromIdentifier(artwork.gfx_first, GFX_CLASSIC_SUBDIR);
2421   if (artwork.gfx_current == NULL)
2422     artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
2423
2424   artwork.snd_current =
2425     getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
2426   if (artwork.snd_current == NULL)
2427     artwork.snd_current =
2428       getTreeInfoFromIdentifier(artwork.snd_first, SND_CLASSIC_SUBDIR);
2429   if (artwork.snd_current == NULL)
2430     artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
2431
2432   artwork.mus_current =
2433     getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
2434   if (artwork.mus_current == NULL)
2435     artwork.mus_current =
2436       getTreeInfoFromIdentifier(artwork.mus_first, MUS_CLASSIC_SUBDIR);
2437   if (artwork.mus_current == NULL)
2438     artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
2439
2440   artwork.gfx_current_identifier = artwork.gfx_current->identifier;
2441   artwork.snd_current_identifier = artwork.snd_current->identifier;
2442   artwork.mus_current_identifier = artwork.mus_current->identifier;
2443
2444 #if 0
2445   printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
2446   printf("sounds set == %s\n\n", artwork.snd_current_identifier);
2447   printf("music set == %s\n\n", artwork.mus_current_identifier);
2448 #endif
2449
2450   sortTreeInfo(&artwork.gfx_first, compareTreeInfoEntries);
2451   sortTreeInfo(&artwork.snd_first, compareTreeInfoEntries);
2452   sortTreeInfo(&artwork.mus_first, compareTreeInfoEntries);
2453
2454 #if 0
2455   dumpTreeInfo(artwork.gfx_first, 0);
2456   dumpTreeInfo(artwork.snd_first, 0);
2457   dumpTreeInfo(artwork.mus_first, 0);
2458 #endif
2459 }
2460
2461 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
2462                                   LevelDirTree *level_node)
2463 {
2464   /* recursively check all level directories for artwork sub-directories */
2465
2466   while (level_node)
2467   {
2468     char *path = getPath2(getLevelDirFromTreeInfo(level_node),
2469                           ARTWORK_DIRECTORY((*artwork_node)->type));
2470
2471 #if 0
2472     if (!level_node->parent_link)
2473       printf("CHECKING '%s' ['%s', '%s'] ...\n", path,
2474              level_node->filename, level_node->name);
2475 #endif
2476
2477     if (!level_node->parent_link)
2478     {
2479       TreeInfo *topnode_last = *artwork_node;
2480
2481       LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path,
2482                                     (*artwork_node)->type);
2483
2484       if (topnode_last != *artwork_node)
2485       {
2486         free((*artwork_node)->identifier);
2487         free((*artwork_node)->name);
2488         free((*artwork_node)->name_sorting);
2489
2490         (*artwork_node)->identifier   = getStringCopy(level_node->filename);
2491         (*artwork_node)->name         = getStringCopy(level_node->name);
2492         (*artwork_node)->name_sorting = getStringCopy(level_node->name);
2493
2494         (*artwork_node)->sort_priority = level_node->sort_priority;
2495         (*artwork_node)->color = LEVELCOLOR((*artwork_node));
2496       }
2497     }
2498
2499     free(path);
2500
2501     if (level_node->node_group != NULL)
2502       LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
2503
2504     level_node = level_node->next;
2505   }
2506 }
2507
2508 void LoadLevelArtworkInfo()
2509 {
2510   DrawInitText("Looking for custom level artwork:", 120, FC_GREEN);
2511
2512   LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first);
2513   LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first);
2514   LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first);
2515
2516   /* needed for reloading level artwork not known at ealier stage */
2517
2518   if (strcmp(artwork.gfx_current_identifier, setup.graphics_set) != 0)
2519   {
2520     artwork.gfx_current =
2521       getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
2522     if (artwork.gfx_current == NULL)
2523       artwork.gfx_current =
2524         getTreeInfoFromIdentifier(artwork.gfx_first, GFX_CLASSIC_SUBDIR);
2525     if (artwork.gfx_current == NULL)
2526       artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
2527   }
2528
2529   if (strcmp(artwork.snd_current_identifier, setup.sounds_set) != 0)
2530   {
2531     artwork.snd_current =
2532       getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
2533     if (artwork.snd_current == NULL)
2534       artwork.snd_current =
2535         getTreeInfoFromIdentifier(artwork.snd_first, SND_CLASSIC_SUBDIR);
2536     if (artwork.snd_current == NULL)
2537       artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
2538   }
2539
2540   if (strcmp(artwork.mus_current_identifier, setup.music_set) != 0)
2541   {
2542     artwork.mus_current =
2543       getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
2544     if (artwork.mus_current == NULL)
2545       artwork.mus_current =
2546         getTreeInfoFromIdentifier(artwork.mus_first, MUS_CLASSIC_SUBDIR);
2547     if (artwork.mus_current == NULL)
2548       artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
2549   }
2550
2551   sortTreeInfo(&artwork.gfx_first, compareTreeInfoEntries);
2552   sortTreeInfo(&artwork.snd_first, compareTreeInfoEntries);
2553   sortTreeInfo(&artwork.mus_first, compareTreeInfoEntries);
2554
2555 #if 0
2556   dumpTreeInfo(artwork.gfx_first, 0);
2557   dumpTreeInfo(artwork.snd_first, 0);
2558   dumpTreeInfo(artwork.mus_first, 0);
2559 #endif
2560 }
2561
2562 static void SaveUserLevelInfo()
2563 {
2564   LevelDirTree *level_info;
2565   char *filename;
2566   FILE *file;
2567   int i;
2568
2569   filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
2570
2571   if (!(file = fopen(filename, MODE_WRITE)))
2572   {
2573     Error(ERR_WARN, "cannot write level info file '%s'", filename);
2574     free(filename);
2575     return;
2576   }
2577
2578   level_info = newTreeInfo();
2579
2580   /* always start with reliable default values */
2581   setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
2582
2583 #if 1
2584   setString(&level_info->name, getLoginName());
2585   setString(&level_info->author, getRealName());
2586   level_info->levels = 100;
2587   level_info->first_level = 1;
2588   level_info->sort_priority = LEVELCLASS_PRIVATE_START;
2589   level_info->readonly = FALSE;
2590   setString(&level_info->graphics_set, GFX_CLASSIC_SUBDIR);
2591   setString(&level_info->sounds_set,   SND_CLASSIC_SUBDIR);
2592   setString(&level_info->music_set,    MUS_CLASSIC_SUBDIR);
2593 #else
2594   ldi.name = getStringCopy(getLoginName());
2595   ldi.author = getStringCopy(getRealName());
2596   ldi.levels = 100;
2597   ldi.first_level = 1;
2598   ldi.sort_priority = LEVELCLASS_PRIVATE_START;
2599   ldi.readonly = FALSE;
2600   ldi.graphics_set = getStringCopy(GFX_CLASSIC_SUBDIR);
2601   ldi.sounds_set = getStringCopy(SND_CLASSIC_SUBDIR);
2602   ldi.music_set = getStringCopy(MUS_CLASSIC_SUBDIR);
2603 #endif
2604
2605   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
2606                                                  getCookie("LEVELINFO")));
2607
2608   ldi = *level_info;
2609   for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
2610     if (i != LEVELINFO_TOKEN_IDENTIFIER &&
2611         i != LEVELINFO_TOKEN_NAME_SORTING &&
2612         i != LEVELINFO_TOKEN_IMPORTED_FROM)
2613       fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
2614
2615   fclose(file);
2616
2617   SetFilePermissions(filename, PERMS_PRIVATE);
2618
2619   freeTreeInfo(level_info);
2620   free(filename);
2621 }
2622
2623 char *getSetupValue(int type, void *value)
2624 {
2625   static char value_string[MAX_LINE_LEN];
2626
2627   if (value == NULL)
2628     return NULL;
2629
2630   switch (type)
2631   {
2632     case TYPE_BOOLEAN:
2633       strcpy(value_string, (*(boolean *)value ? "true" : "false"));
2634       break;
2635
2636     case TYPE_SWITCH:
2637       strcpy(value_string, (*(boolean *)value ? "on" : "off"));
2638       break;
2639
2640     case TYPE_YES_NO:
2641       strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
2642       break;
2643
2644     case TYPE_KEY:
2645       strcpy(value_string, getKeyNameFromKey(*(Key *)value));
2646       break;
2647
2648     case TYPE_KEY_X11:
2649       strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
2650       break;
2651
2652     case TYPE_INTEGER:
2653       sprintf(value_string, "%d", *(int *)value);
2654       break;
2655
2656     case TYPE_STRING:
2657       strcpy(value_string, *(char **)value);
2658       break;
2659
2660     default:
2661       value_string[0] = '\0';
2662       break;
2663   }
2664
2665   return value_string;
2666 }
2667
2668 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
2669 {
2670   int i;
2671   char *line;
2672   static char token_string[MAX_LINE_LEN];
2673   int token_type = token_info[token_nr].type;
2674   void *setup_value = token_info[token_nr].value;
2675   char *token_text = token_info[token_nr].text;
2676   char *value_string = getSetupValue(token_type, setup_value);
2677
2678   /* build complete token string */
2679   sprintf(token_string, "%s%s", prefix, token_text);
2680
2681   /* build setup entry line */
2682   line = getFormattedSetupEntry(token_string, value_string);
2683
2684   if (token_type == TYPE_KEY_X11)
2685   {
2686     Key key = *(Key *)setup_value;
2687     char *keyname = getKeyNameFromKey(key);
2688
2689     /* add comment, if useful */
2690     if (strcmp(keyname, "(undefined)") != 0 &&
2691         strcmp(keyname, "(unknown)") != 0)
2692     {
2693       /* add at least one whitespace */
2694       strcat(line, " ");
2695       for (i = strlen(line); i < TOKEN_COMMENT_POSITION; i++)
2696         strcat(line, " ");
2697
2698       strcat(line, "# ");
2699       strcat(line, keyname);
2700     }
2701   }
2702
2703   return line;
2704 }
2705
2706 void LoadLevelSetup_LastSeries()
2707 {
2708   /* ----------------------------------------------------------------------- */
2709   /* ~/.<program>/levelsetup.conf                                            */
2710   /* ----------------------------------------------------------------------- */
2711
2712   char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
2713   SetupFileHash *level_setup_hash = NULL;
2714
2715   /* always start with reliable default values */
2716   leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
2717
2718   if ((level_setup_hash = loadSetupFileHash(filename)))
2719   {
2720     char *last_level_series =
2721       getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
2722
2723     leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
2724                                                  last_level_series);
2725     if (leveldir_current == NULL)
2726       leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
2727
2728     checkSetupFileHashIdentifier(level_setup_hash, getCookie("LEVELSETUP"));
2729
2730     freeSetupFileHash(level_setup_hash);
2731   }
2732   else
2733     Error(ERR_WARN, "using default setup values");
2734
2735   free(filename);
2736 }
2737
2738 void SaveLevelSetup_LastSeries()
2739 {
2740   /* ----------------------------------------------------------------------- */
2741   /* ~/.<program>/levelsetup.conf                                            */
2742   /* ----------------------------------------------------------------------- */
2743
2744   char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
2745   char *level_subdir = leveldir_current->filename;
2746   FILE *file;
2747
2748   InitUserDataDirectory();
2749
2750   if (!(file = fopen(filename, MODE_WRITE)))
2751   {
2752     Error(ERR_WARN, "cannot write setup file '%s'", filename);
2753     free(filename);
2754     return;
2755   }
2756
2757   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
2758                                                  getCookie("LEVELSETUP")));
2759   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
2760                                                level_subdir));
2761
2762   fclose(file);
2763
2764   SetFilePermissions(filename, PERMS_PRIVATE);
2765
2766   free(filename);
2767 }
2768
2769 static void checkSeriesInfo()
2770 {
2771   static char *level_directory = NULL;
2772   DIR *dir;
2773   struct dirent *dir_entry;
2774
2775   /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
2776
2777   level_directory = getPath2((leveldir_current->user_defined ?
2778                               getUserLevelDir(NULL) :
2779                               options.level_directory),
2780                              leveldir_current->fullpath);
2781
2782   if ((dir = opendir(level_directory)) == NULL)
2783   {
2784     Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
2785     return;
2786   }
2787
2788   while ((dir_entry = readdir(dir)) != NULL)    /* last directory entry */
2789   {
2790     if (strlen(dir_entry->d_name) > 4 &&
2791         dir_entry->d_name[3] == '.' &&
2792         strcmp(&dir_entry->d_name[4], LEVELFILE_EXTENSION) == 0)
2793     {
2794       char levelnum_str[4];
2795       int levelnum_value;
2796
2797       strncpy(levelnum_str, dir_entry->d_name, 3);
2798       levelnum_str[3] = '\0';
2799
2800       levelnum_value = atoi(levelnum_str);
2801
2802 #if 0
2803       if (levelnum_value < leveldir_current->first_level)
2804       {
2805         Error(ERR_WARN, "additional level %d found", levelnum_value);
2806         leveldir_current->first_level = levelnum_value;
2807       }
2808       else if (levelnum_value > leveldir_current->last_level)
2809       {
2810         Error(ERR_WARN, "additional level %d found", levelnum_value);
2811         leveldir_current->last_level = levelnum_value;
2812       }
2813 #endif
2814     }
2815   }
2816
2817   closedir(dir);
2818 }
2819
2820 void LoadLevelSetup_SeriesInfo()
2821 {
2822   char *filename;
2823   SetupFileHash *level_setup_hash = NULL;
2824   char *level_subdir = leveldir_current->filename;
2825
2826   /* always start with reliable default values */
2827   level_nr = leveldir_current->first_level;
2828
2829   checkSeriesInfo(leveldir_current);
2830
2831   /* ----------------------------------------------------------------------- */
2832   /* ~/.<program>/levelsetup/<level series>/levelsetup.conf                  */
2833   /* ----------------------------------------------------------------------- */
2834
2835   level_subdir = leveldir_current->filename;
2836
2837   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
2838
2839   if ((level_setup_hash = loadSetupFileHash(filename)))
2840   {
2841     char *token_value;
2842
2843     token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
2844
2845     if (token_value)
2846     {
2847       level_nr = atoi(token_value);
2848
2849       if (level_nr < leveldir_current->first_level)
2850         level_nr = leveldir_current->first_level;
2851       if (level_nr > leveldir_current->last_level)
2852         level_nr = leveldir_current->last_level;
2853     }
2854
2855     token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
2856
2857     if (token_value)
2858     {
2859       int level_nr = atoi(token_value);
2860
2861       if (level_nr < leveldir_current->first_level)
2862         level_nr = leveldir_current->first_level;
2863       if (level_nr > leveldir_current->last_level + 1)
2864         level_nr = leveldir_current->last_level;
2865
2866       if (leveldir_current->user_defined)
2867         level_nr = leveldir_current->last_level;
2868
2869       leveldir_current->handicap_level = level_nr;
2870     }
2871
2872     checkSetupFileHashIdentifier(level_setup_hash, getCookie("LEVELSETUP"));
2873
2874     freeSetupFileHash(level_setup_hash);
2875   }
2876   else
2877     Error(ERR_WARN, "using default setup values");
2878
2879   free(filename);
2880 }
2881
2882 void SaveLevelSetup_SeriesInfo()
2883 {
2884   char *filename;
2885   char *level_subdir = leveldir_current->filename;
2886   char *level_nr_str = int2str(level_nr, 0);
2887   char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
2888   FILE *file;
2889
2890   /* ----------------------------------------------------------------------- */
2891   /* ~/.<program>/levelsetup/<level series>/levelsetup.conf                  */
2892   /* ----------------------------------------------------------------------- */
2893
2894   InitLevelSetupDirectory(level_subdir);
2895
2896   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
2897
2898   if (!(file = fopen(filename, MODE_WRITE)))
2899   {
2900     Error(ERR_WARN, "cannot write setup file '%s'", filename);
2901     free(filename);
2902     return;
2903   }
2904
2905   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
2906                                                  getCookie("LEVELSETUP")));
2907   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
2908                                                level_nr_str));
2909   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
2910                                                handicap_level_str));
2911
2912   fclose(file);
2913
2914   SetFilePermissions(filename, PERMS_PRIVATE);
2915
2916   free(filename);
2917 }