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