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