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