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