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