rnd-20051211-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 0
1960   if (leveldir_new->levels < 1)
1961     leveldir_new->levels = 1;
1962 #endif
1963
1964   leveldir_new->last_level =
1965     leveldir_new->first_level + leveldir_new->levels - 1;
1966
1967   leveldir_new->in_user_dir =
1968     (strcmp(leveldir_new->basepath, options.level_directory) != 0);
1969
1970   /* adjust some settings if user's private level directory was detected */
1971   if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
1972       leveldir_new->in_user_dir &&
1973       (strcmp(leveldir_new->subdir, getLoginName()) == 0 ||
1974        strcmp(leveldir_new->name,   getLoginName()) == 0 ||
1975        strcmp(leveldir_new->author, getRealName())  == 0))
1976   {
1977     leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
1978     leveldir_new->readonly = FALSE;
1979   }
1980
1981   leveldir_new->user_defined =
1982     (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
1983
1984   leveldir_new->color = LEVELCOLOR(leveldir_new);
1985
1986   setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
1987
1988   leveldir_new->handicap_level =        /* set handicap to default value */
1989     (leveldir_new->user_defined || !leveldir_new->handicap ?
1990      leveldir_new->last_level : leveldir_new->first_level);
1991
1992 #if 1
1993   if (leveldir_new->levels < 1 && !leveldir_new->level_group)
1994   {
1995     /* skip level sets without levels (which are probably artwork base sets) */
1996
1997     freeSetupFileHash(setup_file_hash);
1998     free(directory_path);
1999     free(filename);
2000
2001     return FALSE;
2002   }
2003 #endif
2004
2005   pushTreeInfo(node_first, leveldir_new);
2006
2007   freeSetupFileHash(setup_file_hash);
2008
2009   if (leveldir_new->level_group)
2010   {
2011     /* create node to link back to current level directory */
2012     createParentTreeInfoNode(leveldir_new);
2013
2014     /* step into sub-directory and look for more level series */
2015     LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
2016                               leveldir_new, directory_path);
2017   }
2018
2019   free(directory_path);
2020   free(filename);
2021
2022   return TRUE;
2023 }
2024
2025 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
2026                                       TreeInfo *node_parent,
2027                                       char *level_directory)
2028 {
2029   DIR *dir;
2030   struct dirent *dir_entry;
2031   boolean valid_entry_found = FALSE;
2032
2033   if ((dir = opendir(level_directory)) == NULL)
2034   {
2035     Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
2036     return;
2037   }
2038
2039   while ((dir_entry = readdir(dir)) != NULL)    /* loop until last dir entry */
2040   {
2041     struct stat file_status;
2042     char *directory_name = dir_entry->d_name;
2043     char *directory_path = getPath2(level_directory, directory_name);
2044
2045     /* skip entries for current and parent directory */
2046     if (strcmp(directory_name, ".")  == 0 ||
2047         strcmp(directory_name, "..") == 0)
2048     {
2049       free(directory_path);
2050       continue;
2051     }
2052
2053     /* find out if directory entry is itself a directory */
2054     if (stat(directory_path, &file_status) != 0 ||      /* cannot stat file */
2055         (file_status.st_mode & S_IFMT) != S_IFDIR)      /* not a directory */
2056     {
2057       free(directory_path);
2058       continue;
2059     }
2060
2061     free(directory_path);
2062
2063     if (strcmp(directory_name, GRAPHICS_DIRECTORY) == 0 ||
2064         strcmp(directory_name, SOUNDS_DIRECTORY) == 0 ||
2065         strcmp(directory_name, MUSIC_DIRECTORY) == 0)
2066       continue;
2067
2068     valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
2069                                                     level_directory,
2070                                                     directory_name);
2071   }
2072
2073   closedir(dir);
2074
2075   if (!valid_entry_found)
2076   {
2077     /* check if this directory directly contains a file "levelinfo.conf" */
2078     valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
2079                                                     level_directory, ".");
2080   }
2081
2082   if (!valid_entry_found)
2083     Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
2084           level_directory);
2085 }
2086
2087 void LoadLevelInfo()
2088 {
2089   InitUserLevelDirectory(getLoginName());
2090
2091   DrawInitText("Loading level series:", 120, FC_GREEN);
2092
2093   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
2094   LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
2095
2096   /* before sorting, the first entries will be from the user directory */
2097   leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
2098
2099   if (leveldir_first == NULL)
2100     Error(ERR_EXIT, "cannot find any valid level series in any directory");
2101
2102   sortTreeInfo(&leveldir_first, compareTreeInfoEntries);
2103
2104 #if 0
2105   dumpTreeInfo(leveldir_first, 0);
2106 #endif
2107 }
2108
2109 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
2110                                               TreeInfo *node_parent,
2111                                               char *base_directory,
2112                                               char *directory_name, int type)
2113 {
2114   char *directory_path = getPath2(base_directory, directory_name);
2115   char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
2116   SetupFileHash *setup_file_hash = NULL;
2117   TreeInfo *artwork_new = NULL;
2118   int i;
2119
2120   if (fileExists(filename))
2121     setup_file_hash = loadSetupFileHash(filename);
2122
2123   if (setup_file_hash == NULL)  /* no config file -- look for artwork files */
2124   {
2125     DIR *dir;
2126     struct dirent *dir_entry;
2127     boolean valid_file_found = FALSE;
2128
2129     if ((dir = opendir(directory_path)) != NULL)
2130     {
2131       while ((dir_entry = readdir(dir)) != NULL)
2132       {
2133         char *entry_name = dir_entry->d_name;
2134
2135         if (FileIsArtworkType(entry_name, type))
2136         {
2137           valid_file_found = TRUE;
2138           break;
2139         }
2140       }
2141
2142       closedir(dir);
2143     }
2144
2145     if (!valid_file_found)
2146     {
2147       if (strcmp(directory_name, ".") != 0)
2148         Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
2149
2150       free(directory_path);
2151       free(filename);
2152
2153       return FALSE;
2154     }
2155   }
2156
2157   artwork_new = newTreeInfo();
2158
2159   if (node_parent)
2160     setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
2161   else
2162     setTreeInfoToDefaults(artwork_new, type);
2163
2164   artwork_new->subdir = getStringCopy(directory_name);
2165
2166   if (setup_file_hash)  /* (before defining ".color" and ".class_desc") */
2167   {
2168 #if 0
2169     checkSetupFileHashIdentifier(setup_file_hash, getCookie("..."));
2170 #endif
2171
2172     /* set all structure fields according to the token/value pairs */
2173     ldi = *artwork_new;
2174     for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
2175       setSetupInfo(levelinfo_tokens, i,
2176                    getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
2177     *artwork_new = ldi;
2178
2179     if (strcmp(artwork_new->name, ANONYMOUS_NAME) == 0)
2180       setString(&artwork_new->name, artwork_new->subdir);
2181
2182 #if 0
2183     DrawInitText(artwork_new->name, 150, FC_YELLOW);
2184 #endif
2185
2186     if (artwork_new->identifier == NULL)
2187       artwork_new->identifier = getStringCopy(artwork_new->subdir);
2188
2189     if (artwork_new->name_sorting == NULL)
2190       artwork_new->name_sorting = getStringCopy(artwork_new->name);
2191   }
2192
2193   if (node_parent == NULL)              /* top level group */
2194   {
2195     artwork_new->basepath = getStringCopy(base_directory);
2196     artwork_new->fullpath = getStringCopy(artwork_new->subdir);
2197   }
2198   else                                  /* sub level group */
2199   {
2200     artwork_new->basepath = getStringCopy(node_parent->basepath);
2201     artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
2202   }
2203
2204   artwork_new->in_user_dir =
2205     (strcmp(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)) != 0);
2206
2207   /* (may use ".sort_priority" from "setup_file_hash" above) */
2208   artwork_new->color = ARTWORKCOLOR(artwork_new);
2209
2210   setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
2211
2212   if (setup_file_hash == NULL)  /* (after determining ".user_defined") */
2213   {
2214     if (strcmp(artwork_new->subdir, ".") == 0)
2215     {
2216       if (artwork_new->user_defined)
2217       {
2218         setString(&artwork_new->identifier, "private");
2219         artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
2220       }
2221       else
2222       {
2223         setString(&artwork_new->identifier, "classic");
2224         artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
2225       }
2226
2227       /* set to new values after changing ".sort_priority" */
2228       artwork_new->color = ARTWORKCOLOR(artwork_new);
2229
2230       setString(&artwork_new->class_desc,
2231                 getLevelClassDescription(artwork_new));
2232     }
2233     else
2234     {
2235       setString(&artwork_new->identifier, artwork_new->subdir);
2236     }
2237
2238     setString(&artwork_new->name, artwork_new->identifier);
2239     setString(&artwork_new->name_sorting, artwork_new->name);
2240   }
2241
2242   DrawInitText(artwork_new->name, 150, FC_YELLOW);
2243
2244   pushTreeInfo(node_first, artwork_new);
2245
2246   freeSetupFileHash(setup_file_hash);
2247
2248   free(directory_path);
2249   free(filename);
2250
2251   return TRUE;
2252 }
2253
2254 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
2255                                           TreeInfo *node_parent,
2256                                           char *base_directory, int type)
2257 {
2258   DIR *dir;
2259   struct dirent *dir_entry;
2260   boolean valid_entry_found = FALSE;
2261
2262   if ((dir = opendir(base_directory)) == NULL)
2263   {
2264     if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
2265       Error(ERR_WARN, "cannot read directory '%s'", base_directory);
2266     return;
2267   }
2268
2269   while ((dir_entry = readdir(dir)) != NULL)    /* loop until last dir entry */
2270   {
2271     struct stat file_status;
2272     char *directory_name = dir_entry->d_name;
2273     char *directory_path = getPath2(base_directory, directory_name);
2274
2275     /* skip entries for current and parent directory */
2276     if (strcmp(directory_name, ".")  == 0 ||
2277         strcmp(directory_name, "..") == 0)
2278     {
2279       free(directory_path);
2280       continue;
2281     }
2282
2283     /* find out if directory entry is itself a directory */
2284     if (stat(directory_path, &file_status) != 0 ||      /* cannot stat file */
2285         (file_status.st_mode & S_IFMT) != S_IFDIR)      /* not a directory */
2286     {
2287       free(directory_path);
2288       continue;
2289     }
2290
2291     free(directory_path);
2292
2293     /* check if this directory contains artwork with or without config file */
2294     valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first,node_parent,
2295                                                         base_directory,
2296                                                         directory_name, type);
2297   }
2298
2299   closedir(dir);
2300
2301   /* check if this directory directly contains artwork itself */
2302   valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first,node_parent,
2303                                                       base_directory, ".",
2304                                                       type);
2305   if (!valid_entry_found)
2306     Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
2307           base_directory);
2308 }
2309
2310 static TreeInfo *getDummyArtworkInfo(int type)
2311 {
2312   /* this is only needed when there is completely no artwork available */
2313   TreeInfo *artwork_new = newTreeInfo();
2314
2315   setTreeInfoToDefaults(artwork_new, type);
2316
2317   setString(&artwork_new->subdir,   UNDEFINED_FILENAME);
2318   setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
2319   setString(&artwork_new->basepath, UNDEFINED_FILENAME);
2320
2321   setString(&artwork_new->identifier,   UNDEFINED_FILENAME);
2322   setString(&artwork_new->name,         UNDEFINED_FILENAME);
2323   setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
2324
2325   return artwork_new;
2326 }
2327
2328 void LoadArtworkInfo()
2329 {
2330   DrawInitText("Looking for custom artwork:", 120, FC_GREEN);
2331
2332   LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
2333                                 options.graphics_directory,
2334                                 TREE_TYPE_GRAPHICS_DIR);
2335   LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
2336                                 getUserGraphicsDir(),
2337                                 TREE_TYPE_GRAPHICS_DIR);
2338
2339   LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
2340                                 options.sounds_directory,
2341                                 TREE_TYPE_SOUNDS_DIR);
2342   LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
2343                                 getUserSoundsDir(),
2344                                 TREE_TYPE_SOUNDS_DIR);
2345
2346   LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
2347                                 options.music_directory,
2348                                 TREE_TYPE_MUSIC_DIR);
2349   LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
2350                                 getUserMusicDir(),
2351                                 TREE_TYPE_MUSIC_DIR);
2352
2353   if (artwork.gfx_first == NULL)
2354     artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
2355   if (artwork.snd_first == NULL)
2356     artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
2357   if (artwork.mus_first == NULL)
2358     artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
2359
2360   /* before sorting, the first entries will be from the user directory */
2361   artwork.gfx_current =
2362     getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
2363   if (artwork.gfx_current == NULL)
2364     artwork.gfx_current =
2365       getTreeInfoFromIdentifier(artwork.gfx_first, GFX_CLASSIC_SUBDIR);
2366   if (artwork.gfx_current == NULL)
2367     artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
2368
2369   artwork.snd_current =
2370     getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
2371   if (artwork.snd_current == NULL)
2372     artwork.snd_current =
2373       getTreeInfoFromIdentifier(artwork.snd_first, SND_CLASSIC_SUBDIR);
2374   if (artwork.snd_current == NULL)
2375     artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
2376
2377   artwork.mus_current =
2378     getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
2379   if (artwork.mus_current == NULL)
2380     artwork.mus_current =
2381       getTreeInfoFromIdentifier(artwork.mus_first, MUS_CLASSIC_SUBDIR);
2382   if (artwork.mus_current == NULL)
2383     artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
2384
2385   artwork.gfx_current_identifier = artwork.gfx_current->identifier;
2386   artwork.snd_current_identifier = artwork.snd_current->identifier;
2387   artwork.mus_current_identifier = artwork.mus_current->identifier;
2388
2389 #if 0
2390   printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
2391   printf("sounds set == %s\n\n", artwork.snd_current_identifier);
2392   printf("music set == %s\n\n", artwork.mus_current_identifier);
2393 #endif
2394
2395   sortTreeInfo(&artwork.gfx_first, compareTreeInfoEntries);
2396   sortTreeInfo(&artwork.snd_first, compareTreeInfoEntries);
2397   sortTreeInfo(&artwork.mus_first, compareTreeInfoEntries);
2398
2399 #if 0
2400   dumpTreeInfo(artwork.gfx_first, 0);
2401   dumpTreeInfo(artwork.snd_first, 0);
2402   dumpTreeInfo(artwork.mus_first, 0);
2403 #endif
2404 }
2405
2406 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
2407                                   LevelDirTree *level_node)
2408 {
2409   /* recursively check all level directories for artwork sub-directories */
2410
2411   while (level_node)
2412   {
2413     char *path = getPath2(getLevelDirFromTreeInfo(level_node),
2414                           ARTWORK_DIRECTORY((*artwork_node)->type));
2415
2416     if (!level_node->parent_link)
2417     {
2418       TreeInfo *topnode_last = *artwork_node;
2419
2420       LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path,
2421                                     (*artwork_node)->type);
2422
2423       if (topnode_last != *artwork_node)
2424       {
2425         free((*artwork_node)->identifier);
2426         free((*artwork_node)->name);
2427         free((*artwork_node)->name_sorting);
2428
2429         (*artwork_node)->identifier   = getStringCopy(level_node->subdir);
2430         (*artwork_node)->name         = getStringCopy(level_node->name);
2431         (*artwork_node)->name_sorting = getStringCopy(level_node->name);
2432
2433         (*artwork_node)->sort_priority = level_node->sort_priority;
2434         (*artwork_node)->color = LEVELCOLOR((*artwork_node));
2435       }
2436     }
2437
2438     free(path);
2439
2440     if (level_node->node_group != NULL)
2441       LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
2442
2443     level_node = level_node->next;
2444   }
2445 }
2446
2447 void LoadLevelArtworkInfo()
2448 {
2449   DrawInitText("Looking for custom level artwork:", 120, FC_GREEN);
2450
2451   LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first);
2452   LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first);
2453   LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first);
2454
2455   /* needed for reloading level artwork not known at ealier stage */
2456
2457   if (strcmp(artwork.gfx_current_identifier, setup.graphics_set) != 0)
2458   {
2459     artwork.gfx_current =
2460       getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
2461     if (artwork.gfx_current == NULL)
2462       artwork.gfx_current =
2463         getTreeInfoFromIdentifier(artwork.gfx_first, GFX_CLASSIC_SUBDIR);
2464     if (artwork.gfx_current == NULL)
2465       artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
2466   }
2467
2468   if (strcmp(artwork.snd_current_identifier, setup.sounds_set) != 0)
2469   {
2470     artwork.snd_current =
2471       getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
2472     if (artwork.snd_current == NULL)
2473       artwork.snd_current =
2474         getTreeInfoFromIdentifier(artwork.snd_first, SND_CLASSIC_SUBDIR);
2475     if (artwork.snd_current == NULL)
2476       artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
2477   }
2478
2479   if (strcmp(artwork.mus_current_identifier, setup.music_set) != 0)
2480   {
2481     artwork.mus_current =
2482       getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
2483     if (artwork.mus_current == NULL)
2484       artwork.mus_current =
2485         getTreeInfoFromIdentifier(artwork.mus_first, MUS_CLASSIC_SUBDIR);
2486     if (artwork.mus_current == NULL)
2487       artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
2488   }
2489
2490   sortTreeInfo(&artwork.gfx_first, compareTreeInfoEntries);
2491   sortTreeInfo(&artwork.snd_first, compareTreeInfoEntries);
2492   sortTreeInfo(&artwork.mus_first, compareTreeInfoEntries);
2493
2494 #if 0
2495   dumpTreeInfo(artwork.gfx_first, 0);
2496   dumpTreeInfo(artwork.snd_first, 0);
2497   dumpTreeInfo(artwork.mus_first, 0);
2498 #endif
2499 }
2500
2501 static void SaveUserLevelInfo()
2502 {
2503   LevelDirTree *level_info;
2504   char *filename;
2505   FILE *file;
2506   int i;
2507
2508   filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
2509
2510   if (!(file = fopen(filename, MODE_WRITE)))
2511   {
2512     Error(ERR_WARN, "cannot write level info file '%s'", filename);
2513     free(filename);
2514     return;
2515   }
2516
2517   level_info = newTreeInfo();
2518
2519   /* always start with reliable default values */
2520   setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
2521
2522   setString(&level_info->name, getLoginName());
2523   setString(&level_info->author, getRealName());
2524   level_info->levels = 100;
2525   level_info->first_level = 1;
2526
2527   token_value_position = TOKEN_VALUE_POSITION_SHORT;
2528
2529   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
2530                                                  getCookie("LEVELINFO")));
2531
2532   ldi = *level_info;
2533   for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
2534   {
2535     if (i == LEVELINFO_TOKEN_NAME ||
2536         i == LEVELINFO_TOKEN_AUTHOR ||
2537         i == LEVELINFO_TOKEN_LEVELS ||
2538         i == LEVELINFO_TOKEN_FIRST_LEVEL)
2539       fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
2540
2541     /* just to make things nicer :) */
2542     if (i == LEVELINFO_TOKEN_AUTHOR)
2543       fprintf(file, "\n");      
2544   }
2545
2546   token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
2547
2548   fclose(file);
2549
2550   SetFilePermissions(filename, PERMS_PRIVATE);
2551
2552   freeTreeInfo(level_info);
2553   free(filename);
2554 }
2555
2556 char *getSetupValue(int type, void *value)
2557 {
2558   static char value_string[MAX_LINE_LEN];
2559
2560   if (value == NULL)
2561     return NULL;
2562
2563   switch (type)
2564   {
2565     case TYPE_BOOLEAN:
2566       strcpy(value_string, (*(boolean *)value ? "true" : "false"));
2567       break;
2568
2569     case TYPE_SWITCH:
2570       strcpy(value_string, (*(boolean *)value ? "on" : "off"));
2571       break;
2572
2573     case TYPE_YES_NO:
2574       strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
2575       break;
2576
2577     case TYPE_KEY:
2578       strcpy(value_string, getKeyNameFromKey(*(Key *)value));
2579       break;
2580
2581     case TYPE_KEY_X11:
2582       strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
2583       break;
2584
2585     case TYPE_INTEGER:
2586       sprintf(value_string, "%d", *(int *)value);
2587       break;
2588
2589     case TYPE_STRING:
2590       strcpy(value_string, *(char **)value);
2591       break;
2592
2593     default:
2594       value_string[0] = '\0';
2595       break;
2596   }
2597
2598   return value_string;
2599 }
2600
2601 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
2602 {
2603   int i;
2604   char *line;
2605   static char token_string[MAX_LINE_LEN];
2606   int token_type = token_info[token_nr].type;
2607   void *setup_value = token_info[token_nr].value;
2608   char *token_text = token_info[token_nr].text;
2609   char *value_string = getSetupValue(token_type, setup_value);
2610
2611   /* build complete token string */
2612   sprintf(token_string, "%s%s", prefix, token_text);
2613
2614   /* build setup entry line */
2615   line = getFormattedSetupEntry(token_string, value_string);
2616
2617   if (token_type == TYPE_KEY_X11)
2618   {
2619     Key key = *(Key *)setup_value;
2620     char *keyname = getKeyNameFromKey(key);
2621
2622     /* add comment, if useful */
2623     if (strcmp(keyname, "(undefined)") != 0 &&
2624         strcmp(keyname, "(unknown)") != 0)
2625     {
2626       /* add at least one whitespace */
2627       strcat(line, " ");
2628       for (i = strlen(line); i < token_comment_position; i++)
2629         strcat(line, " ");
2630
2631       strcat(line, "# ");
2632       strcat(line, keyname);
2633     }
2634   }
2635
2636   return line;
2637 }
2638
2639 void LoadLevelSetup_LastSeries()
2640 {
2641   /* ----------------------------------------------------------------------- */
2642   /* ~/.<program>/levelsetup.conf                                            */
2643   /* ----------------------------------------------------------------------- */
2644
2645   char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
2646   SetupFileHash *level_setup_hash = NULL;
2647
2648   /* always start with reliable default values */
2649   leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
2650
2651   if ((level_setup_hash = loadSetupFileHash(filename)))
2652   {
2653     char *last_level_series =
2654       getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
2655
2656     leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
2657                                                  last_level_series);
2658     if (leveldir_current == NULL)
2659       leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
2660
2661     checkSetupFileHashIdentifier(level_setup_hash, getCookie("LEVELSETUP"));
2662
2663     freeSetupFileHash(level_setup_hash);
2664   }
2665   else
2666     Error(ERR_WARN, "using default setup values");
2667
2668   free(filename);
2669 }
2670
2671 void SaveLevelSetup_LastSeries()
2672 {
2673   /* ----------------------------------------------------------------------- */
2674   /* ~/.<program>/levelsetup.conf                                            */
2675   /* ----------------------------------------------------------------------- */
2676
2677   char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
2678   char *level_subdir = leveldir_current->subdir;
2679   FILE *file;
2680
2681   InitUserDataDirectory();
2682
2683   if (!(file = fopen(filename, MODE_WRITE)))
2684   {
2685     Error(ERR_WARN, "cannot write setup file '%s'", filename);
2686     free(filename);
2687     return;
2688   }
2689
2690   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
2691                                                  getCookie("LEVELSETUP")));
2692   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
2693                                                level_subdir));
2694
2695   fclose(file);
2696
2697   SetFilePermissions(filename, PERMS_PRIVATE);
2698
2699   free(filename);
2700 }
2701
2702 static void checkSeriesInfo()
2703 {
2704   static char *level_directory = NULL;
2705   DIR *dir;
2706   struct dirent *dir_entry;
2707
2708   /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
2709
2710   level_directory = getPath2((leveldir_current->in_user_dir ?
2711                               getUserLevelDir(NULL) :
2712                               options.level_directory),
2713                              leveldir_current->fullpath);
2714
2715   if ((dir = opendir(level_directory)) == NULL)
2716   {
2717     Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
2718     return;
2719   }
2720
2721   while ((dir_entry = readdir(dir)) != NULL)    /* last directory entry */
2722   {
2723     if (strlen(dir_entry->d_name) > 4 &&
2724         dir_entry->d_name[3] == '.' &&
2725         strcmp(&dir_entry->d_name[4], LEVELFILE_EXTENSION) == 0)
2726     {
2727       char levelnum_str[4];
2728       int levelnum_value;
2729
2730       strncpy(levelnum_str, dir_entry->d_name, 3);
2731       levelnum_str[3] = '\0';
2732
2733       levelnum_value = atoi(levelnum_str);
2734
2735 #if 0
2736       if (levelnum_value < leveldir_current->first_level)
2737       {
2738         Error(ERR_WARN, "additional level %d found", levelnum_value);
2739         leveldir_current->first_level = levelnum_value;
2740       }
2741       else if (levelnum_value > leveldir_current->last_level)
2742       {
2743         Error(ERR_WARN, "additional level %d found", levelnum_value);
2744         leveldir_current->last_level = levelnum_value;
2745       }
2746 #endif
2747     }
2748   }
2749
2750   closedir(dir);
2751 }
2752
2753 void LoadLevelSetup_SeriesInfo()
2754 {
2755   char *filename;
2756   SetupFileHash *level_setup_hash = NULL;
2757   char *level_subdir = leveldir_current->subdir;
2758
2759   /* always start with reliable default values */
2760   level_nr = leveldir_current->first_level;
2761
2762   checkSeriesInfo(leveldir_current);
2763
2764   /* ----------------------------------------------------------------------- */
2765   /* ~/.<program>/levelsetup/<level series>/levelsetup.conf                  */
2766   /* ----------------------------------------------------------------------- */
2767
2768   level_subdir = leveldir_current->subdir;
2769
2770   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
2771
2772   if ((level_setup_hash = loadSetupFileHash(filename)))
2773   {
2774     char *token_value;
2775
2776     token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
2777
2778     if (token_value)
2779     {
2780       level_nr = atoi(token_value);
2781
2782       if (level_nr < leveldir_current->first_level)
2783         level_nr = leveldir_current->first_level;
2784       if (level_nr > leveldir_current->last_level)
2785         level_nr = leveldir_current->last_level;
2786     }
2787
2788     token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
2789
2790     if (token_value)
2791     {
2792       int level_nr = atoi(token_value);
2793
2794       if (level_nr < leveldir_current->first_level)
2795         level_nr = leveldir_current->first_level;
2796       if (level_nr > leveldir_current->last_level + 1)
2797         level_nr = leveldir_current->last_level;
2798
2799       if (leveldir_current->user_defined || !leveldir_current->handicap)
2800         level_nr = leveldir_current->last_level;
2801
2802       leveldir_current->handicap_level = level_nr;
2803     }
2804
2805     checkSetupFileHashIdentifier(level_setup_hash, getCookie("LEVELSETUP"));
2806
2807     freeSetupFileHash(level_setup_hash);
2808   }
2809   else
2810     Error(ERR_WARN, "using default setup values");
2811
2812   free(filename);
2813 }
2814
2815 void SaveLevelSetup_SeriesInfo()
2816 {
2817   char *filename;
2818   char *level_subdir = leveldir_current->subdir;
2819   char *level_nr_str = int2str(level_nr, 0);
2820   char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
2821   FILE *file;
2822
2823   /* ----------------------------------------------------------------------- */
2824   /* ~/.<program>/levelsetup/<level series>/levelsetup.conf                  */
2825   /* ----------------------------------------------------------------------- */
2826
2827   InitLevelSetupDirectory(level_subdir);
2828
2829   filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
2830
2831   if (!(file = fopen(filename, MODE_WRITE)))
2832   {
2833     Error(ERR_WARN, "cannot write setup file '%s'", filename);
2834     free(filename);
2835     return;
2836   }
2837
2838   fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
2839                                                  getCookie("LEVELSETUP")));
2840   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
2841                                                level_nr_str));
2842   fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
2843                                                handicap_level_str));
2844
2845   fclose(file);
2846
2847   SetFilePermissions(filename, PERMS_PRIVATE);
2848
2849   free(filename);
2850 }