rnd-20040430-1-src
[rocksndiamonds.git] / src / libgame / setup.c
1 /***********************************************************
2 * Artsoft Retro-Game Library                               *
3 *----------------------------------------------------------*
4 * (c) 1994-2002 Artsoft Entertainment                      *
5 *               Holger Schemel                             *
6 *               Detmolder Strasse 189                      *
7 *               33604 Bielefeld                            *
8 *               Germany                                    *
9 *               e-mail: info@artsoft.org                   *
10 *----------------------------------------------------------*
11 * setup.c                                                  *
12 ***********************************************************/
13
14 #include <sys/types.h>
15 #include <sys/stat.h>
16 #include <dirent.h>
17 #include <string.h>
18 #include <unistd.h>
19
20 #include "setup.h"
21 #include "joystick.h"
22 #include "text.h"
23 #include "misc.h"
24 #include "hash.h"
25
26
27 #define NUM_LEVELCLASS_DESC     8
28
29 static char *levelclass_desc[NUM_LEVELCLASS_DESC] =
30 {
31   "Tutorial Levels",
32   "Classic Originals",
33   "Contributions",
34   "Private Levels",
35   "Boulderdash",
36   "Emerald Mine",
37   "Supaplex",
38   "DX Boulderdash"
39 };
40
41
42 #define LEVELCOLOR(n)   (IS_LEVELCLASS_TUTORIAL(n) ?            FC_BLUE :    \
43                          IS_LEVELCLASS_CLASSICS(n) ?            FC_RED :     \
44                          IS_LEVELCLASS_BD(n) ?                  FC_GREEN :   \
45                          IS_LEVELCLASS_EM(n) ?                  FC_YELLOW :  \
46                          IS_LEVELCLASS_SP(n) ?                  FC_GREEN :   \
47                          IS_LEVELCLASS_DX(n) ?                  FC_YELLOW :  \
48                          IS_LEVELCLASS_CONTRIB(n) ?             FC_GREEN :   \
49                          IS_LEVELCLASS_PRIVATE(n) ?             FC_RED :     \
50                          FC_BLUE)
51
52 #define LEVELSORTING(n) (IS_LEVELCLASS_TUTORIAL(n) ?            0 :     \
53                          IS_LEVELCLASS_CLASSICS(n) ?            1 :     \
54                          IS_LEVELCLASS_BD(n) ?                  2 :     \
55                          IS_LEVELCLASS_EM(n) ?                  3 :     \
56                          IS_LEVELCLASS_SP(n) ?                  4 :     \
57                          IS_LEVELCLASS_DX(n) ?                  5 :     \
58                          IS_LEVELCLASS_CONTRIB(n) ?             6 :     \
59                          IS_LEVELCLASS_PRIVATE(n) ?             7 :     \
60                          9)
61
62 #define ARTWORKCOLOR(n) (IS_ARTWORKCLASS_CLASSICS(n) ?          FC_RED :     \
63                          IS_ARTWORKCLASS_CONTRIB(n) ?           FC_YELLOW :  \
64                          IS_ARTWORKCLASS_PRIVATE(n) ?           FC_RED :     \
65                          IS_ARTWORKCLASS_LEVEL(n) ?             FC_GREEN :   \
66                          FC_BLUE)
67
68 #define ARTWORKSORTING(n) (IS_ARTWORKCLASS_CLASSICS(n) ?        0 :     \
69                            IS_ARTWORKCLASS_LEVEL(n) ?           1 :     \
70                            IS_ARTWORKCLASS_CONTRIB(n) ?         2 :     \
71                            IS_ARTWORKCLASS_PRIVATE(n) ?         3 :     \
72                            9)
73
74 #define TOKEN_VALUE_POSITION            40
75 #define TOKEN_COMMENT_POSITION          60
76
77 #define MAX_COOKIE_LEN                  256
78
79
80 /* ------------------------------------------------------------------------- */
81 /* file functions                                                            */
82 /* ------------------------------------------------------------------------- */
83
84 static char *getLevelClassDescription(TreeInfo *ldi)
85 {
86   int position = ldi->sort_priority / 100;
87
88   if (position >= 0 && position < NUM_LEVELCLASS_DESC)
89     return levelclass_desc[position];
90   else
91     return "Unknown Level Class";
92 }
93
94 static char *getUserLevelDir(char *level_subdir)
95 {
96   static char *userlevel_dir = NULL;
97   char *data_dir = getUserDataDir();
98   char *userlevel_subdir = LEVELS_DIRECTORY;
99
100   checked_free(userlevel_dir);
101
102   if (level_subdir != NULL)
103     userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir);
104   else
105     userlevel_dir = getPath2(data_dir, userlevel_subdir);
106
107   return userlevel_dir;
108 }
109
110 static char *getScoreDir(char *level_subdir)
111 {
112   static char *score_dir = NULL;
113   char *data_dir = getCommonDataDir();
114   char *score_subdir = SCORES_DIRECTORY;
115
116   checked_free(score_dir);
117
118   if (level_subdir != NULL)
119     score_dir = getPath3(data_dir, score_subdir, level_subdir);
120   else
121     score_dir = getPath2(data_dir, score_subdir);
122
123   return score_dir;
124 }
125
126 static char *getLevelSetupDir(char *level_subdir)
127 {
128   static char *levelsetup_dir = NULL;
129   char *data_dir = getUserDataDir();
130   char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
131
132   checked_free(levelsetup_dir);
133
134   if (level_subdir != NULL)
135     levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir);
136   else
137     levelsetup_dir = getPath2(data_dir, levelsetup_subdir);
138
139   return levelsetup_dir;
140 }
141
142 static char *getLevelDirFromTreeInfo(TreeInfo *node)
143 {
144   static char *level_dir = NULL;
145
146   if (node == NULL)
147     return options.level_directory;
148
149   checked_free(level_dir);
150
151   level_dir = getPath2((node->user_defined ? getUserLevelDir(NULL) :
152                         options.level_directory), node->fullpath);
153
154   return level_dir;
155 }
156
157 char *getCurrentLevelDir()
158 {
159   return getLevelDirFromTreeInfo(leveldir_current);
160 }
161
162 static char *getTapeDir(char *level_subdir)
163 {
164   static char *tape_dir = NULL;
165   char *data_dir = getUserDataDir();
166   char *tape_subdir = TAPES_DIRECTORY;
167
168   checked_free(tape_dir);
169
170   if (level_subdir != NULL)
171     tape_dir = getPath3(data_dir, tape_subdir, level_subdir);
172   else
173     tape_dir = getPath2(data_dir, tape_subdir);
174
175   return tape_dir;
176 }
177
178 static char *getSolutionTapeDir()
179 {
180   static char *tape_dir = NULL;
181   char *data_dir = getCurrentLevelDir();
182   char *tape_subdir = TAPES_DIRECTORY;
183
184   checked_free(tape_dir);
185
186   tape_dir = getPath2(data_dir, tape_subdir);
187
188   return tape_dir;
189 }
190
191 static char *getDefaultGraphicsDir(char *graphics_subdir)
192 {
193   static char *graphics_dir = NULL;
194
195   if (graphics_subdir == NULL)
196     return options.graphics_directory;
197
198   checked_free(graphics_dir);
199
200   graphics_dir = getPath2(options.graphics_directory, graphics_subdir);
201
202   return graphics_dir;
203 }
204
205 static char *getDefaultSoundsDir(char *sounds_subdir)
206 {
207   static char *sounds_dir = NULL;
208
209   if (sounds_subdir == NULL)
210     return options.sounds_directory;
211
212   checked_free(sounds_dir);
213
214   sounds_dir = getPath2(options.sounds_directory, sounds_subdir);
215
216   return sounds_dir;
217 }
218
219 static char *getDefaultMusicDir(char *music_subdir)
220 {
221   static char *music_dir = NULL;
222
223   if (music_subdir == NULL)
224     return options.music_directory;
225
226   checked_free(music_dir);
227
228   music_dir = getPath2(options.music_directory, music_subdir);
229
230   return music_dir;
231 }
232
233 static char *getDefaultArtworkSet(int type)
234 {
235   return (type == TREE_TYPE_GRAPHICS_DIR ? GFX_CLASSIC_SUBDIR :
236           type == TREE_TYPE_SOUNDS_DIR   ? SND_CLASSIC_SUBDIR :
237           type == TREE_TYPE_MUSIC_DIR    ? MUS_CLASSIC_SUBDIR : "");
238 }
239
240 static char *getDefaultArtworkDir(int type)
241 {
242   return (type == TREE_TYPE_GRAPHICS_DIR ?
243           getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR) :
244           type == TREE_TYPE_SOUNDS_DIR ?
245           getDefaultSoundsDir(SND_CLASSIC_SUBDIR) :
246           type == TREE_TYPE_MUSIC_DIR ?
247           getDefaultMusicDir(MUS_CLASSIC_SUBDIR) : "");
248 }
249
250 static char *getUserGraphicsDir()
251 {
252   static char *usergraphics_dir = NULL;
253
254   if (usergraphics_dir == NULL)
255     usergraphics_dir = getPath2(getUserDataDir(), GRAPHICS_DIRECTORY);
256
257   return usergraphics_dir;
258 }
259
260 static char *getUserSoundsDir()
261 {
262   static char *usersounds_dir = NULL;
263
264   if (usersounds_dir == NULL)
265     usersounds_dir = getPath2(getUserDataDir(), SOUNDS_DIRECTORY);
266
267   return usersounds_dir;
268 }
269
270 static char *getUserMusicDir()
271 {
272   static char *usermusic_dir = NULL;
273
274   if (usermusic_dir == NULL)
275     usermusic_dir = getPath2(getUserDataDir(), MUSIC_DIRECTORY);
276
277   return usermusic_dir;
278 }
279
280 static char *getSetupArtworkDir(TreeInfo *ti)
281 {
282   static char *artwork_dir = NULL;
283
284   checked_free(artwork_dir);
285
286   artwork_dir = getPath2(ti->basepath, ti->fullpath);
287
288   return artwork_dir;
289 }
290
291 char *setLevelArtworkDir(TreeInfo *ti)
292 {
293   char **artwork_path_ptr, **artwork_set_ptr;
294   TreeInfo *level_artwork;
295
296   if (ti == NULL || leveldir_current == NULL)
297     return NULL;
298
299   artwork_path_ptr = &(LEVELDIR_ARTWORK_PATH(leveldir_current, ti->type));
300   artwork_set_ptr  = &(LEVELDIR_ARTWORK_SET( leveldir_current, ti->type));
301
302   checked_free(*artwork_path_ptr);
303
304   if ((level_artwork = getTreeInfoFromIdentifier(ti, *artwork_set_ptr)))
305     *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
306   else
307   {
308     /* No (or non-existing) artwork configured in "levelinfo.conf". This would
309        normally result in using the artwork configured in the setup menu. But
310        if an artwork subdirectory exists (which might contain custom artwork
311        or an artwork configuration file), this level artwork must be treated
312        as relative to the default "classic" artwork, not to the artwork that
313        is currently configured in the setup menu. */
314
315     char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
316
317     checked_free(*artwork_set_ptr);
318
319     if (fileExists(dir))
320     {
321       *artwork_path_ptr = getStringCopy(getDefaultArtworkDir(ti->type));
322       *artwork_set_ptr = getStringCopy(getDefaultArtworkSet(ti->type));
323     }
324     else
325     {
326       *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
327       *artwork_set_ptr = NULL;
328     }
329
330     free(dir);
331   }
332
333   return *artwork_set_ptr;
334 }
335
336 inline static char *getLevelArtworkSet(int type)
337 {
338   if (leveldir_current == NULL)
339     return NULL;
340
341   return LEVELDIR_ARTWORK_SET(leveldir_current, type);
342 }
343
344 inline static char *getLevelArtworkDir(int type)
345 {
346   if (leveldir_current == NULL)
347     return UNDEFINED_FILENAME;
348
349   return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
350 }
351
352 char *getTapeFilename(int nr)
353 {
354   static char *filename = NULL;
355   char basename[MAX_FILENAME_LEN];
356
357   checked_free(filename);
358
359   sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
360   filename = getPath2(getTapeDir(leveldir_current->subdir), 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->subdir), 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("subdir == '%s' ['%s', '%s'] [%d])\n",
932            node->subdir, node->fullpath, node->basepath, node->user_defined);
933 #else
934     printf("subdir == '%s' (%s) [%s] (%d)\n",
935            node->subdir, 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 #define LEVELINFO_TOKEN_HANDICAP        16
1588
1589 #define NUM_LEVELINFO_TOKENS            17
1590
1591 static LevelDirTree ldi;
1592
1593 static struct TokenInfo levelinfo_tokens[] =
1594 {
1595   /* level directory info */
1596   { TYPE_STRING,  &ldi.identifier,      "identifier"    },
1597   { TYPE_STRING,  &ldi.name,            "name"          },
1598   { TYPE_STRING,  &ldi.name_sorting,    "name_sorting"  },
1599   { TYPE_STRING,  &ldi.author,          "author"        },
1600   { TYPE_STRING,  &ldi.imported_from,   "imported_from" },
1601   { TYPE_INTEGER, &ldi.levels,          "levels"        },
1602   { TYPE_INTEGER, &ldi.first_level,     "first_level"   },
1603   { TYPE_INTEGER, &ldi.sort_priority,   "sort_priority" },
1604   { TYPE_BOOLEAN, &ldi.latest_engine,   "latest_engine" },
1605   { TYPE_BOOLEAN, &ldi.level_group,     "level_group"   },
1606   { TYPE_BOOLEAN, &ldi.readonly,        "readonly"      },
1607   { TYPE_STRING,  &ldi.graphics_set,    "graphics_set"  },
1608   { TYPE_STRING,  &ldi.sounds_set,      "sounds_set"    },
1609   { TYPE_STRING,  &ldi.music_set,       "music_set"     },
1610   { TYPE_STRING,  &ldi.level_filename,  "filename"      },
1611   { TYPE_STRING,  &ldi.level_filetype,  "filetype"      },
1612   { TYPE_STRING,  &ldi.handicap,        "handicap"      }
1613 };
1614
1615 static void setTreeInfoToDefaults(TreeInfo *ldi, int type)
1616 {
1617   ldi->type = type;
1618
1619   ldi->node_top = (ldi->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
1620                    ldi->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
1621                    ldi->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
1622                    ldi->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
1623                    NULL);
1624
1625   ldi->node_parent = NULL;
1626   ldi->node_group = NULL;
1627   ldi->next = NULL;
1628
1629   ldi->cl_first = -1;
1630   ldi->cl_cursor = -1;
1631
1632   ldi->subdir = NULL;
1633   ldi->fullpath = NULL;
1634   ldi->basepath = NULL;
1635   ldi->identifier = NULL;
1636   ldi->name = getStringCopy(ANONYMOUS_NAME);
1637   ldi->name_sorting = NULL;
1638   ldi->author = getStringCopy(ANONYMOUS_NAME);
1639
1640   ldi->sort_priority = LEVELCLASS_UNDEFINED;    /* default: least priority */
1641   ldi->latest_engine = FALSE;                   /* default: get from level */
1642   ldi->parent_link = FALSE;
1643   ldi->user_defined = FALSE;
1644   ldi->color = 0;
1645   ldi->class_desc = NULL;
1646
1647   if (ldi->type == TREE_TYPE_LEVEL_DIR)
1648   {
1649     ldi->imported_from = NULL;
1650
1651     ldi->graphics_set = NULL;
1652     ldi->sounds_set = NULL;
1653     ldi->music_set = NULL;
1654     ldi->graphics_path = getStringCopy(UNDEFINED_FILENAME);
1655     ldi->sounds_path = getStringCopy(UNDEFINED_FILENAME);
1656     ldi->music_path = getStringCopy(UNDEFINED_FILENAME);
1657
1658     ldi->level_filename = NULL;
1659     ldi->level_filetype = NULL;
1660
1661     ldi->levels = 0;
1662     ldi->first_level = 0;
1663     ldi->last_level = 0;
1664     ldi->level_group = FALSE;
1665     ldi->handicap_level = 0;
1666     ldi->readonly = TRUE;
1667     ldi->handicap = TRUE;
1668   }
1669 }
1670
1671 static void setTreeInfoToDefaultsFromParent(TreeInfo *ldi, TreeInfo *parent)
1672 {
1673   if (parent == NULL)
1674   {
1675     Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
1676
1677     setTreeInfoToDefaults(ldi, TREE_TYPE_UNDEFINED);
1678
1679     return;
1680   }
1681
1682 #if 1
1683   /* copy all values from the parent structure */
1684
1685   ldi->type = parent->type;
1686
1687   ldi->node_top = parent->node_top;
1688   ldi->node_parent = parent;
1689   ldi->node_group = NULL;
1690   ldi->next = NULL;
1691
1692   ldi->cl_first = -1;
1693   ldi->cl_cursor = -1;
1694
1695   ldi->subdir = NULL;
1696   ldi->fullpath = NULL;
1697   ldi->basepath = NULL;
1698   ldi->identifier = NULL;
1699   ldi->name = getStringCopy(ANONYMOUS_NAME);
1700   ldi->name_sorting = NULL;
1701   ldi->author = getStringCopy(parent->author);
1702
1703   ldi->sort_priority = parent->sort_priority;
1704   ldi->latest_engine = parent->latest_engine;
1705   ldi->parent_link = FALSE;
1706   ldi->user_defined = parent->user_defined;
1707   ldi->color = parent->color;
1708   ldi->class_desc = getStringCopy(parent->class_desc);
1709
1710   if (ldi->type == TREE_TYPE_LEVEL_DIR)
1711   {
1712     ldi->imported_from = getStringCopy(parent->imported_from);
1713
1714     ldi->graphics_set = NULL;
1715     ldi->sounds_set = NULL;
1716     ldi->music_set = NULL;
1717     ldi->graphics_path = getStringCopy(UNDEFINED_FILENAME);
1718     ldi->sounds_path = getStringCopy(UNDEFINED_FILENAME);
1719     ldi->music_path = getStringCopy(UNDEFINED_FILENAME);
1720
1721     ldi->level_filename = NULL;
1722     ldi->level_filetype = NULL;
1723
1724     ldi->levels = 0;
1725     ldi->first_level = 0;
1726     ldi->last_level = 0;
1727     ldi->level_group = FALSE;
1728     ldi->handicap_level = 0;
1729     ldi->readonly = TRUE;
1730     ldi->handicap = TRUE;
1731   }
1732
1733 #else
1734
1735   /* first copy all values from the parent structure ... */
1736   *ldi = *parent;
1737
1738   /* ... then set all fields to default that cannot be inherited from parent.
1739      This is especially important for all those fields that can be set from
1740      the 'levelinfo.conf' config file, because the function 'setSetupInfo()'
1741      calls 'free()' for all already set token values which requires that no
1742      other structure's pointer may point to them!
1743   */
1744
1745   ldi->subdir = NULL;
1746   ldi->fullpath = NULL;
1747   ldi->basepath = NULL;
1748   ldi->identifier = NULL;
1749   ldi->name = getStringCopy(ANONYMOUS_NAME);
1750   ldi->name_sorting = NULL;
1751   ldi->author = getStringCopy(parent->author);
1752
1753   ldi->imported_from = getStringCopy(parent->imported_from);
1754   ldi->class_desc = getStringCopy(parent->class_desc);
1755
1756   ldi->graphics_set = NULL;
1757   ldi->sounds_set = NULL;
1758   ldi->music_set = NULL;
1759   ldi->graphics_path = NULL;
1760   ldi->sounds_path = NULL;
1761   ldi->music_path = NULL;
1762
1763   ldi->level_group = FALSE;
1764   ldi->parent_link = FALSE;
1765
1766   ldi->node_top = parent->node_top;
1767   ldi->node_parent = parent;
1768   ldi->node_group = NULL;
1769   ldi->next = NULL;
1770
1771 #endif
1772 }
1773
1774 static void freeTreeInfo(TreeInfo *ldi)
1775 {
1776   checked_free(ldi->subdir);
1777   checked_free(ldi->fullpath);
1778   checked_free(ldi->basepath);
1779   checked_free(ldi->identifier);
1780
1781   checked_free(ldi->name);
1782   checked_free(ldi->name_sorting);
1783   checked_free(ldi->author);
1784
1785   checked_free(ldi->class_desc);
1786
1787   if (ldi->type == TREE_TYPE_LEVEL_DIR)
1788   {
1789     checked_free(ldi->graphics_set);
1790     checked_free(ldi->sounds_set);
1791     checked_free(ldi->music_set);
1792
1793     checked_free(ldi->graphics_path);
1794     checked_free(ldi->sounds_path);
1795     checked_free(ldi->music_path);
1796   }
1797 }
1798
1799 void setSetupInfo(struct TokenInfo *token_info,
1800                   int token_nr, char *token_value)
1801 {
1802   int token_type = token_info[token_nr].type;
1803   void *setup_value = token_info[token_nr].value;
1804
1805   if (token_value == NULL)
1806     return;
1807
1808   /* set setup field to corresponding token value */
1809   switch (token_type)
1810   {
1811     case TYPE_BOOLEAN:
1812     case TYPE_SWITCH:
1813       *(boolean *)setup_value = get_boolean_from_string(token_value);
1814       break;
1815
1816     case TYPE_KEY:
1817       *(Key *)setup_value = getKeyFromKeyName(token_value);
1818       break;
1819
1820     case TYPE_KEY_X11:
1821       *(Key *)setup_value = getKeyFromX11KeyName(token_value);
1822       break;
1823
1824     case TYPE_INTEGER:
1825       *(int *)setup_value = get_integer_from_string(token_value);
1826       break;
1827
1828     case TYPE_STRING:
1829       checked_free(*(char **)setup_value);
1830       *(char **)setup_value = getStringCopy(token_value);
1831       break;
1832
1833     default:
1834       break;
1835   }
1836 }
1837
1838 static int compareTreeInfoEntries(const void *object1, const void *object2)
1839 {
1840   const TreeInfo *entry1 = *((TreeInfo **)object1);
1841   const TreeInfo *entry2 = *((TreeInfo **)object2);
1842   int class_sorting1, class_sorting2;
1843   int compare_result;
1844
1845   if (entry1->type == TREE_TYPE_LEVEL_DIR)
1846   {
1847     class_sorting1 = LEVELSORTING(entry1);
1848     class_sorting2 = LEVELSORTING(entry2);
1849   }
1850   else
1851   {
1852     class_sorting1 = ARTWORKSORTING(entry1);
1853     class_sorting2 = ARTWORKSORTING(entry2);
1854   }
1855
1856   if (entry1->parent_link || entry2->parent_link)
1857     compare_result = (entry1->parent_link ? -1 : +1);
1858   else if (entry1->sort_priority == entry2->sort_priority)
1859   {
1860     char *name1 = getStringToLower(entry1->name_sorting);
1861     char *name2 = getStringToLower(entry2->name_sorting);
1862
1863     compare_result = strcmp(name1, name2);
1864
1865     free(name1);
1866     free(name2);
1867   }
1868   else if (class_sorting1 == class_sorting2)
1869     compare_result = entry1->sort_priority - entry2->sort_priority;
1870   else
1871     compare_result = class_sorting1 - class_sorting2;
1872
1873   return compare_result;
1874 }
1875
1876 static void createParentTreeInfoNode(TreeInfo *node_parent)
1877 {
1878   TreeInfo *ti_new;
1879
1880   if (node_parent == NULL)
1881     return;
1882
1883   ti_new = newTreeInfo();
1884   setTreeInfoToDefaults(ti_new, node_parent->type);
1885
1886   ti_new->node_parent = node_parent;
1887   ti_new->parent_link = TRUE;
1888
1889 #if 1
1890   setString(&ti_new->identifier, node_parent->identifier);
1891   setString(&ti_new->name, ".. (parent directory)");
1892   setString(&ti_new->name_sorting, ti_new->name);
1893
1894   setString(&ti_new->subdir, "..");
1895   setString(&ti_new->fullpath, node_parent->fullpath);
1896
1897   ti_new->sort_priority = node_parent->sort_priority;
1898   ti_new->latest_engine = node_parent->latest_engine;
1899
1900   setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
1901 #else
1902   ti_new->identifier = getStringCopy(node_parent->identifier);
1903   ti_new->name = ".. (parent directory)";
1904   ti_new->name_sorting = getStringCopy(ti_new->name);
1905
1906   ti_new->subdir = "..";
1907   ti_new->fullpath = getStringCopy(node_parent->fullpath);
1908
1909   ti_new->sort_priority = node_parent->sort_priority;
1910   ti_new->latest_engine = node_parent->latest_engine;
1911
1912   ti_new->class_desc = getLevelClassDescription(ti_new);
1913 #endif
1914
1915   pushTreeInfo(&node_parent->node_group, ti_new);
1916 }
1917
1918 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
1919 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
1920
1921 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
1922                                           TreeInfo *node_parent,
1923                                           char *level_directory,
1924                                           char *directory_name)
1925 {
1926   char *directory_path = getPath2(level_directory, directory_name);
1927   char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
1928   SetupFileHash *setup_file_hash = loadSetupFileHash(filename);
1929   LevelDirTree *leveldir_new = NULL;
1930   int i;
1931
1932   if (setup_file_hash == NULL)
1933   {
1934     Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
1935
1936     free(directory_path);
1937     free(filename);
1938
1939     return FALSE;
1940   }
1941
1942   leveldir_new = newTreeInfo();
1943
1944   if (node_parent)
1945     setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
1946   else
1947     setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
1948
1949   leveldir_new->subdir = getStringCopy(directory_name);
1950
1951   checkSetupFileHashIdentifier(setup_file_hash, getCookie("LEVELINFO"));
1952
1953   /* set all structure fields according to the token/value pairs */
1954   ldi = *leveldir_new;
1955   for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
1956     setSetupInfo(levelinfo_tokens, i,
1957                  getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
1958   *leveldir_new = ldi;
1959
1960 #if 1
1961   if (strcmp(leveldir_new->name, ANONYMOUS_NAME) == 0)
1962     setString(&leveldir_new->name, leveldir_new->subdir);
1963 #else
1964   if (strcmp(leveldir_new->name, ANONYMOUS_NAME) == 0)
1965   {
1966     free(leveldir_new->name);
1967     leveldir_new->name = getStringCopy(leveldir_new->subdir);
1968   }
1969 #endif
1970
1971   DrawInitText(leveldir_new->name, 150, FC_YELLOW);
1972
1973   if (leveldir_new->identifier == NULL)
1974     leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
1975
1976   if (leveldir_new->name_sorting == NULL)
1977     leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
1978
1979   if (node_parent == NULL)              /* top level group */
1980   {
1981     leveldir_new->basepath = getStringCopy(level_directory);
1982     leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
1983   }
1984   else                                  /* sub level group */
1985   {
1986     leveldir_new->basepath = getStringCopy(node_parent->basepath);
1987     leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
1988   }
1989
1990   if (leveldir_new->levels < 1)
1991     leveldir_new->levels = 1;
1992
1993   leveldir_new->last_level =
1994     leveldir_new->first_level + leveldir_new->levels - 1;
1995
1996 #if 1
1997   leveldir_new->user_defined =
1998     (strcmp(leveldir_new->basepath, options.level_directory) != 0);
1999 #else
2000   leveldir_new->user_defined =
2001     (leveldir_new->basepath == options.level_directory ? FALSE : TRUE);
2002 #endif
2003
2004   leveldir_new->color = LEVELCOLOR(leveldir_new);
2005 #if 1
2006   setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
2007 #else
2008   leveldir_new->class_desc = getLevelClassDescription(leveldir_new);
2009 #endif
2010
2011   leveldir_new->handicap_level =        /* set handicap to default value */
2012     (leveldir_new->user_defined || !leveldir_new->handicap ?
2013      leveldir_new->last_level : leveldir_new->first_level);
2014
2015   pushTreeInfo(node_first, leveldir_new);
2016
2017   freeSetupFileHash(setup_file_hash);
2018
2019   if (leveldir_new->level_group)
2020   {
2021     /* create node to link back to current level directory */
2022     createParentTreeInfoNode(leveldir_new);
2023
2024     /* step into sub-directory and look for more level series */
2025     LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
2026                               leveldir_new, directory_path);
2027   }
2028
2029   free(directory_path);
2030   free(filename);
2031
2032   return TRUE;
2033 }
2034
2035 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
2036                                       TreeInfo *node_parent,
2037                                       char *level_directory)
2038 {
2039   DIR *dir;
2040   struct dirent *dir_entry;
2041   boolean valid_entry_found = FALSE;
2042
2043   if ((dir = opendir(level_directory)) == NULL)
2044   {
2045     Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
2046     return;
2047   }
2048
2049   while ((dir_entry = readdir(dir)) != NULL)    /* loop until last dir entry */
2050   {
2051     struct stat file_status;
2052     char *directory_name = dir_entry->d_name;
2053     char *directory_path = getPath2(level_directory, directory_name);
2054
2055     /* skip entries for current and parent directory */
2056     if (strcmp(directory_name, ".")  == 0 ||
2057         strcmp(directory_name, "..") == 0)
2058     {
2059       free(directory_path);
2060       continue;
2061     }
2062
2063     /* find out if directory entry is itself a directory */
2064     if (stat(directory_path, &file_status) != 0 ||      /* cannot stat file */
2065         (file_status.st_mode & S_IFMT) != S_IFDIR)      /* not a directory */
2066     {
2067       free(directory_path);
2068       continue;
2069     }
2070
2071     free(directory_path);
2072
2073     if (strcmp(directory_name, GRAPHICS_DIRECTORY) == 0 ||
2074         strcmp(directory_name, SOUNDS_DIRECTORY) == 0 ||
2075         strcmp(directory_name, MUSIC_DIRECTORY) == 0)
2076       continue;
2077
2078     valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
2079                                                     level_directory,
2080                                                     directory_name);
2081   }
2082
2083   closedir(dir);
2084
2085   if (!valid_entry_found)
2086   {
2087     /* check if this directory directly contains a file "levelinfo.conf" */
2088     valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
2089                                                     level_directory, ".");
2090   }
2091
2092   if (!valid_entry_found)
2093     Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
2094           level_directory);
2095 }
2096
2097 void LoadLevelInfo()
2098 {
2099   InitUserLevelDirectory(getLoginName());
2100
2101   DrawInitText("Loading level series:", 120, FC_GREEN);
2102
2103   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
2104   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
2105
2106   /* before sorting, the first entries will be from the user directory */
2107   leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
2108
2109   if (leveldir_first == NULL)
2110     Error(ERR_EXIT, "cannot find any valid level series in any directory");
2111
2112   sortTreeInfo(&leveldir_first, compareTreeInfoEntries);
2113
2114 #if 0
2115   dumpTreeInfo(leveldir_first, 0);
2116 #endif
2117 }
2118
2119 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
2120                                               TreeInfo *node_parent,
2121                                               char *base_directory,
2122                                               char *directory_name, int type)
2123 {
2124   char *directory_path = getPath2(base_directory, directory_name);
2125   char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
2126   SetupFileHash *setup_file_hash = NULL;
2127   TreeInfo *artwork_new = NULL;
2128   int i;
2129
2130   if (access(filename, F_OK) == 0)              /* file exists */
2131     setup_file_hash = loadSetupFileHash(filename);
2132
2133   if (setup_file_hash == NULL)  /* no config file -- look for artwork files */
2134   {
2135     DIR *dir;
2136     struct dirent *dir_entry;
2137     boolean valid_file_found = FALSE;
2138
2139     if ((dir = opendir(directory_path)) != NULL)
2140     {
2141       while ((dir_entry = readdir(dir)) != NULL)
2142       {
2143         char *entry_name = dir_entry->d_name;
2144
2145         if (FileIsArtworkType(entry_name, type))
2146         {
2147           valid_file_found = TRUE;
2148           break;
2149         }
2150       }
2151
2152       closedir(dir);
2153     }
2154
2155     if (!valid_file_found)
2156     {
2157       if (strcmp(directory_name, ".") != 0)
2158         Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
2159
2160       free(directory_path);
2161       free(filename);
2162
2163       return FALSE;
2164     }
2165   }
2166
2167   artwork_new = newTreeInfo();
2168
2169   if (node_parent)
2170     setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
2171   else
2172     setTreeInfoToDefaults(artwork_new, type);
2173
2174   artwork_new->subdir = getStringCopy(directory_name);
2175
2176   if (setup_file_hash)  /* (before defining ".color" and ".class_desc") */
2177   {
2178 #if 0
2179     checkSetupFileHashIdentifier(setup_file_hash, getCookie("..."));
2180 #endif
2181
2182     /* set all structure fields according to the token/value pairs */
2183     ldi = *artwork_new;
2184     for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
2185       setSetupInfo(levelinfo_tokens, i,
2186                    getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
2187     *artwork_new = ldi;
2188
2189 #if 1
2190     if (strcmp(artwork_new->name, ANONYMOUS_NAME) == 0)
2191       setString(&artwork_new->name, artwork_new->subdir);
2192 #else
2193     if (strcmp(artwork_new->name, ANONYMOUS_NAME) == 0)
2194     {
2195       free(artwork_new->name);
2196       artwork_new->name = getStringCopy(artwork_new->subdir);
2197     }
2198 #endif
2199
2200 #if 0
2201     DrawInitText(artwork_new->name, 150, FC_YELLOW);
2202 #endif
2203
2204     if (artwork_new->identifier == NULL)
2205       artwork_new->identifier = getStringCopy(artwork_new->subdir);
2206
2207     if (artwork_new->name_sorting == NULL)
2208       artwork_new->name_sorting = getStringCopy(artwork_new->name);
2209   }
2210
2211   if (node_parent == NULL)              /* top level group */
2212   {
2213     artwork_new->basepath = getStringCopy(base_directory);
2214     artwork_new->fullpath = getStringCopy(artwork_new->subdir);
2215   }
2216   else                                  /* sub level group */
2217   {
2218     artwork_new->basepath = getStringCopy(node_parent->basepath);
2219     artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
2220   }
2221
2222 #if 1
2223   artwork_new->user_defined =
2224     (strcmp(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)) != 0);
2225 #else
2226   artwork_new->user_defined =
2227     (artwork_new->basepath == OPTIONS_ARTWORK_DIRECTORY(type) ? FALSE : TRUE);
2228 #endif
2229
2230   /* (may use ".sort_priority" from "setup_file_hash" above) */
2231   artwork_new->color = ARTWORKCOLOR(artwork_new);
2232 #if 1
2233   setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
2234 #else
2235   artwork_new->class_desc = getLevelClassDescription(artwork_new);
2236 #endif
2237
2238   if (setup_file_hash == NULL)  /* (after determining ".user_defined") */
2239   {
2240 #if 0
2241     if (artwork_new->name != NULL)
2242     {
2243       free(artwork_new->name);
2244       artwork_new->name = NULL;
2245     }
2246 #endif
2247
2248 #if 0
2249     if (artwork_new->identifier != NULL)
2250     {
2251       free(artwork_new->identifier);
2252       artwork_new->identifier = NULL;
2253     }
2254 #endif
2255
2256     if (strcmp(artwork_new->subdir, ".") == 0)
2257     {
2258       if (artwork_new->user_defined)
2259       {
2260 #if 1
2261         setString(&artwork_new->identifier, "private");
2262 #else
2263         artwork_new->identifier = getStringCopy("private");
2264 #endif
2265         artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
2266       }
2267       else
2268       {
2269 #if 1
2270         setString(&artwork_new->identifier, "classic");
2271 #else
2272         artwork_new->identifier = getStringCopy("classic");
2273 #endif
2274         artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
2275       }
2276
2277       /* set to new values after changing ".sort_priority" */
2278       artwork_new->color = ARTWORKCOLOR(artwork_new);
2279 #if 1
2280       setString(&artwork_new->class_desc,
2281                 getLevelClassDescription(artwork_new));
2282 #else
2283       artwork_new->class_desc = getLevelClassDescription(artwork_new);
2284 #endif
2285     }
2286     else
2287     {
2288 #if 1
2289       setString(&artwork_new->identifier, artwork_new->subdir);
2290 #else
2291       artwork_new->identifier = getStringCopy(artwork_new->subdir);
2292 #endif
2293     }
2294
2295 #if 1
2296     setString(&artwork_new->name, artwork_new->identifier);
2297     setString(&artwork_new->name_sorting, artwork_new->name);
2298 #else
2299     artwork_new->name = getStringCopy(artwork_new->identifier);
2300     artwork_new->name_sorting = getStringCopy(artwork_new->name);
2301 #endif
2302   }
2303
2304   DrawInitText(artwork_new->name, 150, FC_YELLOW);
2305
2306   pushTreeInfo(node_first, artwork_new);
2307
2308   freeSetupFileHash(setup_file_hash);
2309
2310   free(directory_path);
2311   free(filename);
2312
2313   return TRUE;
2314 }
2315
2316 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
2317                                           TreeInfo *node_parent,
2318                                           char *base_directory, int type)
2319 {
2320   DIR *dir;
2321   struct dirent *dir_entry;
2322   boolean valid_entry_found = FALSE;
2323
2324   if ((dir = opendir(base_directory)) == NULL)
2325   {
2326     if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
2327       Error(ERR_WARN, "cannot read directory '%s'", base_directory);
2328     return;
2329   }
2330
2331   while ((dir_entry = readdir(dir)) != NULL)    /* loop until last dir entry */
2332   {
2333     struct stat file_status;
2334     char *directory_name = dir_entry->d_name;
2335     char *directory_path = getPath2(base_directory, directory_name);
2336
2337     /* skip entries for current and parent directory */
2338     if (strcmp(directory_name, ".")  == 0 ||
2339         strcmp(directory_name, "..") == 0)
2340     {
2341       free(directory_path);
2342       continue;
2343     }
2344
2345     /* find out if directory entry is itself a directory */
2346     if (stat(directory_path, &file_status) != 0 ||      /* cannot stat file */
2347         (file_status.st_mode & S_IFMT) != S_IFDIR)      /* not a directory */
2348     {
2349       free(directory_path);
2350       continue;
2351     }
2352
2353     free(directory_path);
2354
2355     /* check if this directory contains artwork with or without config file */
2356     valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first,node_parent,
2357                                                         base_directory,
2358                                                         directory_name, type);
2359   }
2360
2361   closedir(dir);
2362
2363   /* check if this directory directly contains artwork itself */
2364   valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first,node_parent,
2365                                                       base_directory, ".",
2366                                                       type);
2367   if (!valid_entry_found)
2368     Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
2369           base_directory);
2370 }
2371
2372 static TreeInfo *getDummyArtworkInfo(int type)
2373 {
2374   /* this is only needed when there is completely no artwork available */
2375   TreeInfo *artwork_new = newTreeInfo();
2376
2377   setTreeInfoToDefaults(artwork_new, type);
2378
2379 #if 1
2380   setString(&artwork_new->subdir,   UNDEFINED_FILENAME);
2381   setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
2382   setString(&artwork_new->basepath, UNDEFINED_FILENAME);
2383
2384   setString(&artwork_new->identifier,   UNDEFINED_FILENAME);
2385   setString(&artwork_new->name,         UNDEFINED_FILENAME);
2386   setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
2387 #else
2388   artwork_new->subdir   = getStringCopy(UNDEFINED_FILENAME);
2389   artwork_new->fullpath = getStringCopy(UNDEFINED_FILENAME);
2390   artwork_new->basepath = getStringCopy(UNDEFINED_FILENAME);
2391
2392   checked_free(artwork_new->name);
2393
2394   artwork_new->identifier   = getStringCopy(UNDEFINED_FILENAME);
2395   artwork_new->name         = getStringCopy(UNDEFINED_FILENAME);
2396   artwork_new->name_sorting = getStringCopy(UNDEFINED_FILENAME);
2397 #endif
2398
2399   return artwork_new;
2400 }
2401
2402 void LoadArtworkInfo()
2403 {
2404   DrawInitText("Looking for custom artwork:", 120, FC_GREEN);
2405
2406   LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
2407                                 options.graphics_directory,
2408                                 TREE_TYPE_GRAPHICS_DIR);
2409   LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
2410                                 getUserGraphicsDir(),
2411                                 TREE_TYPE_GRAPHICS_DIR);
2412
2413   LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
2414                                 options.sounds_directory,
2415                                 TREE_TYPE_SOUNDS_DIR);
2416   LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
2417                                 getUserSoundsDir(),
2418                                 TREE_TYPE_SOUNDS_DIR);
2419
2420   LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
2421                                 options.music_directory,
2422                                 TREE_TYPE_MUSIC_DIR);
2423   LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
2424                                 getUserMusicDir(),
2425                                 TREE_TYPE_MUSIC_DIR);
2426
2427   if (artwork.gfx_first == NULL)
2428     artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
2429   if (artwork.snd_first == NULL)
2430     artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
2431   if (artwork.mus_first == NULL)
2432     artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
2433
2434   /* before sorting, the first entries will be from the user directory */
2435   artwork.gfx_current =
2436     getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
2437   if (artwork.gfx_current == NULL)
2438     artwork.gfx_current =
2439       getTreeInfoFromIdentifier(artwork.gfx_first, GFX_CLASSIC_SUBDIR);
2440   if (artwork.gfx_current == NULL)
2441     artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
2442
2443   artwork.snd_current =
2444     getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
2445   if (artwork.snd_current == NULL)
2446     artwork.snd_current =
2447       getTreeInfoFromIdentifier(artwork.snd_first, SND_CLASSIC_SUBDIR);
2448   if (artwork.snd_current == NULL)
2449     artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
2450
2451   artwork.mus_current =
2452     getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
2453   if (artwork.mus_current == NULL)
2454     artwork.mus_current =
2455       getTreeInfoFromIdentifier(artwork.mus_first, MUS_CLASSIC_SUBDIR);
2456   if (artwork.mus_current == NULL)
2457     artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
2458
2459   artwork.gfx_current_identifier = artwork.gfx_current->identifier;
2460   artwork.snd_current_identifier = artwork.snd_current->identifier;
2461   artwork.mus_current_identifier = artwork.mus_current->identifier;
2462
2463 #if 0
2464   printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
2465   printf("sounds set == %s\n\n", artwork.snd_current_identifier);
2466   printf("music set == %s\n\n", artwork.mus_current_identifier);
2467 #endif
2468
2469   sortTreeInfo(&artwork.gfx_first, compareTreeInfoEntries);
2470   sortTreeInfo(&artwork.snd_first, compareTreeInfoEntries);
2471   sortTreeInfo(&artwork.mus_first, compareTreeInfoEntries);
2472
2473 #if 0
2474   dumpTreeInfo(artwork.gfx_first, 0);
2475   dumpTreeInfo(artwork.snd_first, 0);
2476   dumpTreeInfo(artwork.mus_first, 0);
2477 #endif
2478 }
2479
2480 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
2481                                   LevelDirTree *level_node)
2482 {
2483   /* recursively check all level directories for artwork sub-directories */
2484
2485   while (level_node)
2486   {
2487     char *path = getPath2(getLevelDirFromTreeInfo(level_node),
2488                           ARTWORK_DIRECTORY((*artwork_node)->type));
2489
2490 #if 0
2491     if (!level_node->parent_link)
2492       printf("CHECKING '%s' ['%s', '%s'] ...\n", path,
2493              level_node->subdir, level_node->name);
2494 #endif
2495
2496     if (!level_node->parent_link)
2497     {
2498       TreeInfo *topnode_last = *artwork_node;
2499
2500       LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path,
2501                                     (*artwork_node)->type);
2502
2503       if (topnode_last != *artwork_node)
2504       {
2505         free((*artwork_node)->identifier);
2506         free((*artwork_node)->name);
2507         free((*artwork_node)->name_sorting);
2508
2509         (*artwork_node)->identifier   = getStringCopy(level_node->subdir);
2510         (*artwork_node)->name         = getStringCopy(level_node->name);
2511         (*artwork_node)->name_sorting = getStringCopy(level_node->name);
2512
2513         (*artwork_node)->sort_priority = level_node->sort_priority;
2514         (*artwork_node)->color = LEVELCOLOR((*artwork_node));
2515       }
2516     }
2517
2518     free(path);
2519
2520     if (level_node->node_group != NULL)
2521       LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
2522
2523     level_node = level_node->next;
2524   }
2525 }
2526
2527 void LoadLevelArtworkInfo()
2528 {
2529   DrawInitText("Looking for custom level artwork:", 120, FC_GREEN);
2530
2531   LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first);
2532   LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first);
2533   LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first);
2534
2535   /* needed for reloading level artwork not known at ealier stage */
2536
2537   if (strcmp(artwork.gfx_current_identifier, setup.graphics_set) != 0)
2538   {
2539     artwork.gfx_current =
2540       getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
2541     if (artwork.gfx_current == NULL)
2542       artwork.gfx_current =
2543         getTreeInfoFromIdentifier(artwork.gfx_first, GFX_CLASSIC_SUBDIR);
2544     if (artwork.gfx_current == NULL)
2545       artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
2546   }
2547
2548   if (strcmp(artwork.snd_current_identifier, setup.sounds_set) != 0)
2549   {
2550     artwork.snd_current =
2551       getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
2552     if (artwork.snd_current == NULL)
2553       artwork.snd_current =
2554         getTreeInfoFromIdentifier(artwork.snd_first, SND_CLASSIC_SUBDIR);
2555     if (artwork.snd_current == NULL)
2556       artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
2557   }
2558
2559   if (strcmp(artwork.mus_current_identifier, setup.music_set) != 0)
2560   {
2561     artwork.mus_current =
2562       getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
2563     if (artwork.mus_current == NULL)
2564       artwork.mus_current =
2565         getTreeInfoFromIdentifier(artwork.mus_first, MUS_CLASSIC_SUBDIR);
2566     if (artwork.mus_current == NULL)
2567       artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
2568   }
2569
2570   sortTreeInfo(&artwork.gfx_first, compareTreeInfoEntries);
2571   sortTreeInfo(&artwork.snd_first, compareTreeInfoEntries);
2572   sortTreeInfo(&artwork.mus_first, compareTreeInfoEntries);
2573
2574 #if 0
2575   dumpTreeInfo(artwork.gfx_first, 0);
2576   dumpTreeInfo(artwork.snd_first, 0);
2577   dumpTreeInfo(artwork.mus_first, 0);
2578 #endif
2579 }
2580
2581 static void SaveUserLevelInfo()
2582 {
2583   LevelDirTree *level_info;
2584   char *filename;
2585   FILE *file;
2586   int i;
2587
2588   filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
2589
2590   if (!(file = fopen(filename, MODE_WRITE)))
2591   {
2592     Error(ERR_WARN, "cannot write level info file '%s'", filename);
2593     free(filename);
2594     return;
2595   }
2596
2597   level_info = newTreeInfo();
2598
2599   /* always start with reliable default values */
2600   setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
2601
2602 #if 1
2603   setString(&level_info->name, getLoginName());
2604   setString(&level_info->author, getRealName());
2605   level_info->levels = 100;
2606   level_info->first_level = 1;
2607   level_info->sort_priority = LEVELCLASS_PRIVATE_START;
2608   level_info->readonly = FALSE;
2609   setString(&level_info->graphics_set, GFX_CLASSIC_SUBDIR);
2610   setString(&level_info->sounds_set,   SND_CLASSIC_SUBDIR);
2611   setString(&level_info->music_set,    MUS_CLASSIC_SUBDIR);
2612 #else
2613   ldi.name = getStringCopy(getLoginName());
2614   ldi.author = getStringCopy(getRealName());
2615   ldi.levels = 100;
2616   ldi.first_level = 1;
2617   ldi.sort_priority = LEVELCLASS_PRIVATE_START;
2618   ldi.readonly = FALSE;
2619   ldi.graphics_set = getStringCopy(GFX_CLASSIC_SUBDIR);
2620   ldi.sounds_set = getStringCopy(SND_CLASSIC_SUBDIR);
2621   ldi.music_set = getStringCopy(MUS_CLASSIC_SUBDIR);
2622 #endif
2623
2624   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
2625                                                  getCookie("LEVELINFO")));
2626
2627   ldi = *level_info;
2628   for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
2629     if (i != LEVELINFO_TOKEN_IDENTIFIER &&
2630         i != LEVELINFO_TOKEN_NAME_SORTING &&
2631         i != LEVELINFO_TOKEN_IMPORTED_FROM)
2632       fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
2633
2634   fclose(file);
2635
2636   SetFilePermissions(filename, PERMS_PRIVATE);
2637
2638   freeTreeInfo(level_info);
2639   free(filename);
2640 }
2641
2642 char *getSetupValue(int type, void *value)
2643 {
2644   static char value_string[MAX_LINE_LEN];
2645
2646   if (value == NULL)
2647     return NULL;
2648
2649   switch (type)
2650   {
2651     case TYPE_BOOLEAN:
2652       strcpy(value_string, (*(boolean *)value ? "true" : "false"));
2653       break;
2654
2655     case TYPE_SWITCH:
2656       strcpy(value_string, (*(boolean *)value ? "on" : "off"));
2657       break;
2658
2659     case TYPE_YES_NO:
2660       strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
2661       break;
2662
2663     case TYPE_KEY:
2664       strcpy(value_string, getKeyNameFromKey(*(Key *)value));
2665       break;
2666
2667     case TYPE_KEY_X11:
2668       strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
2669       break;
2670
2671     case TYPE_INTEGER:
2672       sprintf(value_string, "%d", *(int *)value);
2673       break;
2674
2675     case TYPE_STRING:
2676       strcpy(value_string, *(char **)value);
2677       break;
2678
2679     default:
2680       value_string[0] = '\0';
2681       break;
2682   }
2683
2684   return value_string;
2685 }
2686
2687 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
2688 {
2689   int i;
2690   char *line;
2691   static char token_string[MAX_LINE_LEN];
2692   int token_type = token_info[token_nr].type;
2693   void *setup_value = token_info[token_nr].value;
2694   char *token_text = token_info[token_nr].text;
2695   char *value_string = getSetupValue(token_type, setup_value);
2696
2697   /* build complete token string */
2698   sprintf(token_string, "%s%s", prefix, token_text);
2699
2700   /* build setup entry line */
2701   line = getFormattedSetupEntry(token_string, value_string);
2702
2703   if (token_type == TYPE_KEY_X11)
2704   {
2705     Key key = *(Key *)setup_value;
2706     char *keyname = getKeyNameFromKey(key);
2707
2708     /* add comment, if useful */
2709     if (strcmp(keyname, "(undefined)") != 0 &&
2710         strcmp(keyname, "(unknown)") != 0)
2711     {
2712       /* add at least one whitespace */
2713       strcat(line, " ");
2714       for (i = strlen(line); i < TOKEN_COMMENT_POSITION; i++)
2715         strcat(line, " ");
2716
2717       strcat(line, "# ");
2718       strcat(line, keyname);
2719     }
2720   }
2721
2722   return line;
2723 }
2724
2725 void LoadLevelSetup_LastSeries()
2726 {
2727   /* ----------------------------------------------------------------------- */
2728   /* ~/.<program>/levelsetup.conf                                            */
2729   /* ----------------------------------------------------------------------- */
2730
2731   char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
2732   SetupFileHash *level_setup_hash = NULL;
2733
2734   /* always start with reliable default values */
2735   leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
2736
2737   if ((level_setup_hash = loadSetupFileHash(filename)))
2738   {
2739     char *last_level_series =
2740       getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
2741
2742     leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
2743                                                  last_level_series);
2744     if (leveldir_current == NULL)
2745       leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
2746
2747     checkSetupFileHashIdentifier(level_setup_hash, getCookie("LEVELSETUP"));
2748
2749     freeSetupFileHash(level_setup_hash);
2750   }
2751   else
2752     Error(ERR_WARN, "using default setup values");
2753
2754   free(filename);
2755 }
2756
2757 void SaveLevelSetup_LastSeries()
2758 {
2759   /* ----------------------------------------------------------------------- */
2760   /* ~/.<program>/levelsetup.conf                                            */
2761   /* ----------------------------------------------------------------------- */
2762
2763   char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
2764   char *level_subdir = leveldir_current->subdir;
2765   FILE *file;
2766
2767   InitUserDataDirectory();
2768
2769   if (!(file = fopen(filename, MODE_WRITE)))
2770   {
2771     Error(ERR_WARN, "cannot write setup file '%s'", filename);
2772     free(filename);
2773     return;
2774   }
2775
2776   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
2777                                                  getCookie("LEVELSETUP")));
2778   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
2779                                                level_subdir));
2780
2781   fclose(file);
2782
2783   SetFilePermissions(filename, PERMS_PRIVATE);
2784
2785   free(filename);
2786 }
2787
2788 static void checkSeriesInfo()
2789 {
2790   static char *level_directory = NULL;
2791   DIR *dir;
2792   struct dirent *dir_entry;
2793
2794   /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
2795
2796   level_directory = getPath2((leveldir_current->user_defined ?
2797                               getUserLevelDir(NULL) :
2798                               options.level_directory),
2799                              leveldir_current->fullpath);
2800
2801   if ((dir = opendir(level_directory)) == NULL)
2802   {
2803     Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
2804     return;
2805   }
2806
2807   while ((dir_entry = readdir(dir)) != NULL)    /* last directory entry */
2808   {
2809     if (strlen(dir_entry->d_name) > 4 &&
2810         dir_entry->d_name[3] == '.' &&
2811         strcmp(&dir_entry->d_name[4], LEVELFILE_EXTENSION) == 0)
2812     {
2813       char levelnum_str[4];
2814       int levelnum_value;
2815
2816       strncpy(levelnum_str, dir_entry->d_name, 3);
2817       levelnum_str[3] = '\0';
2818
2819       levelnum_value = atoi(levelnum_str);
2820
2821 #if 0
2822       if (levelnum_value < leveldir_current->first_level)
2823       {
2824         Error(ERR_WARN, "additional level %d found", levelnum_value);
2825         leveldir_current->first_level = levelnum_value;
2826       }
2827       else if (levelnum_value > leveldir_current->last_level)
2828       {
2829         Error(ERR_WARN, "additional level %d found", levelnum_value);
2830         leveldir_current->last_level = levelnum_value;
2831       }
2832 #endif
2833     }
2834   }
2835
2836   closedir(dir);
2837 }
2838
2839 void LoadLevelSetup_SeriesInfo()
2840 {
2841   char *filename;
2842   SetupFileHash *level_setup_hash = NULL;
2843   char *level_subdir = leveldir_current->subdir;
2844
2845   /* always start with reliable default values */
2846   level_nr = leveldir_current->first_level;
2847
2848   checkSeriesInfo(leveldir_current);
2849
2850   /* ----------------------------------------------------------------------- */
2851   /* ~/.<program>/levelsetup/<level series>/levelsetup.conf                  */
2852   /* ----------------------------------------------------------------------- */
2853
2854   level_subdir = leveldir_current->subdir;
2855
2856   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
2857
2858   if ((level_setup_hash = loadSetupFileHash(filename)))
2859   {
2860     char *token_value;
2861
2862     token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
2863
2864     if (token_value)
2865     {
2866       level_nr = atoi(token_value);
2867
2868       if (level_nr < leveldir_current->first_level)
2869         level_nr = leveldir_current->first_level;
2870       if (level_nr > leveldir_current->last_level)
2871         level_nr = leveldir_current->last_level;
2872     }
2873
2874     token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
2875
2876     if (token_value)
2877     {
2878       int level_nr = atoi(token_value);
2879
2880       if (level_nr < leveldir_current->first_level)
2881         level_nr = leveldir_current->first_level;
2882       if (level_nr > leveldir_current->last_level + 1)
2883         level_nr = leveldir_current->last_level;
2884
2885       if (leveldir_current->user_defined || !leveldir_current->handicap)
2886         level_nr = leveldir_current->last_level;
2887
2888       leveldir_current->handicap_level = level_nr;
2889     }
2890
2891     checkSetupFileHashIdentifier(level_setup_hash, getCookie("LEVELSETUP"));
2892
2893     freeSetupFileHash(level_setup_hash);
2894   }
2895   else
2896     Error(ERR_WARN, "using default setup values");
2897
2898   free(filename);
2899 }
2900
2901 void SaveLevelSetup_SeriesInfo()
2902 {
2903   char *filename;
2904   char *level_subdir = leveldir_current->subdir;
2905   char *level_nr_str = int2str(level_nr, 0);
2906   char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
2907   FILE *file;
2908
2909   /* ----------------------------------------------------------------------- */
2910   /* ~/.<program>/levelsetup/<level series>/levelsetup.conf                  */
2911   /* ----------------------------------------------------------------------- */
2912
2913   InitLevelSetupDirectory(level_subdir);
2914
2915   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
2916
2917   if (!(file = fopen(filename, MODE_WRITE)))
2918   {
2919     Error(ERR_WARN, "cannot write setup file '%s'", filename);
2920     free(filename);
2921     return;
2922   }
2923
2924   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
2925                                                  getCookie("LEVELSETUP")));
2926   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
2927                                                level_nr_str));
2928   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
2929                                                handicap_level_str));
2930
2931   fclose(file);
2932
2933   SetFilePermissions(filename, PERMS_PRIVATE);
2934
2935   free(filename);
2936 }