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