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